Skip to content

revrsefr/skybot

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

823 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Skybot (will rename it when find something).

Goals

  • simplicity
    • little boilerplate
    • minimal magic
  • power
    • multithreading
    • automatic reloading
    • extensibility

Features

  • Multithreaded dispatch and the ability to connect to multiple networks at a time.
  • Easy plugin development with automatic reloading and a simple hooking API.

Requirements

To install dependencies, run:

pip install -r requirements.txt

Database

Skybot defaults to SQLite (stored under persist/).

To use PostgreSQL instead, add this to config.json:

"database": {
  "type": "postgres",
  "postgres": {
    "dsn": "postgresql://USER:PASSWORD@HOST:5432/DBNAME",
    "schema_prefix": "skybot"
  }
}

Install the driver:

pip install "psycopg[binary]"

Skybot runs on Python 2.7, 3.7 and Python 3.13.(WIP in some areas to full update code to 3.13, for now partial support.)

IRCv3 support (message-tags, etc)

Skybot supports a small but useful subset of IRCv3:

  • message-tags — parses incoming IRCv3 message tags (@key=value;flag) and exposes them to plugins
  • batch — enables related-message groups (used by features like chat history)
  • cap-notify — server can notify clients about new/removed capabilities
  • labeled-response — lets clients label commands and correlate server responses via @label=...
  • away-notify — notifies you when users set/unset away via AWAY
  • server-time — enables a time tag on servers that support it
  • echo-message — server echoes your own PRIVMSG/NOTICE back to you (useful for message tags like msgid/time on your own messages)
  • setname — allows changing realname on an active connection via SETNAME
  • +draft/multiline — work-in-progress multiline messages via BATCH (messages can include line breaks)
  • account-tag — enables an account tag on servers that support it
  • account-notify — sends ACCOUNT messages when a user's account status changes
  • chghost — sends CHGHOST messages when a user's user/host changes
  • extended-join — JOIN messages may include account/realname fields
  • invite-notify — notifies you when you are invited to channels
  • inspircd.org/stats-tags — InspIRCd vendor capability adding extra message-tags to some stats output
  • multi-prefix — NAMES replies can include multiple user prefixes (e.g. @+nick)
  • userhost-in-names — NAMES replies may include nick!user@host entries
  • standard-replies — server may use standardized numeric replies for common errors (varies by server)
  • extended-monitor — extends MONITOR to send metadata change notifications (away/account/chghost/setname) for monitored nicks
  • chathistory / draft/chathistory — allows requesting chat history via the CHATHISTORY command (server support varies)
  • msgid — when supported, servers attach a msgid tag to messages (often delivered via message-tags)

Related (non-CAP) extensions:

  • WHOX (ISUPPORT token) — extended WHO replies (numeric 354). Skybot tracks ISUPPORT and can send WHOX-style queries via conn.who(mask, fields=..., token=...).
  • MONITOR (ISUPPORT token) — server-side online/offline notifications via the MONITOR command (numerics 730-734). Skybot provides helpers: conn.monitor_add([...]), conn.monitor_remove([...]), conn.monitor_list(), conn.monitor_status(), conn.monitor_clear().
  • UTF8ONLY (ISUPPORT token) — server enforces UTF-8-only traffic; servers may use FAIL/WARN ... INVALID_UTF8 when standard-replies is enabled.
  • BOT (ISUPPORT token) — advertises the bot user mode (often +B) used by the bot-mode extension. Skybot will set it on connect when available (disable with connection setting "bot_mode": false).

Configuration

IRCv3 capability configuration is per-connection (inside the connections object).

Default behavior: Skybot requests message-tags, batch, cap-notify, labeled-response, away-notify, server-time, echo-message, setname, draft/multiline, account-tag, account-notify, chghost, extended-join, invite-notify, inspircd.org/stats-tags, multi-prefix, userhost-in-names, standard-replies, and extended-monitor.

Note: When echo-message is enabled, Skybot ignores its own echoed PRIVMSG/NOTICE lines to prevent double-processing (e.g., command loops when the bot speaks with its own prefix).

Note: chathistory / draft/chathistory is supported by Skybot but is intentionally opt-in (some servers may auto-send history on join when the capability is enabled). Add it to ircv3.caps only if you want it.

Some networks may still send server-driven BATCH ... chathistory on join even when you don't request the capability. Skybot suppresses these batches by default so plugins/logging don't treat history as new messages.

Per connection you can control this in config.json:

  • ignore_chathistory_batches (bool, default true): suppress all chathistory batches.
  • ignore_chathistory_channels (list of strings): if set, suppress only these targets.
  • allow_chathistory_channels (list of strings): if set, allow only these targets (everything else suppressed).

To override the requested capabilities:

"connections": {
  "network name": {
    "server": "irc.example.net",
    "nick": "Skybot",
    "ircv3": {
      "caps": ["message-tags", "batch", "cap-notify", "labeled-response", "server-time", "echo-message", "account-tag", "account-notify", "chghost", "extended-join", "invite-notify", "inspircd.org/stats-tags", "multi-prefix", "userhost-in-names", "standard-replies", "extended-monitor", "msgid", "draft/msgid"]
    }
  }
}

("caps": [...] at the top level of the connection is also accepted for compatibility.)

Plugin access

For every incoming line, plugins receive a tags dict:

  • input.tags — mapping of tag name to value
    • tags without a value (flag tags) map to None
    • tags with an explicit empty value (key=) map to ""

Example:

@hook.regex(r"^\\.whoami$")
def whoami(inp, reply=None, tags=None, **kwargs):
    reply("account=%s time=%s" % (tags.get("account"), tags.get("time")))

