Skip to content

Administrator Manual

This manual covers everything needed to install, configure, and operate Inquisitor on your own Limnoria instance. It is intended for bot administrators responsible for the full lifecycle of the plugin.


Installation

Prerequisites

  • Python 3.10+
  • MariaDB 10.5+
  • Limnoria (Supybot fork): pip install limnoria

Install Dependencies

pip install limnoria mariadb rapidfuzz inflect

Clone the Repository

Clone Inquisitor into Limnoria's plugins directory:

cd ~/.local/share/supybot/plugins/
git clone https://git.ptp-trivia.me/Inquisitor/Inquisitor Inquisitor

Load the Plugin

Start your bot and load the plugin:

@load Inquisitor

Database Setup

Create the Database

Run this SQL as a privileged MariaDB user:

CREATE DATABASE inquisitor CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'inquisitor'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON inquisitor.* TO 'inquisitor'@'localhost';
FLUSH PRIVILEGES;

Configure the Database Connection

Set database credentials via Limnoria's config system (in IRC or in supybot.conf):

@config plugins.Inquisitor.database.host localhost
@config plugins.Inquisitor.database.port 3306
@config plugins.Inquisitor.database.user inquisitor
@config plugins.Inquisitor.database.password your_password
@config plugins.Inquisitor.database.database inquisitor

Note

These are the only settings stored in Limnoria's config. All game and network settings live in the database itself, managed via @config.

Automatic Migrations

When Inquisitor first connects to the database, the migration runner executes all pending schema migrations automatically. No manual database setup is needed beyond creating the database and user.

Database Integrity Check

On every plugin load, Inquisitor runs a database integrity check. If the check fails, the plugin raises a RuntimeError and blocks all functionality until the database is repaired. Check Limnoria's log file for details.


Global Setup Wizard

First-Run Setup

After loading the plugin and configuring the database connection, run the global setup command once from your master network:

@setup <network#channel> <note>

Example:

@setup libera#trivia-admin Initial setup by rb14060

This command:

  1. Marks the database as verified (database_verified = true)
  2. Sets libera#trivia-admin as the master log channel (receives all events from all networks)
  3. Makes the caller the first admin with global ADMIN permission across all networks

Warning

The @setup command is protected by a one-time state guard. It can only be run once. If you need to change the master log channel after setup, use @setmasterlog.

Activate Your Network

After global setup, activate each network for trivia:

