2026-02-27 19:46:09 +01:00
2026-02-27 19:42:08 +01:00
2026-02-27 19:42:08 +01:00
2026-02-27 19:41:24 +01:00
2026-02-27 19:42:08 +01:00
2026-02-27 19:46:09 +01:00
2026-02-27 19:42:08 +01:00

Resilience — Limnoria plugin

Automatic IRC self-maintenance for Limnoria bots.

  • Retries joining channels after ban, full, invite-only, or bad-key errors
  • Sends a configurable command (e.g. ChanServ UNBAN) before each retry
  • Removes own bans via MODE -b when the bot retains ops
  • Rejoins after kicks, with a configurable attempt limit
  • Recovers ops after being deopped (halfop self-op or a configurable command)
  • Sends a command on every successful join (e.g. ChanServ UP on Anope)
  • Runs a per-network perform list on connect (like ZNC *perform)
  • Reclaims the configured nick when on a fallback nick, including a DALnet-style RECOVER/RELEASE sequence

Installation

cp -r Resilience/ /path/to/your/bot/plugins/

Then in your bot:

load Resilience

Configuration hierarchy

All channel-level settings support Limnoria's three-level inheritance:

global default
  └─ per-network override
       └─ per-channel override

Set a network-wide default:

config network libera supybot.plugins.Resilience.<setting> <value>

Override for a specific channel:

config channel #chan supybot.plugins.Resilience.<setting> <value>

Reset a per-channel override back to the network default:

config channel #chan supybot.plugins.Resilience.<setting>

Network-only settings (nick password, perform, nick recovery) use config network only — they don't apply per-channel.


Command templates

Several settings accept a raw IRC command string with $-substitutions:

Variable Value
$nick The bot's configured (desired) nick for this network
$botnick Same as $nick
$currentnick The nick the bot is actually using right now
$network The network name
$password The value of nickPassword for this network
$channel The channel name (where applicable)

The command must be a valid raw IRC string, including a colon before any trailing parameter:

PRIVMSG ChanServ :UNBAN $channel
PRIVMSG ChanServ :OP $channel $botnick
MODE $nick +ix

For the perform and nickRecoverCommands settings, multiple commands are separated by commas. To include a literal comma inside a single command, escape it as \,.


Settings reference

Join retry

These are channel-level settings.


retryJoin

Type: Boolean
Default: True
Scope: channel

Master switch for all join-retry behaviour. When False, the bot gives up immediately on any join error and ignores all other retry settings for that channel.

config network libera supybot.plugins.Resilience.retryJoin True
config channel #readonly supybot.plugins.Resilience.retryJoin False

retryJoinDelay

Type: positive integer (seconds)
Default: 60
Scope: channel

How long to wait between join retry attempts. Applies to all join error types (ban, full, invite-only, bad key).

config network libera supybot.plugins.Resilience.retryJoinDelay 60

retryJoinMax

Type: non-negative integer
Default: 10
Scope: channel

Maximum number of join attempts before giving up entirely. The counter resets to zero on a successful join, so a temporary ban that later gets lifted will start fresh next time.

Set to 0 for unlimited retries (the bot will keep trying forever — use with care, as this can get the bot K-Lined on some networks).

config network libera supybot.plugins.Resilience.retryJoinMax 10
config channel #important supybot.plugins.Resilience.retryJoinMax 0

onBanCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent before each retry attempt when the bot is banned from a channel (numeric 474). Empty by default — set it to opt in.

The command is sent on every cycle, so if services successfully lift the ban the next retry join will succeed.

# Anope / Atheme ChanServ:
config network libera supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel

# IRCop bot or custom service:
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG OpBot :unban $channel $botnick

onFullCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent before each retry when the channel is full (numeric 471). Usually left empty — there is little you can do except wait for a slot to open. Could be used to ask an operator bot to raise the limit.

config network mynet supybot.plugins.Resilience.onFullCommand PRIVMSG OpBot :limit $channel

onInviteOnlyCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent before each retry when the channel is invite-only (numeric 473).

config network libera supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel

onBadKeyCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent before each retry when the channel key is wrong (numeric 475). Usually left empty unless you have a way to retrieve the current key from a service.


selfUnban

Type: Boolean
Default: True
Scope: channel

When True, if the bot has +o in the channel at the time it is banned, it will issue MODE #channel -b <mask> to remove its own matching ban entries before retrying the join.

This is a fallback for the unusual case where someone sets a ban on the bot without kicking it first, leaving it opped. It works alongside onBanCommand — both can be active at the same time.

config channel #chan supybot.plugins.Resilience.selfUnban True

Kick rejoin

These are channel-level settings.


rejoinOnKick

Type: Boolean
Default: True
Scope: channel

Whether the bot automatically rejoins after being kicked.

config channel #staff supybot.plugins.Resilience.rejoinOnKick False

rejoinKickDelay

Type: positive integer (seconds)
Default: 5
Scope: channel

Seconds to wait before rejoining after a kick. A small delay looks less aggressive and is required by the rules of some channels.

config network libera supybot.plugins.Resilience.rejoinKickDelay 5

rejoinKickMax

Type: non-negative integer
Default: 3
Scope: channel

Maximum number of rejoin attempts after kicks before the bot stops trying. Repeated kicks from the same channel usually mean someone actively wants the bot out, not a mistake. The counter resets to zero on a successful join.

Set to 0 for unlimited.

config network libera supybot.plugins.Resilience.rejoinKickMax 3

Op recovery

These are channel-level settings.


autoReop

Type: Boolean
Default: True
Scope: channel

When True, the bot tries to recover ops after being deopped. It first checks whether it has +h (halfop) and uses that to self-op with no external command. If it doesn't have halfop, it falls back to onDeopCommand if that is set.

config channel #chan supybot.plugins.Resilience.autoReop True

autoReopDelay

Type: non-negative integer (seconds)
Default: 3
Scope: channel

Seconds to wait after being deopped before attempting to recover ops. A brief pause avoids triggering a mode war if the deop was intentional. Set to 0 to react immediately.

config network libera supybot.plugins.Resilience.autoReopDelay 3

onDeopCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent to recover ops after being deopped, if the bot does not have halfop. Empty by default — set to opt in.

# Anope:
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick

# Atheme:
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel

onJoinCommand

Type: string (command template)
Default: (empty)
Scope: channel

Raw IRC command sent every time the bot successfully joins a channel. Empty by default — set to opt in.

The Anope UP command requests all access flags (op, halfop, voice) the bot has registered, making it a convenient single setting that covers both initial join and rejoin after kick.

# Anope UP — requests whatever access the bot has registered:
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel

# Atheme OP:
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel

Nick recovery

These are network-level settings.


recoverNick

Type: Boolean
Default: True
Scope: network

When True and the bot is not using its configured nick (e.g. it connected as MyBot_ because MyBot was taken), the bot will:

  1. Watch for the desired nick to become free via QUIT or NICK events and claim it immediately.
  2. Run a periodic poll every recoverNickDelay seconds and attempt NICK <desired> if the nick appears free.
config network libera supybot.plugins.Resilience.recoverNick True

recoverNickDelay

Type: non-negative integer (seconds)
Default: 30
Scope: network

Two roles:

  1. Polling interval — seconds between periodic attempts to reclaim the configured nick while the bot is on a fallback nick.
  2. Recovery gap — seconds the bot waits between sending nickRecoverCommands and issuing the actual NICK <desired> command. This gives services (e.g. DALnet RECOVER/RELEASE) time to process before the bot tries to take the nick.

Set to 0 to disable polling entirely (the bot will only react to QUIT/ NICK events) and to skip the gap before NICK.

config network libera supybot.plugins.Resilience.recoverNickDelay 30
config network dalnet supybot.plugins.Resilience.recoverNickDelay 5

nickRecoverCommands

Type: string (comma-separated command templates)
Default: (empty)
Scope: network

Comma-separated raw IRC commands sent when the bot is not using its configured nick. Fired automatically on connect (after MOTD) and when claimnick is run manually. After sending them, the bot waits recoverNickDelay seconds then issues NICK <desired>.

