{"id":16132262,"url":"https://github.com/luk3yx/miniirc","last_synced_at":"2026-02-28T01:09:46.303Z","repository":{"id":48959343,"uuid":"155003830","full_name":"luk3yx/miniirc","owner":"luk3yx","description":"A mini IRC framework.","archived":false,"fork":false,"pushed_at":"2024-12-09T00:13:46.000Z","size":309,"stargazers_count":12,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-04T01:49:52.446Z","etag":null,"topics":["irc","irc-framework","ircv3","ircv3-support","miniirc"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luk3yx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-27T19:58:31.000Z","updated_at":"2025-01-26T13:04:28.000Z","dependencies_parsed_at":"2023-01-29T01:45:33.815Z","dependency_job_id":null,"html_url":"https://github.com/luk3yx/miniirc","commit_stats":null,"previous_names":[],"tags_count":61,"template":false,"template_full_name":null,"purl":"pkg:github/luk3yx/miniirc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luk3yx%2Fminiirc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luk3yx%2Fminiirc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luk3yx%2Fminiirc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luk3yx%2Fminiirc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luk3yx","download_url":"https://codeload.github.com/luk3yx/miniirc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luk3yx%2Fminiirc/sbom","scorecard":{"id":603879,"data":{"date":"2025-08-11","repo":{"name":"github.com/luk3yx/miniirc","commit":"e426c6cd29cc6eec9ffc7223b00c2e61b2ba4def"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonapp.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/luk3yx/miniirc/pythonapp.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpublish.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/luk3yx/miniirc/pythonpublish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpublish.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/luk3yx/miniirc/pythonpublish.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:30","Warn: pipCommand not pinned by hash: .github/workflows/pythonapp.yml:31","Warn: pipCommand not pinned by hash: .github/workflows/pythonpublish.yml:18","Warn: pipCommand not pinned by hash: .github/workflows/pythonpublish.yml:19","Info:   0 out of   3 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/pythonapp.yml:1","Warn: no topLevel permission defined: .github/workflows/pythonpublish.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-21T01:08:50.724Z","repository_id":48959343,"created_at":"2025-08-21T01:08:50.724Z","updated_at":"2025-08-21T01:08:50.724Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29922071,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T19:37:42.220Z","status":"ssl_error","status_checked_at":"2026-02-27T19:37:41.463Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["irc","irc-framework","ircv3","ircv3-support","miniirc"],"created_at":"2024-10-09T22:29:34.174Z","updated_at":"2026-02-28T01:09:46.281Z","avatar_url":"https://github.com/luk3yx.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# miniirc\n\n[![Python 3.4+]](#python-version-support) [![Available on PyPI.]](https://pypi.org/project/miniirc/) [![License: MIT]](https://github.com/luk3yx/miniirc/blob/master/LICENSE.md)\n\nA relatively simple thread-safe(-ish) IRC client framework.\n\nTo install miniirc, simply run `pip3 install miniirc`.\n\nIf you have previously used miniirc, you may want to read the\n[deprecations list] (last updated 2020-04-28).\n\n*This repository is available on both [GitHub](https://github.com/luk3yx/miniirc) and [GitLab](https://gitlab.com/luk3yx/miniirc).*\n\n[Python 3.4+]: https://img.shields.io/badge/python-3.4+-blue.svg\n[Available on PyPI.]: https://img.shields.io/pypi/v/miniirc.svg\n[License: MIT]: https://img.shields.io/pypi/l/miniirc.svg\n[deprecations list]: #deprecations\n\n## Parameters\n\n```py\nirc = miniirc.IRC(ip, port, nick, channels=None, *, ssl=None, ident=None, realname=None, persist=True, debug=False, ns_identity=None, auto_connect=True, ircv3_caps=set(), quit_message='I grew sick and died.', ping_interval=60, ping_timeout=None, verify_ssl=True, server_password=None, executor=None)\n```\n\n*Note that everything before the \\* is a positional argument.*\n\n### Typical usage\n\nYou don't need to add every argument, and the `ip`, `port`, `nick`, and\n`channels` arguments should be specified as positional arguments.\n\n```py\nirc = miniirc.IRC('irc.example.com', 6697, 'my-bot', ['#my-channel'], ns_identity=('my-bot', 'hunter2'), executor=concurrent.futures.ThreadPoolExecutor())\n```\n\nIf you are not doing anything with the main thread after connecting to IRC,\nplease call `irc.wait_until_disconnected()` to prevent Python from trying to\nshut down while miniirc is still connected, breaking thread pools (in\nPython 3.9 and later).\n\n```py\nirc.wait_until_disconnected()\n```\n\n### Parameter descriptions\n\n| Parameter     | Description                                                |\n| ------------- | -------------------------------------------------------- |\n| `ip`          | The IP/hostname of the IRC server to connect to.          |\n| `port`        | The port to connect to.                                   |\n| `nick`        | The nickname of the bot.                                  |\n| `channels`    | The channels to join on connect. This can be an iterable containing strings (list, set, etc), or (since v1.5.0) a comma-delimited string. |\n| `ssl`         | Enable TLS/SSL. If `None`, TLS is disabled unless the port is `6697`. |\n| `ident`       | The ident to use, defaults to `nick`.                     |\n| `realname`    | The realname to use, defaults to `nick` as well.          |\n| `persist`     | Whether to automatically reconnect.                       |\n| `debug`       | Enables debug mode, prints all IRC messages. This can also be a file-like object (with write mode enabled) if you want debug messages to be written into a file instead of being printed to stdout, or a function (for example `logging.debug`). |\n| `ns_identity` | The NickServ account to use as a tuple/list of length 2 (`('\u003cuser\u003e', '\u003cpassword\u003e')`). For compatibility, this can be a string (`'\u003cuser\u003e \u003cpassword\u003e'`). |\n| `auto_connect`| Runs `irc.connect()` straight away.                          |\n| `ircv3_caps`  | A set() of additional IRCv3 capabilities to request. SASL is auto-added if `ns_identity` is specified. |\n| `connect_modes` | A mode string (for example `'+B'`) of UMODEs to set when connected. |\n| `quit_message`| Sets the default quit message. This can be modified per-quit with `irc.disconnect()`. |\n| `ping_interval` | If no packets are sent or received for this amount of seconds, miniirc will send a `PING`, and if no reply is sent, after the ping timeout, miniirc will attempt to reconnect. Set to `None` to disable. |\n| `ping_timeout` | The ping timeout used alongside the above `ping_interval` option, if unspecified will default to `ping_interval`. |\n| `verify_ssl`  | Verifies TLS/SSL certificates. Disabling this is not recommended as it opens the IRC connection up to MiTM attacks. If you have trouble with certificate verification, try running `pip3 install certifi` first. |\n| `server_password` | Sends the password with `PASS` command immediately after connection. If you are looking to log into a NickServ account, you probably want to use `ns_identity` instead. |\n| `executor`    | An instance of `concurrent.futures.ThreadPoolExecutor` to use when running handlers. *New in v1.10.0.* |\n\n*The only mandatory parameters are `ip`, `port`, and `nick`.*\n\n## Functions\n\n| Function      | Description                                               |\n| ------------- | --------------------------------------------------------  |\n| `change_parser(parser=...)` | *See the message parser section for documentation.* |\n| `connect()`   | Connects to the IRC server if not already connected.      |\n| `ctcp(target, *msg, reply=False, tags=None)` | Sends a `CTCP` request or reply to `target`. |\n| `debug(...)`  | Debug, calls `print(...)` if debug mode is on.            |\n| `disconnect(msg=..., *, auto_reconnect=False)`| Disconnects from the IRC server. `auto_reconnect` will be overridden by `self.persist` if set to `True`. |\n| `Handler(...)` | An event handler, see [Handlers](#handlers) for more info. |\n| `me(target, *msg, tags=None)`        | Sends a `/me` (`CTCP ACTION`) to `target`.  |\n| `msg(target, *msg, tags=None)`       | Sends a `PRIVMSG` to `target`. `target` should not contain spaces or start with a colon. |\n| `notice(target, *msg, tags=None)`    | Sends a `NOTICE` to `target`. `target` should not contain spaces or start with a colon. |\n| `quote(*msg, force=False, tags=None)` | Sends a raw message to IRC, use `force=True` to send while disconnected. Do not send multiple commands in one `irc.quote()`, as the newlines will be stripped and it will be sent as one command. The `tags` parameter optionally allows you to add a `dict` with IRCv3 client tags (all starting in `+`), and will not be sent to IRC servers that do not support client tags. |\n| `send(*msg, force=False, tags=None)` | Sends a command to the IRC server, treating every positional argument as a parameter. The usage of this is recommended over `irc.quote()` unless you know what you are doing. |\n| `wait_until_disconnected()` | Waits until the IRC server is disconnected and automatic reconnecting is turned off. |\n\n*Note that if `force=False` on `irc.quote` (or `irc.msg` etc is called) while\nminiirc is not connected, messages will be temporarily stored and then sent\nonce miniirc is connected. Setting `force=True` will throw errors if miniirc is\ncompletely disconnected (`irc.connected` is `None`).*\n\n### irc.quote and irc.send\n\nThe two functions `irc.quote` and `irc.send` may sound similar, however are\nfundamentally different: `irc.quote()` joins all provided arguments with spaces\nand sends them as a raw message to IRC, while `irc.send()` treats each argument\nas a parameter. If arguments passed to `irc.send()` contain spaces, they are\nreplaced with U+00A0 (a non-breaking space, visually similar to a regular\nspace however not interpreted as one).\n\n#### Examples\n\n - `irc.quote('PRIVMSG', '#channel :Hello,', 'world!')` sends \"Hello, world!\"\n    to #channel.\n - `irc.quote('PRIVMSG', '#channel', 'Hello, world!')` is invalid (\"Hello,\" and\n    \"world!\" are sent as separate parameters).\n - `irc.send('PRIVMSG', '#channel', 'Hello, world!')` will send \"Hello, world!\"\n    to \"#channel\".\n - `irc.send('PRIVMSG', '#channel :Hello,', 'world!')` will send \"world!\" to\n    `#channel\\xa0:Hello,`, where `\\xa0` is a non-breaking space.\n\n*If you are unsure and do not need compatibility with miniirc \u003c1.5.0, use\n`irc.send()`. `PRIVMSG` is just used as an example, if you need to send\n`PRIVMSG`s use `irc.msg()` instead.*\n\n## Variables\n\n*These variables should not be changed outside `miniirc.py`.*\n\n| Variable      | Description                                               |\n| ------------- | --------------------------------------------------------  |\n| `active_caps` | A `set` of IRCv3 capabilities that have been successfully negotiated with the IRC server. This is empty while disconnected. |\n| `connected`   | A boolean (or `None`), `True` when miniirc is connected, `False` when miniirc is connecting, and `None` when miniirc is not connected. |\n| `current_nick` | The bot/client's current nickname. Do not modify this, and use this instead of `irc.nick` when getting the bot's current nickname. |\n| `isupport`    | A `dict` with values (not necessarily strings) from `ISUPPORT` messages sent to the client. |\n| `msglen`      | The maximum length (in bytes) of messages (including `\\r\\n`). This is automatically changed if the server supports the `oragono.io/maxline-2` capability. |\n| `nick`        | The nickname to use when connecting to IRC. Until miniirc v2.0.0, you should only use or modify this while disconnected, as it is currently automatically updated with nickname changes. |\n\nThe following arguments passed to `miniirc.IRC` are also available: `ip`,\n`port`, `channels`, `ssl`, `ident`, `realname`, `persist`, `connect_modes`,\n`quit_message`, `ping_interval`, `verify_ssl`.\n\n## Handlers\n\n`miniirc.Handler` and `miniirc.CmdHandler` are function decorators that add\nfunctions to an event handler list. Functions in this list are called in their\nown thread when their respective IRC event(s) is/are received. Handlers may\nwork on every IRC object in existence (`miniirc.Handler`) or only on\nspecific IRC objects (`irc.Handler`).\n\nThe basic syntax for a handler is as followed, where `*events` is a list of events (`PRIVMSG`, `NOTICE`, etc) are called.\n\n```py\nimport miniirc\n@miniirc.Handler(*events, colon=False)\ndef handler(irc, hostmask, args):\n    # irc:      An 'IRC' object.\n    # hostmask: A 'hostmask' object.\n    # args:     A list containing the arguments sent to the command. Everything\n    #             following the first `:` in the command is put into one item\n    #             (args[-1]). If \"colon\" is \"False\", the leading \":\" (if any)\n    #             is automatically removed. To prevent your code from horribly\n    #             breaking, always set it to False unless you know what you are\n    #             doing.\n    pass\n```\n\n#### Recommendations when using handlers:\n\n - If you don't need support for miniirc \u003c1.4.0 and are parsing the last\n    parameter, setting `colon` to `False` is strongly recommended. If the\n    `colon` parameter is omitted, it defaults to `True`, however this will\n    change when miniirc v2.0.0 is released.\n - Although `Handler` and `CmdHandler` currently accept any object that can be\n    converted to a string, every event is converted to a string internally.\n - Not specifying the [`ircv3`](#ircv3-tags) parameter when it is not required\n    prevents a redundant `dict` from being created.\n - To add handlers to a specific `IRC` object and not every one in existence,\n    use `irc.Handler` and `irc.CmdHandler` instead. If you want to create a\n    `Bot` or `Client` class and automatically add handlers to `IRC` objects\n    created inside it, see\n    [making existing functions handlers](#making-existing-functions-handlers).\n\n### Hostmask object\n\nHostmasks are tuples with the format `('user', 'ident', 'hostname')`. If `ident`\nand `hostname` aren't sent from the server, they will be filled in with the\nprevious value. If a command is received without a hostmask, all the `hostmask`\nelements will be set to the name of the command. This is deprecated, however,\nand when miniirc v2.0.0 is released the `hostmask` elements will be set to\nempty strings.\n\n### Making existing functions handlers\n\nYou can make existing functions handlers (for example class instance methods)\nwith `irc.Handler(*events)(handler_function)`. You probably don't want to use\n`miniirc.Handler` for class instance methods, as this will create a handler\nthat gets triggered for every `IRC` object.\n\nYou can also add multiple handlers of the same type easily:\n\n```py\nadd_handler = irc.Handler('PRIVMSG', colon=False)\nadd_handler(handler_1)\nadd_handler(self.instance_handler)\n```\n\nThis is useful if you want to create a `Bot` (or `Client`) class and add\nclass-specific handlers without creating global process-wide handlers or\ncreating a wrapper function for every class instance.\n\n### IRCv3 support\n\n#### IRCv3 tags\n\nIf you want your handler to support IRCv3 message tags, you need to add\n`ircv3=True` to the `Handler` or `CmdHandler` decorator. You will need to add a\n`tags` parameter to your function after `hostmask`. IRCv3 tags are sent to the\nhandlers as `dict`s, with values of either strings or `True`.\n\n*miniirc will automatically un-escape IRCv3 tag values.*\n\n```py\nimport miniirc\n@miniirc.Handler(*events, colon=False, ircv3=True)\ndef handler(irc, hostmask, tags, args):\n    pass\n```\n\n#### IRCv3 capabilities\n\nYou can handle IRCv3 capabilities before connecting using a handler.\nYou must use `force=True` on any `irc.quote()` called here, as when this is\ncalled, miniirc may not yet be fully connected. Do not use the `colon` argument\nfor `Handler` when creating these handlers to avoid unexpected side-effects.\n\n```py\nimport miniirc\n@miniirc.Handler('IRCv3 my-cap-name')\ndef handler(irc, hostmask, args):\n    # Process the capability here\n\n    # IRCv3.2 capabilities:\n    #   args = ['my-cap-name', 'IRCv3.2-parameters']\n\n    # IRCv3.1 capabilities:\n    #   args = ['my-cap-name']\n\n    # Remove the capability from the processing list.\n    irc.finish_negotiation(args[0]) # This can also be 'my-cap-name'.\n```\n\n### Custom message parsers (not recommended)\n\nIf the IRC server you are connecting to supports a non-standard message syntax, you can\ncreate custom message parsers. These are called with the raw message (as a `str`) and\ncan either return `None` to ignore the message or a 4-tuple (`cmd, hostmask, tags, args`)\nthat will then be sent on to the handlers. The items in this 4-tuple should be the same\ntype as the items expected by handlers (and `cmd` should be a string).\n\n#### Message parser example\n\nThis message parser makes the normal parser allow `~` as an IRCv3 tag prefix character.\n\n```py\nimport miniirc\n\ndef my_message_parser(msg):\n    if msg.startswith('~'):\n        msg = '@' + msg[1:]\n    return miniirc.ircv3_message_parser(msg)\n```\n\n#### Changing message parsers\n\nTo change message parsers, you can use `irc.change_parser(func=...)`. If `func` is not\nspecified, it will default to the built-in parser. You can only change message parsers\non-the-fly (for example in an IRCv3 CAP handler). If you need to change message parsers\nbefore connecting, you can disable `auto_connect` and change it then.\n\n```py\nirc = miniirc.IRC(..., auto_connect=False)\nirc.change_parser(my_message_parser)\nirc.connect()\n```\n\n### Handling multiple events\n\nIf you want to handle multiple events and/or be able to get the name of the\nevent being triggered, you can use `irc.CmdHandler`. This will pass an extra\n`command` argument to the handler function (between `irc` and `hostmask`),\ncontaining a string with the command name (such as `PRIVMSG`).\n\n#### Catch-all handlers\n\n**Please do not use these unless there is no other alternative.**\n\nIf you want to handle *every* event, you can use catch-all handlers. To create\nthese, you can call `irc.CmdHandler()` *without* any parameters. Note that this\nhandler will be called many times while connecting (and once connected).\n\n*You cannot call `irc.Handler()` without parameters.*\n\n### Example\n\n```py\nimport miniirc\n\n# Not required, however this makes sure miniirc isn't outdated.\nassert miniirc.ver \u003e= (1,8,2)\n\n@miniirc.Handler('PRIVMSG', 'NOTICE', colon=True)\ndef handler(irc, hostmask, args):\n    print(hostmask[0], 'sent a message to', args[0], 'with content', args[1])\n    # nickname sent a message to #channel with content :Hello, world!\n\n@miniirc.CmdHandler('PRIVMSG', 'NOTICE', colon=False)\ndef cmdhandler(irc, command, hostmask, args):\n    print(hostmask[0], 'sent a', command, 'to', args[0], 'with content',\n        args[1])\n    # nickname sent a PRIVMSG to #channel with content Hello, world!\n```\n\nThis will print a line whenever the bot gets a `PRIVMSG` or `NOTICE`.\n\n## Misc functions\n\nminiirc provides the following helper functions:\n\n| Name                          | Description                               |\n| ----------------------------- | ----------------------------------------- |\n| `miniirc.get_ca_certs()`      | Runs `certifi.where()` if `certifi` is installed, otherwise returns `None`. |\n| `miniirc.ircv3_message_parser(msg)` | The default IRCv2/IRCv3 message parser, returns `cmd, hostmask, tags, args`. |\n| `miniirc.ver`                 | A tuple containing version information.   |\n| `miniirc.version`             | The `CTCP VERSION` reply, can be changed. |\n\nThe version numbering system should be similar to [SemVer](https://semver.org/),\nhowever backwards compatibility is preserved where possible when major releases\nchange.\n\n## Python version support\n\n - Python 3.3 and below are unsupported and do not work with miniirc.\n - Python 3.4, 3.5, and 3.6 are currently supported, but support will likely be\n   dropped in miniirc v2.1.0. Major bugfixes may be backported to v2.0 for a\n   few months after v2.1's release.\n - Python 3.7 and above should work with the latest stable version of miniirc.\n\nIf there is a bug/error in Python 3.4 or newer, please open an issue or pull\nrequest on [GitHub](https://github.com/luk3yx/miniirc/issues) or\n[GitLab](https://gitlab.com/luk3yx/miniirc/issues).\n\n*If you are using Python 3.7 or an older version of Python, I strongly\nrecommend updating. Later versions of Python include features such as f-strings\nthat make software development easier.*\n\n## miniirc_extras\n\nIf you want more advanced(-ish) features such as user tracking, you can use\n[miniirc_extras](https://pypi.org/project/miniirc-extras/)\n([GitHub](https://github.com/luk3yx/miniirc_extras),\n[GitLab](https://gitlab.com/luk3yx/miniirc_extras)). Note that miniirc_extras\nis still in beta and there will be breaking API changes in the future.\n\n## Deprecations\n\nIf miniirc v2.0.0 is ever released, the following breaking changes will\n(probably) be made:\n\n - Internal-only attributes `irc.handlers`, `irc.sock`, and `irc.sendq`\n    (please do not use these) will be renamed. Again, please do not use these.\n - `irc.nick` will always be the nickname used when connecting to IRC rather\n    than the current nickname, use `irc.current_nick` for the current nickname\n    (since v1.4.3).\n - `irc.ns_identity` will be stored as a tuple instead of a string, for example\n    `('username', 'password with spaces')` instead of\n    `'username password with spaces'`. Both formats are currently accepted and\n    will be accepted in the `ns_identity` keyword argument.\n - No exceptions will be raised in `irc.quote`/`irc.send` with `force=True`\n    when the socket is closed. Instead of relying on these exceptions, use\n    `irc.connected` which is set to `None` when completely disconnected.\n - As stated in the Python version support section, Python 3.4 support will be\n    dropped in miniirc v2.1.0, however bugfixes will be backported for a few\n    months.\n - The `colon` keyword argument to `Handler` and `CmdHandler` will default to\n    `False` instead of `True`.\n - Unspecified hostmasks will be an empty string instead of the command. Don't\n    rely on this \"feature\" if possible, simply ignore the hostmask if you do\n    not need it.\n - The `extended-join` capability will be requested by default, use `args[0]`\n    instead of `args[-1]` to get the channel from a `JOIN` event.\n - The `tags` keyword argument will be read-only.\n\n## Working examples/implementations\n\nHere is a list of some (open-source) bots using miniirc, in alphabetical order:\n\n - [irc-rss-feed-bot] - Posts RSS entry titles and shortened URLs to IRC\n    channels. *Python 3.8+*\n - [irc-url-title-bot] - Gets webpage titles from URLs posted in IRC channels.\n    *Python 3.8+*\n - [lurklite] - A generic configurable IRC bot.\n    *[GitHub](https://github.com/luk3yx/lurklite) link.*\n - [stdinbot] - A very simple bot that dumps stdin to an IRC channel.\n    *[GitHub](https://github.com/luk3yx/stdinbot) link.*\n\n*Want to add your own bot/client to this list? Open an issue on\n[GitHub](https://github.com/luk3yx/miniirc/issues) or\n[GitLab](https://gitlab.com/luk3yx/miniirc/issues).*\n\n[irc-rss-feed-bot]:  https://github.com/impredicative/irc-rss-feed-bot\n[irc-url-title-bot]: https://github.com/impredicative/irc-url-title-bot\n[lurklite]:          https://gitlab.com/luk3yx/lurklite\n[stdinbot]:          https://gitlab.com/luk3yx/stdinbot\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluk3yx%2Fminiirc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluk3yx%2Fminiirc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluk3yx%2Fminiirc/lists"}