The IRC Connector bridges eevee.bot to IRC networks. It manages one or more IRC connections defined in a YAML configuration file, translates incoming IRC events (messages, actions, notices) into NATS messages for the router, and relays outgoing NATS messages back to IRC channels and users. Each IRC connection is identified by a unique name, and all NATS subjects include that name so downstream consumers can route messages to the correct instance.

Features

  • Multiple connections — connect to any number of IRC networks simultaneously from a single process
  • Bidirectional messaging — supports privmsg, action (/me), and notice messages in both directions
  • Hot config reload — watches the config file and reconnects automatically on changes
  • Control channel — runtime join, part, kick, and user-list queries via NATS control messages
  • Auto-reconnect and auto-rejoin — configurable retry behavior for network drops and channel kicks
  • Post-connect actions — auto-join channels after connecting
  • Prometheus metrics — message counters, connection gauges, channel gauges, error counters
  • Uptime and stats reporting via NATS

Hot Reload

Configuration is file-driven and hot-reloaded: when the YAML config file changes on disk, chokidar detects the change and connector-irc disconnects all existing clients and reconnects using the new settings, with no process restart required.

NATS Subjects

Incoming Messages (IRC → NATS)

SubjectDescription
chat.message.incoming.irc.{name}.{channel}.{nick}Incoming privmsg or action from IRC

Outgoing Messages (NATS → IRC)

SubjectPayloadDescription
chat.message.outgoing.irc.{name}.>{ channel, text }Send a message to a channel/user
chat.action.outgoing.irc.{name}.>{ channel, text }Send a /me action
chat.notice.outgoing.irc.{name}.>{ channel, text }Send a notice to a channel
chat.notice.outgoing.irc.{name}{ target, text }Send a private notice to a user

Control Channel

Send JSON messages to control.chatConnectors.irc.{name} to control the bot at runtime:

Join a channel:

  {
  "action": "join",
  "data": { "channel": "#newchannel", "key": "optionalkey" }
}
  

Part a channel:

  {
  "action": "part",
  "data": { "channel": "#oldchannel" }
}
  

Kick a user:

  {
  "action": "kick",
  "data": { "channel": "#channel", "nick": "troublemaker", "reason": "optional reason" }
}
  

List users in a channel:

  {
  "action": "list-users-in-channel",
  "data": { "channel": "#channel", "replyChannel": "ephemeral.reply.subject" }
}
  

The user list response is published to the replyChannel:

  {
  "channel": "#channel",
  "users": [
    { "nick": "alice", "ident": "alice", "hostname": "user/host", "modes": ["o"], "isChannelAdmin": true }
  ],
  "count": 1
}
  

Each user object includes an isChannelAdmin boolean — true if the user has channel mode +h (halfop), +o (op), +a (admin/protect), or +q (owner).

If the NAMES response times out (10 seconds), an error response is sent:

  {
  "channel": "#channel",
  "error": "Timeout waiting for user list",
  "users": []
}
  

Get modes for a user:

  {
  "action": "get-modes-for-user",
  "data": { "channel": "#channel", "nick": "someuser", "replyChannel": "ephemeral.reply.subject" }
}
  

The modes response is published to the replyChannel:

  {
  "channel": "#channel",
  "nick": "someuser",
  "modes": ["o", "v"],
  "isChannelAdmin": true
}
  

If the user is not found in the channel:

  {
  "channel": "#channel",
  "nick": "someuser",
  "modes": [],
  "isChannelAdmin": false,
  "warning": "User not found in channel"
}
  

If the WHO query times out (5 seconds):

  {
  "channel": "#channel",
  "nick": "someuser",
  "modes": [],
  "isChannelAdmin": false,
  "error": "Timeout waiting for user modes"
}
  

This action sends a WHO query to the IRC server every time (no caching) to ensure fresh, authoritative mode data.

Stats Subjects