Leave empty on networks where the bot can simply re-use the nick without any service interaction (Libera, OFTC, etc.). Set it for networks that require an explicit recovery sequence.

# DALnet — RECOVER frees the nick, RELEASE makes it immediately available:
config network dalnet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password

# Anope GHOST (alternative to RECOVER on some networks):
config network mynet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :GHOST $nick $password

Available substitutions: $nick (desired nick), $botnick (same), $currentnick (what the bot is actually using now), $network, $password.


nickPassword

Type: string
Default: (empty)
Scope: network
Private: yes (stored in the private config file, not logged)

The password for the bot's registered nick on this network. Used as $password in perform, nickRecoverCommands, and all on*Command templates.

Set this with the nickpassword command, not by editing the config file directly, so that the value is written to the private config file:

nickpassword libera mySecretPassword

Perform on connect

These are network-level settings.


perform

Type: string (comma-separated command templates)
Default: (empty)
Scope: network

Comma-separated raw IRC commands sent after the bot receives the end of MOTD (numerics 376 / 422). The equivalent of ZNC's *perform module.

Commands are separated by commas. To include a literal comma inside a single command, escape it as \,.

Available substitutions: $nick, $botnick, $currentnick, $network, $password.

# Identify to NickServ and set user modes:
config network libera supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix

# Multiple steps for a network that needs them:
config network dalnet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $nick $password, PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password

# Operator auth:
config network mynet supybot.plugins.Resilience.perform OPER mylogin $password

Manage the perform list interactively with the perform subcommands (see Commands below).


performDelay

Type: non-negative integer (seconds)
Default: 2
Scope: network

Seconds to wait after receiving MOTD before sending perform commands. A brief delay lets server-side authentication (SASL, etc.) complete before NickServ or other commands are sent on top. Set to 0 to send immediately.

config network libera supybot.plugins.Resilience.performDelay 2

Commands

All commands require the admin capability.


perform set <network> <cmd1>, <cmd2>, ...

Set the perform list for <network>. Replaces the entire existing list. Separate commands with commas; use \, for a literal comma inside a command.

perform set libera PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix

perform show <network>

Display the current perform list for <network>, numbered.


perform run <network>

Immediately send the perform list for <network> as if the bot had just connected, without actually reconnecting. Useful for testing.


perform clear <network>

Clear the entire perform list for <network>.


nickrecover set <network> <cmd1>, <cmd2>, ...

Set the nick-recovery command list for <network>. Same comma-separated format as perform.

nickrecover set dalnet PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password

nickrecover show <network>

Display the current nick-recovery command list for <network>.


nickrecover run <network>

Immediately run the nick-recovery commands for <network> and schedule a NICK <desired> after recoverNickDelay seconds.


nickrecover clear <network>

Clear the nick-recovery command list for <network>.


nickpassword <network> <password>

Set (or update) the nick password for <network>. The password is stored in the private config file and never logged. Use this instead of setting nickPassword via the config command.

nickpassword libera mySecretPassword

retrylist

Show all channels currently waiting for a join retry, across all networks.


retrycancel [<channel>]

Cancel the pending join retry for <channel> on the current network and clear the attempt counter. <channel> defaults to the current channel.


retrynow [<channel>]

Cancel any scheduled retry and immediately attempt to join <channel> on the current network. Clears the attempt counter so the full retryJoinMax budget is available again. <channel> defaults to the current channel.


claimnick

Immediately run nickRecoverCommands for the current network and schedule a NICK <desired> after recoverNickDelay seconds. Also starts the periodic polling loop if recoverNick is enabled.


reop [<channel>]

Manually trigger op recovery in <channel>: tries halfop self-op first, then falls back to onDeopCommand if configured. <channel> defaults to the current channel.


selfunban [<channel>]

Manually attempt to remove the bot's own ban masks from <channel> via MODE -b. Requires the bot to currently have +o in the channel. <channel> defaults to the current channel.


Recipes

Libera.Chat (Atheme, SASL handled by Limnoria)

