Sopel

The Python IRC Bot

Version 8.0.0

Highlights

Detailed coverage of the major changes can be found in the dedicated Sopel 8 upgrade guide.

For users:

  • Python 3.8+ is now required
  • IRC connections are made with TLS on port 6697 if not configured
  • SASL EXTERNAL authentication is now supported
  • Plugins in ~/.sopel/modules are no longer loaded by default
    • Use the core.extra setting to add this directory back if needed
  • Database options can be configured all at once in new db_url setting (useful for managed cloud hosting such as Heroku)
  • .blocks command accepts “nick” and “host” types now, and no longer lies about supporting “hostmasks” (further improvements to come)
  • Sopel no longer supports loading (very!) old Phenny/Jenni plugins
  • Several built-in plugins have been converted to external packages, to simplify maintenance and release-management going forward

For developers:

  • Identifier was moved to sopel.tools.identifiers and now supports dynamic casemapping; an optional casemapping or identifier_factory kwarg has been added to many object types to help manage this at runtime
    • You can usually just pass bot.make_identifier as the factory function and things will Just Work™
    • Feel free to use bot.make_identifier() yourself to get an Identifier representing any nick or channel name you get, e.g. as input to a command
  • The core.nick setting value is now returned as a str, not an Identifier
  • Capability negotiation is a first-class plugin API feature (see plugin.capability and the related documentation chapter)
  • trigger.sender attribute as passed to plugin callables is now None in cases where its value is meaningless (events with no channel/query context)
  • Numerous deprecated API features were removed:
    • bot.privileges (use bot.channels)
    • bot.msg() (use bot.say())
    • sopel.web submodule (use sopel.tools.web)
  • Messages from other bots are ignored by default on supported networks, but plugins can opt back in with the @plugin.allow_bots decorator
  • STATUSMSG prefix is removed from trigger.sender if present and stored in a separate trigger.status_prefix attribute
    • See documentation for bot.SopelWrapper.default_destination
  • bot.connection_registered is now the way to check whether the bot is connected to IRC, registered with the network, and ready for your plugin to send commands