SubjectDescription
stats.uptimeResponds with module uptime via replyChannel
stats.emit.requestResponds with full stats (uptime, memory, Prometheus metrics) via replyChannel
control.connectors.irc.core.>Logs core control messages

Architecture

  ┌─────────────────────────────────────────────────────┐
│                   connector-irc                      │
│                                                      │
│  ┌──────────────┐    ┌──────────────┐               │
│  │  IrcClient   │    │  IrcClient   │  ...          │
│  │  (liberachat)│    │  (oftc)      │               │
│  └──────┬───────┘    └──────┬───────┘               │
│         │                   │                        │
│    privmsg/action      privmsg/action                │
│    events              events                       │
│         │                   │                        │
│         ▼                   ▼                        │
│  ┌─────────────────────────────────┐                │
│  │          main.mts               │                │
│  │  ┌────────────┐ ┌────────────┐  │                │
│  │  │  chokidar  │ │  express   │  │                │
│  │  │  (config   │ │  (metrics  │  │                │
│  │  │   reload)  │ │   HTTP)    │  │                │
│  │  └────────────┘ └────────────┘  │                │
│  └──────────────┬──────────────────┘                │
│                 │                                    │
└─────────────────┼────────────────────────────────────┘
                  │ NATS
                  ▼
         ┌──────────────┐
         │    router     │  →  command modules
         └──────────────┘
  

Key components:

  • main.mts — Entry point. Sets up NATS, reads config, creates IrcClient instances, wires event handlers, subscribes to outgoing and control NATS subjects, watches the config file for changes.
  • IrcClient (lib/irc-client.mts) — Wraps irc-framework’s Client with eevee-specific concerns: status tracking, channel management, Prometheus metrics, and EventEmitter passthrough of all IRC events.

Configuration

The IRC Connector is deployed as a botmodule with moduleName: "irc":

  botModules:
- name: irc-connector
  spec:
    size: 1
    image: ghcr.io/eeveebot/connector-irc:latest
    pullPolicy: Always
    metrics: true
    metricsPort: 8080
    ipcConfig: my-eevee-bot
    moduleName: irc
    moduleConfig: |
      connections:
      - name: my-irc-network
        irc:
          host: irc.my-irc-network.local
          port: 6667
          ssl: true
          autoReconnect: true
          autoReconnectWait: 5000
          autoReconnectMaxRetries: 10
          autoRejoin: true
          autoRejoinWait: 5000
          autoRejoinMaxRetries: 5
          pingInterval: 30
          pingTimeout: 120
        ident:
          nick: eevee
          username: eevee
          gecos: eevee.bot
          version: "0.4.20"
          quitMsg: "eevee v0.4.20"
        postConnect:
          - action: join
            join:
              - channel: '#bots'
              - channel: '#cool-kids-club'
        commands:
          commonPrefixRegex: "^-"
  

IRC Settings

KeyTypeDefaultDescription
hoststringlocalhostIRC server hostname
portnumber/string6667IRC server port
sslbooleanfalseEnable TLS
autoReconnectbooleantrueReconnect on disconnect
autoReconnectMaxRetriesnumber10Max reconnect attempts
autoReconnectWaitnumber5000Milliseconds between retries
autoRejoinbooleantrueRejoin channels after kick
autoRejoinMaxRetriesnumber5Max rejoin attempts
autoRejoinWaitnumber5000Milliseconds between rejoin retries
pingIntervalnumber30Seconds between keepalive pings
pingTimeoutnumber120Seconds before ping timeout

Identity Settings

KeyTypeDefaultDescription
nickstringeeveeBot nickname
usernamestringeeveeIRC username/ident
gecosstringeevee.botReal name (GECOS) field
quitMsgstringQuit message (required)
versionstringconnector versionCTCP VERSION response

Post-Connect Actions

Each entry has an action field. Currently only join is supported:

  postConnect:
- action: join
  join:
  - channel: '#general'
  - channel: '#private'
    key: channelkey