@activate <question_set_name> [#log-channel]

Example:

@activate General #trivia-log

This command:

  • Links the current network to the specified question set
  • Optionally sets a per-network log channel (receives events from this network only)
  • Creates the network's settings record with global defaults
  • Sets network state to active

Note

@activate is a first-run-only command. After a network has been activated and then deactivated, use @reactivate instead.

Network States

Inquisitor tracks three network states:

State Description
never_activated Network has never been set up
active Network is live and operational
deactivated Network was soft-deactivated (data preserved)

Network Management

Commands

Command Permission Description
@activate <set> [#chan] None (first-run guard) Activate network for trivia
@deactivate [--hard] ADMIN Soft-deactivate this network
@reactivate ADMIN Re-enable a deactivated network
@netstatus [network] ADMIN View network configuration and state
@listnetworks ADMIN List all networks with their status
@setquestionset <name> MODERATE Switch the active question set

Deactivation

@deactivate

Sets the network's setup.archived = true. This is a soft-deactivation: - All data (questions, scores, settings) is preserved - Trivia cannot be started on the network while deactivated - Reactivate at any time with @reactivate

@deactivate --hard

Warning

Hard deactivation removes the network's settings and activation record. This is not reversible without running @activate again and importing data. Use only when permanently retiring a network.

Reactivation

@reactivate

Brings a soft-deactivated network back to active state. If the question set that was active before deactivation has been deleted, Inquisitor falls back to the global default question set with a warning.

Question Set Switching

@setquestionset <question_set_name>

Switches the current network's active question set. This command:

  • Checks that no games are currently running on any channel of this network
  • Announces the change to the current channel
  • Takes effect for the next game started

Note

@setquestionset requires MODERATE permission, not ADMIN, so channel moderators can manage question sets without full admin access.


Permission Management

Overview

Inquisitor uses an internal permission system independent of IRC server modes. Permissions are:

  • Per-network: ADMIN on Network A does not grant anything on Network B
  • Hierarchical: Higher levels inherit all capabilities of lower levels
  • Pattern-based: Can be granted to specific users or wildcard hostmask patterns

Permission Levels

ADMIN
  └─ MANAGE
      └─ MODERATE
          └─ START
              └─ PLAY
Level Grants
PLAY Answer questions, use @recall, view stats
START Everything in PLAY + start/stop games (@trivia)
MODERATE Everything in START + manage settings, switch question sets, manage pools
MANAGE Everything in MODERATE + add/edit/delete questions and question sets
ADMIN Everything in MANAGE + manage permissions, manage networks, use admin settings

Commands

Command Permission Description
@grant <nick> <level> ADMIN Grant an explicit permission level
@revoke <nick> <level> ADMIN Remove an explicit grant or denial
@deny <nick> <level> ADMIN Block a user's default permissions
@undeny <nick> <level> ADMIN Remove a denial (same as @revoke for denials)
@permissions <nick> ADMIN View a user's effective permissions
@listperms [network] ADMIN List all grants/denials on the network (sent via private notice for long lists)
@mypermissions None View your own effective permissions

Granting Permissions

@grant alice MODERATE

If alice is currently in the channel, her hostmask is resolved to *!*@<host> automatically. If she is not in the channel, the grant uses alice!*@*.

You can also grant directly to a hostmask pattern:

@grant *!*@staff.example.com ADMIN

Grants are network-wide — they apply to all channels on the current network.

Revoking and Denying

@revoke alice MODERATE      # Remove explicit grant
@deny spammer PLAY          # Block default PLAY from a specific user
@revoke spammer PLAY        # Remove the denial (restores access via defaults)

Note

@revoke removes both grants and denials. To restore a user who has a denial, use @revoke (not @deny).

Permission Check Order

  1. Limnoria owner → Automatic ADMIN on all networks
  2. Explicit grants (checked first — grants beat denials)
  3. Permission hierarchy from grants
  4. Explicit denials (only block if no grant exists)
  5. Default permissions (see Default Permissions)
  6. Channel mode-based permissions (if permissions.modeMapEnabled=true)
  7. Final deny

Default Permissions

Default permissions determine what new users can do without any explicit grants:

Setting Default Description
permissions.defaultPlay true All users can play by default
permissions.defaultStart true All users can start/stop games by default
permissions.defaultModerate false Moderation requires explicit grant
permissions.defaultManage false Question management requires explicit grant
permissions.defaultAdmin false Admin access requires explicit grant

Change defaults for your network:

@config set permissions.defaultStart=false   # Require explicit grant to start games
@config set permissions.defaultPlay=false    # Require explicit grant to play (private network)

Warning

All permissions.default* settings are admin-only. Changing permissions.defaultModerate=true grants PLAY and START to all users via hierarchy.

Channel Mode-Based Permissions (Optional)

When permissions.modeMapEnabled=true, Inquisitor grants permissions based on IRC channel modes:

IRC Mode Default Permission
+v (voice) PLAY
+h (halfop) START
+o (op) MODERATE
+a (admin) MANAGE
+q (owner) ADMIN
@config set permissions.modeMapEnabled=true
@config set permissions.modeMapOp=ADMIN   # Make ops full admins instead

This feature is disabled by default for backwards compatibility. Explicit grants always take precedence over mode-based permissions.


Configuration Reference

Settings are stored in the database and can be scoped per-channel, per-network, or globally.

Hierarchy

Channel override > Network override > Global default > Code default

Use @config list to see all current settings with scope indicators: - [C] = Channel-specific - [N] = Network-level - [G] = Global default

Commands

Command Permission Description
@config get <key> PLAY Show setting value and metadata
@config list [namespace] PLAY List all settings
@config set <key=value> MODERATE (ADMIN for admin-only settings) Change a setting
@config reset <key> MODERATE Reset setting to default
@config diff [target] MODERATE Compare settings with another context
@config export [scope] MODERATE Export settings as JSON
@config import <json> MODERATE Import settings from JSON
@config set-default <key> <value> ADMIN Change global default for a setting
@config show-overrides <key> ADMIN List all networks/channels overriding a setting
@config help [subcommand] PLAY Show help for config commands

game.* Namespace

Setting Type Default Range Admin Only Description
game.enabled bool true No Enable/disable trivia in this channel
game.hintDelay int 15 5–300 No Seconds between standard question hints
game.questionTimeout int 60 10–600 No Seconds before question times out
game.maxHints int 3 0–10 No Maximum hints per question
game.delayBetweenQuestions int 5 1–60 No Seconds between questions

Note

game.hintDelay must be less than game.questionTimeout. The validator enforces this cross-setting constraint.

scoring.* Namespace

Setting Type Default Range Admin Only Description
scoring.minPoints int 10 1–1000 No Minimum base points for standard/scramble questions
scoring.maxPoints int 50 1–1000 No Maximum base points for standard/scramble questions
scoring.lengthBonus bool true No Award bonus for longer answers
scoring.speedBonus bool true No Award bonus for fast answers
scoring.timezone str UTC Yes IANA timezone for period boundaries (e.g., America/New_York)
scoring.periodOffset int 0 0–23 Yes Hours after midnight when daily/weekly/monthly periods reset
scoring.basePoints int 10 1–1000 No Legacy setting (superseded by min/max range)

Note

scoring.timezone uses IANA timezone names validated by Python's zoneinfo module. Examples: America/New_York, Europe/London, Asia/Tokyo, UTC. Changing this setting affects when daily/weekly/monthly periods roll over.

kaos.* Namespace

Setting Type Default Range Admin Only Description
kaos.frequency int 10 -1–100 No KAOS frequency: -1=KAOS-only, 0=disabled, N=1 per N standard questions
kaos.hintDelay int 20 5–300 No Seconds between KAOS hints
kaos.autoRecall int 3 0–20 No Auto-recall after N KAOS answers (0=disabled)
kaos.minPoints int 10 1–1000 No Minimum base points per KAOS answer
kaos.maxPoints int 50 1–1000 No Maximum base points per KAOS answer
kaos.bonusLow int 50 0–10000 Yes Minimum KAOS completion bonus (BogusTrivia: kbonlo)
kaos.bonusHigh int 200 0–10000 Yes Maximum KAOS completion bonus (BogusTrivia: kbonhi)
kaos.bonusMinAnswers int 0 0–100 Yes Minimum answers required to award bonus (0 = always, if multi-player)

scramble.* Namespace

Setting Type Default Range Admin Only Description
scramble.hintDelay int 15 5–300 No Seconds between unscramble hints
scramble.questionTimeout int 60 10–600 No Seconds before scramble question times out

bonus.* Namespace

Setting Type Default Range Admin Only Description
bonus.enabled bool true No Enable streak bonus multiplier
bonus.interval int 10 1–100 No Questions between bonus multipliers
bonus.multiplier float 2.0 1.0–10.0 No Bonus point multiplier

autovoice.* Namespace (Admin Only)

Setting Type Default Range Admin Only Description
autovoice.enabled bool false Yes Auto-voice top players in channel
autovoice.topN int 3 1–25 Yes Number of top players to auto-voice
autovoice.period str weekly Yes Period for auto-voice calculation

flood.* Namespace (Admin Only)

Setting Type Default Range Admin Only Description
flood.maxPerSecond float 2.0 0.1–10.0 Yes Max messages per second before flood protection
flood.muteSeconds int 30 5–600 Yes Mute duration when flood protection triggers

permissions.* Namespace (Admin Only)

Setting Type Default Admin Only Description
permissions.defaultPlay bool true Yes Default PLAY permission for users without grants
permissions.defaultStart bool true Yes Default START permission for users without grants
permissions.defaultModerate bool false Yes Default MODERATE permission for users without grants
permissions.defaultManage bool false Yes Default MANAGE permission for users without grants
permissions.defaultAdmin bool false Yes Default ADMIN permission for users without grants
permissions.modeMapEnabled bool false Yes Enable automatic permissions based on IRC channel modes
permissions.modeMapVoice str PLAY Yes Permission level for voiced users (+v); NONE to disable
permissions.modeMapHalfop str START Yes Permission level for half-ops (+h); NONE to disable
permissions.modeMapOp str MODERATE Yes Permission level for ops (+o); NONE to disable
permissions.modeMapAdmin str MANAGE Yes Permission level for admins (+a); NONE to disable
permissions.modeMapOwner str ADMIN Yes Permission level for owners (+q); NONE to disable

debug.* Namespace (Admin Only)

Setting Type Default Admin Only Description
debug.verbose bool false Yes Enable verbose debug logging to file. Global scope only — use @config set-default debug.verbose=true

Logging Configuration

Log Channels

Inquisitor supports two log channels:

  • Master log channel (set during @setup): Receives all events from all networks. Lives on the bot's home network.
  • Per-network log channel (set via @activate or @setnetlog): Receives events from that network only.

Change log channels at runtime:

@setmasterlog libera#trivia-admin         # Change master log channel
@setnetlog #network-specific-log          # Set per-network log channel
@setnetlog none                           # Disable per-network log channel

Both commands require ADMIN permission.

What Is Logged

Event Master Log Network Log
Global setup Yes
Network activate/deactivate/reactivate Yes Yes
Question set change Yes Yes
Game start/stop Yes Yes
Config changes (with old → new values) Yes Yes
Permission grant/revoke Yes Yes

Log Format

[NetworkName] Event description

Messages are color-coded by severity: - Green: Informational (game start, question set change) - Yellow: Warnings (pool exhaustion, fallback activation) - Red: Errors and destructive actions (deactivation, hard deactivate)

File Logging

All events are written to Limnoria's log directory at DEBUG level. Enable verbose debug output for operational traces:

@config set-default debug.verbose=true

Note

debug.verbose is a global-scope setting. Use @config set-default rather than @config set. Security-related log calls ([SECURITY]) and error events are always logged regardless of this setting.


Security Overview

SQL Injection Protection

All database queries use parameterized statements with %s placeholders (MariaDB connector syntax). No user input is ever interpolated directly into SQL strings.

An automated regression test (SQLInjectionRegressionTest) scans all Python source files for f-string SQL construction patterns on every test run. Pre-existing safe dynamic patterns (table constants, whitelisted column sets) are annotated with # noqa: SQL-FSTRING.

Files audited: db/connection.py, core/settings_manager.py, core/permissions.py, core/question_manager.py, importers/engine.py, core/game_manager.py, core/score_tracker.py, core/stats_module.py.

Input Validation

All validation logic lives in utils/validators.py.

Input Validation On Failure
Question text Strip IRC controls; length 1–400 chars Error message to user
Answer text Strip IRC controls; length 1–200 chars Error message to user
Alternative answers Each validated individually Error message to user
Hostmask patterns Non-empty; no SQL metacharacters (', ;, --, /*, */) Suspicious: silent drop + [SECURITY] log entry; empty: error to user
Channel names Starts with #, &, +, or !; no invalid IRC chars; 2–50 chars Error message to user
Import file paths Resolved with realpath; must be within allowed data directory Error message to user
Imported question/answer text IRC controls stripped before INSERT Silent strip

IRC control characters stripped: \x02 (bold), \x03 (color), \x0f (reset), \x16 (reverse), \x1d (italic), \x1e (strikethrough), \x1f (underline).

Rate Limiting

Command Cooldown
Permission denials (all commands) 60 seconds per user per command
@question search 8 seconds
@stats 10 seconds
@importpreview, @importpointspreview 60 seconds

Rate-limited requests are silently dropped and logged with a [RATE LIMIT] prefix.

Note

@importconfirm and @importpointsconfirm are intentionally not rate-limited. The preview commands gate the expensive database operations.

File Path Traversal Prevention

Import commands validate file paths:

  1. Path is resolved to an absolute real path via os.path.realpath()
  2. Resolved path must share a common prefix with Limnoria's data directory
  3. Paths containing .. or symlinks pointing outside the data directory are rejected

Known Limitations

  • IRC command injection: Not addressed at plugin level. Limnoria handles command dispatch. Plugin output does not trigger secondary command execution.
  • Nick-based lookups: @stats <nick> looks up by nick in the database. Impersonation via nick change is possible only if the target has never played.
  • File encoding: Import uses chardet/charset_normalizer for encoding detection. Malformed non-UTF-8 files may produce garbled text but will not cause code execution.

See SECURITY.md at the repository root for the complete technical security audit.


Data Import

BogusTrivia Question Import

Via IRC:

@importpreview <file_path> <question_set_name> [default_category]

This starts the preview-confirm workflow:

  1. @importpreview /path/to/questions.ques General — preview what would be imported
  2. @importconfirm /path/to/questions.ques General — commit the import (within 5 minutes)

Via CLI tool (recommended for large imports):

python tools/import_questions.py /path/to/questions.ques \
  --config /path/to/supybot.conf \
  --question-set "General" \
  --dry-run

CLI tool flags:

Flag Description
--config <path> Read database credentials from supybot.conf
--question-set <name> Override destination question set name (single-file imports)
--dry-run Preview import without writing to database
--default-category <name> Override fallback category for uncategorized questions

The CLI tool supports wildcards and multiple file arguments:

python tools/import_questions.py /data/*.ques
python tools/import_questions.py general.ques sports.ques movies.ques

Player Point Import

@importpointspreview <file_path>

Imports player scores from a BogusTrivia player data file. Points are accumulated — existing scores are added to, not replaced.

Follow with @importpointsconfirm <file_path> to commit.

Accepted delimiters: space, colon, or equals sign.

Import Safety

  • All-or-nothing transactions: Any error during import causes a full rollback — your database is never left in a partial state.
  • Checkpoint every 500 questions: Large imports can resume from the last checkpoint if interrupted.
  • Duplicate detection: Questions are checked against existing content using fuzzy matching at 85% threshold (token_set_ratio). Add --force in CLI or use @question add --force in IRC to bypass.
  • Quality checks: Questions with text under 10 chars, empty answers, question equal to answer, or more than 20 alternatives are flagged.

Question Management

Adding Questions via IRC

@question add "Question text" "Answer" --set "Set Name" [--category "Cat"] [--type standard|kaos|scramble] [--alternatives "alt1,alt2"] [--force]

Types: standard (default), kaos, scramble

Requires MANAGE permission.

Question Sets

@questionset add <name>          # Create a new question set (MANAGE)
@questionset rename <old> <new>  # Rename a question set (MANAGE)
@questionset list                # List all sets (no permission required)
@questionset stats <name>        # Show statistics for a set (no permission required)
@questionset delete <name>       # Delete a set (ADMIN, requires @confirm)

Warning

@questionset delete cascades to all questions in the set and all associated asked-question tracking history. This is not reversible.

Bulk Operations

@question bulkdelete --set "SetName" [--category "Cat"]    # Delete all questions in a set/category (ADMIN)
@question bulkmove --from-set "Source" --to-set "Target"   # Move all questions to another set (ADMIN)

Both require @confirm within 5 minutes of preview.

Warning

Bulk operations are destructive and require ADMIN permission. Always preview before confirming.


Statistics and Maintenance

Cross-Network Statistics

@crossstats <nick_or_hostmask>

Shows a user's scores across all networks. Requires ADMIN permission.

Database Commands (Bot Owner Only)

@dbstatus     # Show database connection status and pool metrics
@dbconnect    # Reconnect to database with current Limnoria config

These commands require Limnoria bot owner capability (not Inquisitor ADMIN), because they expose database credentials.

Question Pool Status

@poolstatus    # Show asked/total for each pool type (no permission required)
@poolreset     # Reset pool history for current question set (MODERATE)

The pool tracks which questions have been asked to prevent repeats. Pools reset automatically when exhausted (silently reshuffled). Use @poolreset to force a reset before exhaustion.

Viewing Network Status

@netstatus [network]   # Show current network config, timezone, question set, log channel
@listnetworks          # List all networks with their activation state and question set

Troubleshooting

Plugin Won't Load

Check Limnoria's log for a RuntimeError from the database integrity check. Common causes:

  • Database credentials not configured (run @config plugins.Inquisitor.database.*)
  • MariaDB server not running or unreachable
  • MariaDB user lacks sufficient privileges

"Permission denied" errors

Check the user's effective permissions:

@permissions <nick>
@config list permissions

Common causes: hostmask changed on reconnect; permission was granted on a different network; explicit denial blocking default access.

KAOS questions not appearing

Check the kaos.frequency setting:

@config get kaos.frequency
  • 0 = KAOS disabled
  • Positive integer N = 1 KAOS per N standard questions (default: 10)
  • -1 = KAOS-only mode

If the question set has no KAOS-type questions and kaos.frequency=-1, Inquisitor falls back to standard questions with a warning.

Period scores look wrong

Verify the timezone setting:

@config get scoring.timezone

Period boundaries (daily/weekly/monthly) are calculated using the configured IANA timezone. A network in UTC will reset at midnight UTC. Use @config set scoring.timezone=America/New_York (ADMIN) to use a different timezone.


Best Practices

Tip

Run the @setup command from your most reliable network. The master log channel should be on a network the bot is connected to continuously.

Tip

Use explicit ADMIN grants sparingly. Prefer granting MODERATE for game management and MANAGE for question set maintenance. Reserve ADMIN for trusted operators who need to manage networks and permissions.

Tip

Before running destructive operations (bulk delete, question set delete, deactivation), export your configuration with @config export channel and document the question count with @questionset stats <name>.

Tip

For large BogusTrivia migrations (thousands of questions), use the CLI tool tools/import_questions.py with --dry-run first to validate the import, then run without --dry-run. The CLI tool avoids IRC message length limits and timeouts that can interrupt large imports.