SASL is configured at the Limnoria level (supybot.networks.libera.sasl.*), so perform only needs post-auth steps.

config network libera supybot.plugins.Resilience.perform MODE $nick +ix
config network libera supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network libera supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network libera supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel
config network libera supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel

OFTC (custom NickServ, no SASL)

OFTC runs its own services and does not support SASL. Identification is done via PRIVMSG NickServ after connect.

nickpassword oftc mypassword
config network oftc supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
config network oftc supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network oftc supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network oftc supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel
config network oftc supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel

Anope-based network (SASL handled by Limnoria)

Most Anope 2.x networks support SASL. Configure it at the Limnoria level; perform only needs post-auth steps. The UP command requests all access flags the bot has registered (op, halfop, voice) in one shot.

config network mynet supybot.plugins.Resilience.perform MODE $nick +ix
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network mynet supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network mynet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel
config network mynet supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick

Anope-based network (no SASL / password auth only)

nickpassword mynet mypassword
config network mynet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $password, MODE $nick +ix
config network mynet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network mynet supybot.plugins.Resilience.onInviteOnlyCommand PRIVMSG ChanServ :INVITE $channel
config network mynet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :UP $channel
config network mynet supybot.plugins.Resilience.onDeopCommand PRIVMSG ChanServ :OP $channel $botnick

DALnet (Dreamforge / old Anope, RECOVER+RELEASE nick flow)

nickpassword dalnet mypassword
config network dalnet supybot.plugins.Resilience.perform PRIVMSG NickServ :IDENTIFY $nick $password
config network dalnet supybot.plugins.Resilience.nickRecoverCommands PRIVMSG NickServ :RECOVER $nick $password, PRIVMSG NickServ :RELEASE $nick $password
config network dalnet supybot.plugins.Resilience.recoverNickDelay 5
config network dalnet supybot.plugins.Resilience.onBanCommand PRIVMSG ChanServ :UNBAN $channel
config network dalnet supybot.plugins.Resilience.onJoinCommand PRIVMSG ChanServ :OP $channel $botnick

Disable all retry on a specific channel

config channel #sensitive supybot.plugins.Resilience.retryJoin False
config channel #sensitive supybot.plugins.Resilience.rejoinOnKick False
config channel #sensitive supybot.plugins.Resilience.autoReop False

Aggressive retry on an important channel

config channel #important supybot.plugins.Resilience.retryJoinMax 0
config channel #important supybot.plugins.Resilience.retryJoinDelay 30
config channel #important supybot.plugins.Resilience.rejoinKickMax 0

Full settings summary

Setting Scope Type Default Description
retryJoin channel Boolean True Master switch for join retrying
retryJoinDelay channel integer (s) 60 Seconds between retry attempts
retryJoinMax channel integer 10 Max attempts; 0 = unlimited
onBanCommand channel template (empty) Command sent before each ban retry
onFullCommand channel template (empty) Command sent before each full-channel retry
onInviteOnlyCommand channel template (empty) Command sent before each invite-only retry
onBadKeyCommand channel template (empty) Command sent before each bad-key retry
selfUnban channel Boolean True Remove own bans via MODE -b when opped
rejoinOnKick channel Boolean True Auto-rejoin after kick
rejoinKickDelay channel integer (s) 5 Seconds before rejoining after kick
rejoinKickMax channel integer 3 Max kick-rejoin attempts; 0 = unlimited
autoReop channel Boolean True Recover ops after deop
autoReopDelay channel integer (s) 3 Seconds before attempting reop
onDeopCommand channel template (empty) Command sent to recover ops
onJoinCommand channel template (empty) Command sent on every successful join
recoverNick network Boolean True Reclaim configured nick when free
recoverNickDelay network integer (s) 30 Poll interval and RECOVER→NICK gap
nickRecoverCommands network templates (empty) Commands sent when on a fallback nick
nickPassword network string (empty) Nick password (private); use nickpassword cmd
perform network templates (empty) Commands sent after MOTD
performDelay network integer (s) 2 Seconds before sending perform
S
Description
No description provided
Readme MIT 54 KiB
Languages
Python 100%