Plugin changes

  • admin:
    • Added .raw command to make Sopel send a raw IRC line [#2104]
  • adminchannel:
    • Refactored hostmask handling & added code comments/tests [#2222]
    • Improved result of trying to kick the bot with .kick [#2240]
    • Unified topic-mask management under .tmask with subcommands [#2601]
  • bugzilla:
  • calc:
    • Improved error handling [#2507]
    • Improved help output & test coverage [#2530]
  • clock:
    • Added unset commands for user & channel time zone/format [#2181]
  • coretasks:
    • Replaced “hostmask” type with “host” in .blocks command [#2344]
      • Real “hostmask” support is planned; see #1355
    • Improved help output for .blocks [#2345]
  • currency:
    • Added .currencies command to list known currency symbols [#2439]
    • fiat_provider setting takes precedence over the existence of a fixer_io_key value [#2330]
    • Switched from exchangerate.host to open.er-api.com [#2512]
  • dice:
    • Refactoring, bugfixes, and improved test coverage [#2532]
  • emoticons:
    • Added .tback alias for .unflip command [#2287]
    • Made .unflip table more closely match the .tflip one [#2287]
  • find:
    • Fixed use of flags when | is used as input separator [#2447]
    • Code cleanup [#2267]
  • help:
    • Now updatable independently from its own package, but still installs with Sopel as a dependency [#2332]
  • ip:
  • meetbot:
  • pronouns:
    • Accept abbreviated pronoun sets [#2070]
    • Added .clearpronouns command [#2154]
    • Fetch pronoun list dynamically at startup [#2130]
    • Support configurable pronoun backend [#2437, #2438]
  • py:
  • reddit:
  • reload:
    • Remove unsupported .update command [#2416]
    • Improve plugin help strings [#2417]
  • remind:
  • safety:
    • Comprehensive rework improving caching, output, etc. [#2279]
    • Ignore invalid hostnames [#2472]
  • search:
    • Fix .suggest when there is only one result [#2513]
    • Code style and dependency updates [#2260, #2334, #2420]
  • seen:
    • Adapted to aware trigger.time [#2265]
    • Prevent error spam if database is temporarily inaccessible [#2338]
  • tell:
    • Fixed edge cases in cleanup of tellee argument [#2584]
  • translate:
    • Improved help output [#2453]
  • unicode_info:
  • url:
    • Improved interaction between .title command and link handlers [#2282]
    • Removed traceback from debug log when URL fetch fails [#2280]
    • Made channel-privilege-based access to .urlexclude command and friends configurable [#2352]
    • Cleaned up code [#2304, #2307, #2433]
    • Added better error handling for DNS lookups [#2428]
    • Ignore invalid hostnames [#2472]
  • version:
    • Support retrieving plugin versions with .version pluginname [#2133]
  • wikipedia:
    • Commands are now .wp, .wikipedia; old commands (.w, .wik, & .wiki) were removed [#1966]
    • Remove deprecated lang_per_channel setting [#2142]
    • Work around excessive whitespace in math formulas [#2286]
    • Don’t ping the user who posted a URL that fails to load [#2315]
    • Output image description if URL has an image viewer fragment [#2388]
    • Handle query strings in article links [#2414]
    • Fail gracefully on Special: namespace links [#2575]
  • xkcd:

Core changes

  • Removed support for EOL Python versions (2.7, 3.3, 3.4, 3.5, 3.6, & 3.7), added testing on newer Python versions (up to 3.12), and modernized coding standards [#2062, #2073, #2123, #2124, #2134, #2136, #2138, #2205, #2213, #2227, #2298, #2326, #2327, #2342, #2384, #2464, #2500, #2516]
  • Modified default settings:
    • Removed default B mode from core.modes [#2448]
    • Added space before timezone in default_time_format [#2531]
  • IRC backend refactored to use asyncio [#2256]
    • Just in time, too: The asynchat module was removed in Python 3.12
  • Improved IRC connection error handling [#2430, #2431]
  • SopelDB adapted to SQLAlchemy 2.x style [#2243]
  • Database can be configured all at once with a new db_url setting [#2087]
  • Replaced pkg_resources with importlib.metadata [#2261, #2268]
  • Added support for several new IRC features and IRCv3 specifications:
    • SASL EXTERNAL client certificate authentication [#2100, #2561]
    • Sopel uses the Bot Mode specification to mark itself as a bot and track other users that are flagged as bots [#2088, #2448]
    • userhost-in-names capability and the legacy UHNAMES feature [#2102]
    • chghost capability [#2116]
  • Improved SASL handling when auth fails [#2187, #2191]
  • Added automatic CASEMAPPING handling based on RPL_ISUPPORT [#2231]
  • Plugins in ~/.sopel/modules are no longer loaded by default [#2119]
  • Removed support for Phenny/Jenni plugin style [#2126]
  • Ignore bot-tagged messages by default [#2089, #2272]
  • Privilege tracking (MODE event handling) refactored [#2131]
  • Maintain ordering of ISupport.PREFIX property [#2200]
  • Added __slots__ to Channel and User objects [#2233]
  • Improved handling of the bot’s nick getting changed [#2240]
  • Improved tracking and enforcement of rate limits [#2297]
  • Prevent infinite loop on connection failure [#2306]
  • Added ssl_ciphers and ssl_minimum_version settings [#2246, #2306]
  • Added antiloop_repeat_text, antiloop_silent_after, antiloop_threshold, and antiloop_window settings to control command-loop prevention [#2320]
  • Using , in ListAttribute values logs a warning [#2252]
    • Write lists with newlines instead; Sopel 9 will remove splitting on ,
  • IRC connections use TLS on port 6697 by default [#2277]
    • Potentially a breaking change, but the configuration wizard has long written these settings to the final config file even if they were left at the default values
  • Fixed UHNAMES race condition caused by joining channels too soon [#2321]
  • Removed hunting for CA root store [#2278, #2303]
    • Sopel will simply use the system TLS library’s default trust store unless the ca_certs setting is specified
  • Improved truncation/splitting of over-length messages in e.g. bot.say() [#2310, #2450]
  • Gracefully handle missing userhost-in-names data, e.g. with ZNC [#2312]
  • Override get_version() method for EntryPointPlugin [#2313]
  • Take advantage of LINELEN token if advertised in ISUPPORT [#2346]
  • Unescape ISUPPORT parameter values [#2429]
  • Keep track of user realnames via WHO/WHOX [#2383, #2396]
  • Raise error on receiving non-UTF-8 data if server advertises UTF8ONLY [#2365, #2369, #2372]
  • Make all arguments safe() when preparing IRC commands [#2368]
  • Ignore disabling coretasks handlers in per-channel settings [#2400]
  • Add guardrails to channel logging [#2419]
    • Default is now always WARNING, regardless of file logging level
    • DEBUG level is no longer available for channel logs; it is far too noisy
  • Handle core.modes setting being None [#2510]
  • Handle broken symlinks to plugin files [#2545]
  • Fixed auto-saving changes to Sopel’s ignore list with .blocks [#2550]

API changes

  • Importing sopel.module now emits a deprecation warning [#2170]
  • Removed support for Phenny/Jenni plugin style [#2126]
  • Stopped searching bot.memory['url_callbacks'] for link handlers [#2121]
  • Deprecated bot.search_url_callbacks() [#2121, #2156, #2581]
  • Deprecated db.execute() [#2243]
  • Cleaned up parts of sopel.tools namespace
    • Deprecated tools.web.entity() and its r_entity constant, to be removed in Sopel 9.0 [#2205]
    • Deprecated the iteritems, iterkeys, itervalues, and raw_input 2to3-style shims, to be removed in Sopel 8.1 [#2228]
    • Moved Identifier to its own submodule, tools.identifiers [#2231]
      • tools.Identifier remains for now as a compatibility shortcut
    • Moved check_pid() and stderr() functions to cli.utils [#2385]
    • Deprecated tools.OutputRedirect class left over from the days before modern logging, to be removed in Sopel 8.1 [#2385]
  • Moved channel privilege constants into their own sopel.privileges submodule [#2179, #2352, #2540]
    • The new sopel.privileges.AccessLevel type encapsulates all levels in a single object, which can support dynamic features in the future
    • The original, individual constants in sopel.plugin are still available for now, mapped to their corresponding AccessLevel values
  • Sopel’s API now uses enumerated types where suitable:
    • formatting.colors [#2122]
    • privileges.AccessLevel [#2540]
    • tools.events [#2127]
  • bot-tagged messages are ignored by default [#2089]
  • trigger.time is offset-aware [#2099]
  • tools.time improvements & changes:
    • format_time() accepts offset-aware datetime values [#2132]
    • seconds_to_split() returns a Duration named tuple [#2446]
    • validate_timezone() now raises ValueError for None [#2446]
  • Added plugin.allow_bots decorator [#2244]
  • Added more rate-limit controls [#2290, #2434]
    • New message keyword-only argument to plugin.rate decorator, an optional string sent via NOTICE when a command is rate-limited
    • New plugin.rate_user, plugin.rate_channel, and plugin.rate_global decorators provide simpler control over a single rate-limit type, with the rate and message (optional) as positional parameters
    • Messages support various placeholders that will be replaced with runtime data about the triggering user, the rate limit in effect, and the plugin/command that was limited; see plugin.rate decorator documentation for details
  • Added bot.safe_text_length() method [#2136]
  • Added RPL_WHOISBOT to tools.events list [#2145]
  • Added bot.plugins property [#2199]
  • Added db.forget_channel() and db.forget_plugin() methods [#2224]
  • Renamed db.delete_nick_group() to db.forget_nick_group() [#2224]
  • Added CASEMAPPING support to Identifier [#2231]
    • Static Identifier._lower() method now obeys RFC 1459 casing rules
    • Identifier constructor now takes optional casemapping and chantypes kwargs; see documentation for details, or use the bot.make_identifier() helper method to automatically use the bot’s knowledge about the current server’s configuration
  • Added CHANTYPES support to Identifier [#2236]
  • Added bot.make_identifier_memory() helper to easily take advantage of the bot’s CASEMAPPING and CHANTYPES knowledge [#2552]
  • Changed reading core.nick from the bot’s settings to return a str instead of Identifier [#2231]
    • Depending on what your plugin does, you might need to use the result of bot.make_identifier(bot.settings.core.nick) instead of the raw value
  • Moved memory classes to sopel.tools.memories [#2237]
  • Moved deprecated() from sopel.tools to sopel.lifecycle [#2232]
  • db.get_nick_id() no longer creates a new nick ID by default [#2234]
  • Standardized on ctcp instead of intent [#2253]
    • The old module.intent() decorator was deprecated in Sopel 7.1; use the plugin.ctcp() decorator instead
    • Sopel 8.0 removes intent from trigger.tags; use trigger.ctcp instead
  • Updated invite-related event names in tools.events [#2270]
  • Fixed checking if None exists in a SopelIdentifierMemory [#2306]
  • Fixed and tested SopelIdentifierMemory interactions with plainer-vanilla dictionary types [#2525]
  • Reworked capability negotiation [#2341]
  • Changed trigger.sender property to be None for events not associated with a channel or query [#2359]
  • Fixed trigger.text erroneously containing command name for events with empty args [#2360]
  • Removed STATUSMSG prefix from trigger.sender [#2370, #2441]
    • Status prefix is now stored in a new trigger.status_prefix attribute
    • The bot passed to plugin callables will use the status_prefix and sender attributes to build its default_destination, meaning no change to plugins’ most common usages of bot.say(), bot.reply(), etc.
  • Fixed formatting.color result when passing 0 as fg or bg [[#2366][]]
  • config.types.FilenameAttribute strips quotes from its value [#2371]
  • Made bot.connection_registered more robust [#2375, #2406, #2410]
  • The bot initializes with an UninitializedBackend instead of None, to intercept actions that don’t work prior to connecting [#2394]
    • Prohibited actions raise RuntimeError instead of falling through to a more esoteric error type or—worse—silently failing
  • Added realname & is_bot fields to User objects [#2383, #2448]
  • Use of plugin.require_privilege() or plugin.require_bot_privilege() decorators now implies require_chanmsg() [#2405, #2580]
  • Fixed an inconsistency between behavior and documentation for tools.calculation.pow_complexity() [#2543]
  • Soft-deprecated SopelWrapper type [#2521]
    • Long-term, we will phase out this subclass in favor of using contextvars; see #2460 for the full timeline
  • Moved sopel.plugins to sopel.builtins [#2504]
    • We don’t technically consider these to be part of the API, but if this isn’t mentioned we just know someone will complain that the move broke some custom plugin code
  • Removed previously-deprecated API features [#2128, #2129, #2141, #2144, #2146, #2147, #2148, #2150, #2329]

Housekeeping changes