
Security News
Astral Launches pyx: A Python-Native Package Registry
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
npm i modernirc
import { createBot } from 'modernirc'
async function main() {
await createBot({
uplink: {
host: 'localhost',
port: 6667,
},
autojoin: [{ name: '#Home' }],
})
}
main().catch(console.error)
Congratulations, the bot is now started.
The configuration object that is passed as the first and only
argument of the init
function has the following default properties :
{
"uplink": {
"host": "localhost",
"port": 6667,
"password": null,
"tls": false,
"rejectUnauthorized": false,
"autoPong": true
},
"identity": {
"nickname": "modernbot",
"username": "modernbot",
"realname": "modernbot",
"password": null
},
"autojoin": [],
"modules": {},
"logger": {},
"api": {
"enabled": false,
"listen": {
"port": 9999,
"host": ""
},
"auth": {
"key": null
}
}
}
The uplink configuration contains all the details of the connection to be made.
The
host
property can be the domain name or the IP address of the IRC server.
On an IRC server, three properties are defining an user identity :
The password
property is used by the bot if there is a services operator (like Anope or Atheme)
to identify against a registered nickname. If the property is defined, the bot will wait for
a specific message from the NickServ service before sending the identify command.
You can force the bot to automatically join channels at startup like this :
{
autojoin: [
{ name: '#Foo' },
]
}
And if the channel needs a key :
{
autojoin: [
{ name: '#Foo', key: 'bar' }
]
}
By default, the bot is doing just... nothing. It connects, takes some configuration from the server first messages but it will not react to anything.
This is the responsibility of the modules added to the bot.
In your project folder, start by creating a modules
folder at the root. It will contain all the
modules you'll add to the bot. Inside this new folder, create a new folder
which will contain the actual module you're creating.
In our example, we'll create a module that will greet every people joining a channel where the bot is present.
mkdir -p modules/helloworld
// modules/helloworld/index.js
export function init(bot, options) {
return {
onJoin(message) {
if (message.prefix.nickname !== bot.identity.nickname) {
bot.message(message.params[0], `Hello, ${message.prefix.nickname}!`)
}
},
}
}
The name of the function is built from the event name prefixed by
on
. For example, thejoin
event will trigger theonJoin
method, and aprivmsg
message will trigger aonPrivmsg
method, etc.
The message is a JSON object containing all the information relative to the incoming message, for example :
:foo!foo@bar.baz PRIVMSG #Home :Hello World !
{
"prefix": {
"nickname": "foo",
"username": "foo",
"hostname": "bar.baz"
},
"code": "PRIVMSG",
"command": "privmsg",
"params": ["#Home"],
"message": "Hello World !",
"self": false
}
The self
property is set to true
if the incoming message is a message from the bot itself. It can happen
on join and channel notice events.
IRC commands are alphanumeric codes that are translated in the library into some human-readable events.
First, there is a special event that handles every message recieved from
the server, the message
event.
Then, please find below the complete list of event names available:
Command | Event Name |
---|---|
PRIVMSG | privmsg |
JOIN | join |
QUIT | quit |
PART | part |
NOTICE | notice |
NICK | nick |
KICK | kick |
TOPIC | topic |
MODE | mode |
INVITE | invite |
001 | welcome |
002 | yourhost |
003 | created |
004 | myinfo |
005 | serverconfig |
300 | none |
302 | userhost |
303 | ison |
301 | away |
305 | unaway |
306 | noaway |
311 | whoisuser |
312 | whoisserver |
313 | whoisoperator |
317 | whoisidle |
318 | endofwhois |
319 | whoischannels |
314 | whomasuser |
369 | endofwhowas |
321 | liststart |
322 | list |
323 | listend |
324 | channelmodeis |
325 | uniqopis |
331 | notopic |
332 | topic |
341 | inviting |
342 | summoning |
346 | invitelist |
347 | endofinvitelist |
348 | exceptlist |
349 | endofexceptlist |
351 | version |
352 | whoreply |
315 | endofwho |
353 | namreply |
366 | endofnames |
364 | links |
365 | endoflinks |
367 | banlist |
368 | endofbanlist |
371 | info |
374 | endofinfo |
375 | motdstart |
372 | motd |
376 | endofmotd |
381 | youreoper |
382 | rehashing |
383 | youreservice |
391 | time |
392 | usersstart |
393 | users |
394 | endofusers |
395 | nousers |
200 | tracelink |
201 | traceconnecting |
202 | tracehandshake |
203 | traceunknown |
204 | traceoperator |
205 | traceuser |
206 | traceserver |
207 | traceservice |
208 | tracenewtype |
209 | traceclass |
210 | tracereconnect |
261 | tracelog |
262 | traceend |
211 | statslinkinfo |
212 | statscommands |
213 | statscline |
214 | statsnline |
215 | statsiline |
216 | statskline |
218 | statsyline |
219 | endofstats |
241 | statsline |
242 | statsuptime |
243 | statsoline |
244 | statshline |
221 | umodeis |
251 | luserclient |
252 | luserop |
253 | luserunknown |
254 | luserchannels |
255 | luserme |
256 | adminme |
257 | adminloc1 |
258 | adminloc2 |
259 | adminemail |
263 | tryagain |
401 | nosuchnick |
402 | nosuchserver |
403 | nosuchchannel |
404 | cannotsendtochan |
405 | toomanychannels |
406 | wasnosuchnick |
407 | toomanytargets |
408 | nosuchservice |
409 | noorigin |
411 | norecipient |
412 | notexttosend |
413 | notoplevel |
414 | wildtoplevel |
421 | unknowncommand |
422 | nomotd |
423 | noadmininfo |
424 | fileerror |
431 | nonicknamegiven |
432 | erroneousnickname |
433 | nicknameinuse |
436 | nickcollision |
437 | unavailresource |
441 | usernotinchannel |
442 | notonchannel |
443 | useronchannel |
444 | nologin |
445 | summondisabled |
446 | usersdisabled |
451 | notregistered |
461 | needmoreparams |
462 | alreadyregistered |
463 | nopermforhost |
464 | passwdmismatch |
465 | yourebannedcreep |
467 | keyset |
471 | channelisfull |
472 | unknownmode |
473 | inviteonlychan |
474 | bannedfromchan |
475 | badchannelkey |
476 | badchanmask |
477 | nochanmodes |
478 | banlistfull |
481 | noprivileges |
482 | chanoprivsneeded |
483 | cantkillserver |
484 | restricted |
485 | uniqopprivsneeded |
491 | nooperhost |
501 | umodeunknownflag |
502 | usersdontmatch |
209 | traceclass |
231 | serviceinfo |
232 | endofservices |
233 | service |
235 | servlistend |
316 | whoischanop |
362 | closing |
373 | infostart |
466 | youwillbebanned |
217 | statsqline |
234 | servlist |
361 | killdone |
363 | closeend |
384 | myportis |
476 | badchanmask |
492 | noservicehost |
All events can be ignored or handled according to what you want to do with your bot.
Please note that even if all RFC 1459/2812 events have been mapped in the library, some of them are only triggered between servers or for IRCOps, so your bot will never receive them.
A logger is embedded in the robot to allow unified logs. It can write logs in console, in a file and to an HTTP request (as JSON or URL-encoded).
{
// ...
"logger": {
"level": "info",
"levels": ["error", "warning", "info", "log", "debug"],
"transports": [{
"type": "console",
"level": "debug",
"opts": { "pretty": true }
}],
"name": "app"
}
}
By default, it logs to the console every message with level more important than info, so info, warning and error. You can customise the whole thing, and create custom transports if you need.
When a module uses the embedded logger (options.logger
), it will automatically append the name
of the module to the name in the configuration.
By default, the name is app
. In a module hello
, the name
property will be app/hello
.
You can create your own logger children if you want with the child
method.
// assuming the logger name is `app`
const child = logger.child('childName')
// this child will log with name `app/childName`
A transport in the ModernIRC logger is a function taking the name
and the opts
object. It returns an object
with a single function output(_level: string, _raw: string)
doing the real job of outputting the log.
The output
function can be sync or async, it will never be awaited to avoid I/O issues.
Example :
// configuration
{
//...
logger: {
//...
transports: [{
type: 'custom',
level: 'debug',
builder: (name, opts) => ({
output(_level: string, _raw: string) {
console.log(`${_level}: ${_raw}`)
},
}),
}],
},
}
If you don't want to write a full custom transport, but only format the output string,
you can custom the log string format by giving a formatter
property to a default transport metadata.
A formatter is a function taking an object with the following properties :
date
: Date of the logname
: Name of the logger (app or child)level
: Log leveltext
: Text message of the logThis function must return a string.
Example :
{
//...
logger: {
//...
transports: [{
type: 'console',
level: 'debug',
formatter: ({ date, name, level, text }) => `${level} ${date} - ${text}\n`,
}],
},
}
You can control some actions of the bot from an HTTP interface. You must enable api
in the configuration
first, and define an authentication key.
{
"api": {
"enabled": true,
"auth": {
"key": "123456"
}
}
}
The key can be any string, but we recommend using UUIDs or a strong random bytes string.
For every call done to this HTTP gateway, the key must be set in an HTTP header X-Key
and is
mandatory. If a call is done without this key, a 403 Forbidden will be returned.
GET /identity
{
"nickname": "modernbot",
"username": "modernbot",
"realname": "modernbot"
}
This can be helpful to get the current identity of the bot.
Note that if you change the nickname, this will change it there as well.
GET /channels
{
"#Bots": {
"name": "#Bots",
"users": [
{
"nickname": "chaksoft",
"mode": ""
},
{
"nickname": "modernbot",
"mode": ""
}
]
}
}
Gets the full list of channels and users where the bot is present as well. This list is up-to-date according to the different join, part, kick and quit events.
Note that the bot will always be in all the users list.
GET /modules
[
{
"name": "hello",
"enabled": true
}
]
Gets the list of all the modules in the bot configuration and if they are enabled or not. In the future, the control API will be able to enable and disable modules on runtime.
All the modifiers are just shortcuts for sending commands. Please note that all these endpoints will return 200 if the command has been successfully transmitted to the bot. But if the IRC server refused the command for whatever reason, it will not be bubbled back to the API.
In case of 200, all below endpoints will return a simple JSON with { ok: true }
.
POST /nick
JSON Body
{
"nickname": "ModernBot2"
}
Changes the nickname.
POST /join
JSON Body
{
"channel": "#Foo",
"key": "bar"
}
Joins a channel, key
is optional if there is no key for the channel.
POST /part
JSON Body
{
"channel": "#Foo"
}
Leaves the channel. If the bot is not in the channel, this command does nothing.
POST /privmsg
JSON Body
{
"target": "#Bots",
"message": "Hello there !"
}
Sends a message to the designated target.
POST /notice
JSON Body
{
"target": "chaksoft",
"message": "hello there, this is a notice !"
}
Sends a notice to the designated target.
POST /kick
JSON Body
{
"channel": "#Bots",
"target": "chaksoft",
"reason": "Chop Chop !"
}
Kicks the designated target from the designated channel. Please note that if the bot has not enough privileges, the command will do nothing.
POST /module
JSON Body
{
"moduleName": "hello",
"someparam": "somevalue"
}
Sends a message to a specific module. The moduleName
property is mandatory, if empty it will return a 404 error.
The other properties of the body depends on what the module is waiting for in its onApiRequest
handler.
export async function init(bot, options) {
return {
onApiRequest(body) {
// do something here with the body (will not contain the `moduleName` property)
},
}
}
The onApiRequest
can return a JSON object that will be serialized into the HTTP response (in the result
property).
If you return nothing, the response will be { "ok": true, "result": null }
.
bot.send(str)
Sends a raw command string to the server.
bot.message(target, str)
Sends a text message to the target. Target can be a channel or a private channel with another user.
bot.notice(target, str)
Sends a notice to the target. Target usually is an user but can also be a channel.
bot.join(channel, [key = null])
Joins a channel with the given key if applicable. Check for the error codes in the table above to check for available answers.
bot.changeNickname(nickname)
Changes the nickname of the bot. This change is temporary and is not saved. When the bot restarts, it will take the configured identity to register itself.
bot.part(channel, [reason = ''])
Makes the bot leave a specified channel and optionally a reason.
bot.kick(channel, target, [reason = ''])
Kicks a target (usually an user) from a channel. Please note that this
command will do nothing if the bot has no operator rights in the specified
channel. Otherwise, the CHANOPRIVSNEEDED
error is sent by the server as
an failure notification.
bot.joinedChannels
Array of channels where the bot is present. The list of users inside each channel is kept updated following the part, kick and quit events.
bot.serverConfig
Object containing all the supported modes and flags from the server. At startup, a server will send a full list of all supported modes and specific flags enabled.
options.logger
This property contains the child logger affected to the module. In order to have unified logs, it's
recommended to use this one instead of console.log
.
FAQs
IRC library for creating modern IRC bots
We found that modernirc demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.
Security News
The latest Opengrep releases add Apex scanning, precision rule tuning, and performance gains for open source static code analysis.