If you want to use labeled-response from a plugin:

label = inp.conn.cmd_labeled("WHO", [inp.nick])
inp.reply("sent WHO with label=%s" % label)

If you want to request account/host info using WHOX (when the server supports it):

# Request: account (a), user (u), host (h), nick (n)
inp.conn.who("#channel", fields="auhn", token=42)

# Listen for numeric 354 (RPL_WHOSPCRPL) in a plugin:
@hook.event("354")
def whox_reply(inp, **kwargs):
    # inp.paraml contains the WHOX fields in server-defined order.
    pass

Chat history notes

Skybot does not automatically fetch history on connect; it only provides the plumbing.

If the server supports CHATHISTORY, a plugin can request history using the connection object:

  • conn.chathistory_latest("#channel", limit=50)
  • conn.chathistory_before("#channel", reference="2025-12-17T00:00:00.000Z", limit=50)
  • conn.chathistory_after("#channel", reference="2025-12-17T00:00:00.000Z", limit=50)

Servers deliver results using BATCH plus @batch=... message tags on the enclosed lines. You can read the batch association from input.tags.get("batch").

Feeds plugin

The feeds plugin (plugins/feeds.py) can watch RSS/Atom feeds and announce new items into IRC channels.

Configuration

Top-level feeds settings:

"feeds": {
  "poll_interval": 300,
  "max_items_per_poll": 3
}

Notes:

  • Watches are stored in the database, so they survive restarts and work with both SQLite and PostgreSQL.
  • The plugin polls on normal bot activity (event-driven), so in a totally idle network it may not poll until some messages/events are seen.

Commands

  • .feed add <url> [#channel] — start watching a feed (defaults to the current channel)
  • .feed remove <url> [#channel] — stop watching a feed
  • .feed list — list watched feeds
  • .feed info — show plugin status

Crowdcontrol plugin

Crowdcontrol (plugins/crowdcontrol.py) applies moderation rules to channel messages.

Rule types

Rules are configured under the top-level crowdcontrol array. A rule can match by:

  • re — a regular expression
  • badness — mojibake/spam heuristic score (matches when badness(message) >= threshold)
  • flood — per-user flood control (matches when a user sends more than count lines within seconds)

Common fields:

  • msg — message used either as a warning (reply) or as a kick reason
  • kick1 to kick, 0 to only warn
  • ban_length0 no ban, -1 ban without unbanning, >0 ban then unban after N seconds

Temporary bans: when ban_length > 0, the plugin schedules an unban in the database and unbans asynchronously (no time.sleep() blocking). This means unbans survive bot restarts.

Optional tuning (defaults are fine):

"crowdcontrol_unban": {
  "poll_interval": 10,
  "batch": 50
}

Flood control example

"crowdcontrol": [
  {
    "flood": {
      "count": 5,
      "seconds": 8,
      "escalate": {"ban_after": 2, "window": 600, "ban_length": 300}
    },
    "msg": "Flood in #{channel} ({flood_strikes}/{flood_ban_after}). Action={flood_action}.",
    "kick": 1,
    "ban_length": 0
  }
]

Flood escalation (kick first, ban on repeat):

  • escalate.ban_after — strike count to start banning (default 2)
  • escalate.window — seconds in which strikes accumulate before resetting (default 600)
  • escalate.ban_length — seconds to ban once ban_after is reached (default 300)

Notes:

  • Keep the rule-level ban_length as 0 for flood rules. Escalation controls bans.
  • Flood escalation uses the same identity key as the flood limiter (nick!user@host), scoped per server+channel.

Flood backend:

  • Default is in-memory (fast, bounded with LRU).

  • Optional DB-backed mode exists if you really want no per-user state in the bot process, but it does a DB read+write per message and may not scale well on very busy channels.

    "crowdcontrol": [ { "flood": {"backend": "db", "count": 5, "seconds": 8}, "msg": "Flood in #{channel} (>{flood_count}/{flood_seconds}s via {flood_backend}).", "kick": 1, "ban_length": 0 } ]

Flood tuning keys (in-memory backend only):

  • flood.max_keys — maximum tracked identities (default 50000)
  • flood.idle_ttl — evict state for idle identities after N seconds (default max(300, seconds*10))

Flood tuning keys (DB backend):

  • flood.idle_ttl — controls cleanup of old DB rows

Mojibake/badness example

"crowdcontrol": [
  {
    "badness": 2,
    "msg": "Mojibake/spam in #{channel} (score={badness} threshold={threshold}).",
    "kick": 1,
    "ban_length": 60
  }
]

Message placeholders

In msg, you can use placeholders (unknown placeholders are left as-is):

  • {channel} / {chan} (also supports Ruby-style #{channel})
  • {nick} {user} {host} {server} {message}
  • {badness} {threshold}
  • {flood_backend} {flood_count} {flood_seconds} {flood_hits} {flood_tokens} {flood_max_keys}
  • {flood_strikes} {flood_ban_after} {flood_strike_window} {flood_action} {flood_ban_length}

Database tables

Crowdcontrol creates the following tables automatically:

  • crowdcontrol_unbans — scheduled unbans (used when ban_length > 0)
  • crowdcontrol_flood — flood token buckets (only when using flood.backend: "db")
  • crowdcontrol_flood_strikes — strike counters for flood escalation (only when using flood.backend: "db")

License

Skybot is public domain. If you find a way to make money using it, I'll be very impressed.

See LICENSE for precise terms.

About

Python IRC bot

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 97.7%
  • HTML 2.3%