Repository: gawel/irc3 Branch: main Commit: 62446ad4ded8 Files: 166 Total size: 746.4 KB Directory structure: gitextract_t3elpfhu/ ├── .coveragerc ├── .github/ │ └── workflows/ │ └── tox.yml ├── .gitignore ├── CHANGES.rst ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── conftest.py ├── docker/ │ ├── Dockerfile │ └── Makefile ├── docs/ │ ├── Makefile │ ├── conf.py │ ├── dcc.rst │ ├── dec.rst │ ├── hack.rst │ ├── index.rst │ ├── irc3.rst │ ├── plugins/ │ │ ├── asynchronious.rst │ │ ├── autocommand.rst │ │ ├── autojoins.rst │ │ ├── casefold.rst │ │ ├── command.rst │ │ ├── core.rst │ │ ├── cron.rst │ │ ├── ctcp.rst │ │ ├── dcc.rst │ │ ├── feeds.rst │ │ ├── fifo.rst │ │ ├── human.rst │ │ ├── log.rst │ │ ├── logger.rst │ │ ├── pager.rst │ │ ├── quakenet.rst │ │ ├── sasl.rst │ │ ├── search.rst │ │ ├── shell_command.rst │ │ ├── slack.rst │ │ ├── social.rst │ │ ├── storage.rst │ │ ├── uptime.rst │ │ ├── userlist.rst │ │ └── web.rst │ ├── reloadable.rst │ ├── rfc.rst │ └── utils.rst ├── examples/ │ ├── async_command.py │ ├── benches.ini │ ├── benches.py │ ├── bot.ini │ ├── commands.rst │ ├── config.ini │ ├── dcc_chat.py │ ├── dcc_send.py │ ├── dcc_send_and_get.py │ ├── dev.ini │ ├── freenode_irc3.py │ ├── humans.py │ ├── mybot.py │ ├── mybot_plugin.py │ ├── mycommands.py │ ├── mycrons.py │ ├── myextends.py │ ├── nickserv.py │ ├── paginate.py │ ├── proxy.py │ ├── slack.ini │ ├── spy.ini │ ├── spy.py │ ├── topic.py │ └── wsgiapp.py ├── irc3/ │ ├── __init__.py │ ├── __main__.py │ ├── _gen_doc.py │ ├── _parse_rfc.py │ ├── _rfc.py │ ├── asynchronous.py │ ├── base.py │ ├── compat.py │ ├── config.py │ ├── dcc/ │ │ ├── __init__.py │ │ ├── client.py │ │ └── manager.py │ ├── dec.py │ ├── plugins/ │ │ ├── __init__.py │ │ ├── asynchronious.py │ │ ├── autocommand.py │ │ ├── autojoins.py │ │ ├── casefold.py │ │ ├── command.py │ │ ├── core.py │ │ ├── cron.py │ │ ├── ctcp.py │ │ ├── dcc.py │ │ ├── feeds.py │ │ ├── fifo.py │ │ ├── human.py │ │ ├── log.py │ │ ├── logger.py │ │ ├── pager.py │ │ ├── quakenet.py │ │ ├── sasl.py │ │ ├── search.py │ │ ├── shell_command.py │ │ ├── slack.py │ │ ├── social.py │ │ ├── storage.py │ │ ├── uptime.py │ │ ├── userlist.py │ │ └── web.py │ ├── rfc.py │ ├── rfc1459.txt │ ├── rfc2812.txt │ ├── tags.py │ ├── template/ │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── config.ini │ │ └── plugin.py │ ├── testing.py │ └── utils.py ├── irc3d/ │ ├── __init__.py │ ├── __main__.py │ ├── dec.py │ ├── motd.txt │ └── plugins/ │ ├── __init__.py │ ├── command.py │ ├── core.py │ └── userlist.py ├── pyproject.toml ├── tests/ │ ├── __init__.py │ ├── feed.atom │ ├── test.ini │ ├── test_async.py │ ├── test_autocommand.py │ ├── test_autojoins.py │ ├── test_bot.py │ ├── test_casefold.py │ ├── test_commands.py │ ├── test_cron.py │ ├── test_ctcp.py │ ├── test_dcc.py │ ├── test_dec.py │ ├── test_events.py │ ├── test_feeds.py │ ├── test_fifo.py │ ├── test_irc3d.py │ ├── test_irc3d_commands.py │ ├── test_irc3d_userlist.py │ ├── test_logger.py │ ├── test_paginate.py │ ├── test_protocol.py │ ├── test_quakenet.py │ ├── test_reconn.py │ ├── test_reload.py │ ├── test_run.py │ ├── test_sasl.py │ ├── test_search.py │ ├── test_slack.py │ ├── test_social.py │ ├── test_template.py │ ├── test_uptime.py │ ├── test_userlist.py │ ├── test_utils.py │ └── test_web.py └── tox.ini ================================================ FILE CONTENTS ================================================ ================================================ FILE: .coveragerc ================================================ [run] include = */irc3/*.py */irc3/*/*.py omit = conftest.py .tox/* tests/* docs/conf.py */examples/social* */examples/mycrons* */examples/mycommands* */examples/myextends* */examples/nickserv* */examples/freenode* */irc3/template/plugin* */irc3/_gen_doc* */irc3/_parse_rfc* */site-packages* */lib/python* */lib-python* */lib_pypy* [report] exclude_lines = pragma: no cover def __repr__ raise NotImplementedError if __name__ == .__main__.: def parse_args ================================================ FILE: .github/workflows/tox.yml ================================================ name: tox on: [push, pull_request] jobs: flake8-and-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.12 - name: Install Tox and any other packages run: pip install tox - name: Run flake8 / docs run: tox -e flake8,docs test: runs-on: ubuntu-latest strategy: matrix: python: ["3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Start Redis uses: supercharge/redis-github-action@1.8.1 with: redis-version: 8 - name: Install Tox and any other packages run: pip install tox - name: Run Tox run: tox -e py ================================================ FILE: .gitignore ================================================ *~ *.bck bin build _build .bzr .bzrignore .eggs .cache .chutifab .coverage coverage* develop-eggs dist downloads *.egg *.EGG *.egg-info *.EGG-INFO eggs fake-eggs .hg .hgignore .idea .installed.cfg irssi_config *.jar *.mo .mr.developer.cfg nosetest* *.old *.orig parts *.pyc *.pyd *.pyo .ropeproject/ *.so src .svn *.swp .tox *.tmp* var *.wpr ================================================ FILE: CHANGES.rst ================================================ 1.1.11 (unreleased) =================== - Nothing changed yet. 1.1.10 (2023-04-17) =================== - fix `shell_command` plugin 1.1.9 (2023-02-27) ================== - drop support for python 3.5 - python 3.11 compat 1.1.8 (2022-07-17) ================== - Allow comma-separated chans in server userlist JOIN - python 3.10 compat - improve feeds plugin 1.1.7 (2021-02-13) ================== - Add sqlite storage. - Escape string that are interpolated into the regular expressions. 1.1.6 (2020-05-13) ================== - Allow to overrides config values via `os.environ`. 1.1.5 (2020-01-18) ================== - Allow to use `irc3.rfc.*` as `iotype='out'` - Use more pop in userlist to avoid bug when a weird event occurs 1.1.4 (2019-11-22) ================== - Use pop in userlist to avoid bug when a weird event occurs - Expend filename path in logger plugin - Force latest venusian version 1.1.3 (2019-08-22) ================== - Rename async plugin to asynchronious so we can use it with python3.7 1.1.2 (2019-02-28) ================== - Allow to have more than one `[bot]` section in config file - IrcString now allow to retrieve username/hostname from mask 1.1.1 (2018-09-22) ================== - Do not show builtins command in help. Fixed #167 1.1.0 (2018-07-30) ================== - Backward incompatibility: async is renamed to asynchronous for py37 compat (thanks to @JulienPalard) - Except a KeyError on missing twitter configuration in social plugin 1.0.3 (2018-05-08) ================== - Add web plugin to allow to POST messages to channel via http - Add slack plugin - Avoid bug in userlist when a user parts before join (twitch) - Drop py33, py34 support 1.0.2 (2017-11-13) ================== - Add `irc3.__main__` to allow `python -m irc3 config.ini` 1.0.1 (2017-11-11) ================== - Add `command(error_format=callable)` argument. - Add ${#} as a shorter alias for ${hash}. - Bug fix in fifo plugin. We now remove the file first if it's not a pipe. - Fix some irc3d issues with QUIT and KICK command 1.0.0 (2017-02-05) ================== - Added storage.getlist()/setlist() - Improve autocommands 0.9.8 (2017-01-03) ================== - Command aliases support (#121) - Add quakenet plugin (#99) - Allow dash character in command 0.9.7 (2016-12-07) ================== - Added ``bot.async_cmds.topic()`` - Use shlex to parse command args by default 0.9.6 (2016-10-24) ================== - Fixed #118: ssl context.check_hostname must be False when using CERT_NONE 0.9.5 (2016-10-15) ================== - ``.privmsg(nowait=True)`` now really don't wait - clean up some old py2/3 compat code 0.9.4 (2016-09-15) ================== - Added SASL Auth support 0.9.3 (2016-05-31) ================== - New release due to release problem (broken pypi) 0.9.2 (2016-05-31) ================== - Fixed flood_burst sending one extra line (#92) 0.9.1 (2016-05-22) ================== - Added autojoin_delay option. Handle reload in autojoin plugin. - Added flood_rate_delay option. - Added --help and --version CLI options. - Fixed #91 bug with command arguments and spaces 0.9.0 (2016-04-20) ================== - WARNING: we do no longer support python2. python3.3+ is required. - WARNING: realname is now username and userinfo is now realname in config - Introduce some plugins: fifo, shell_commands, pager - Add ``flood_burst`` and ``flood_rate`` options. Queue outgoing messages in a single queue handle by ``send_line('...', nowait=False)``. - ``bot.async`` is now aliased to ``bot.async_cmds`` to be able to use ``await`` 0.8.9 (2016-02-23) ================== - use re.escape to escape command char 0.8.8 (2016-01-27) ================== - logger plugin now take care of unicode 0.8.7 (2016-01-16) ================== - fixed 76: split large messages using textwrap.wrap(). This will avoid RevQ exceeded. 0.8.6 (2016-01-07) ================== - fix DCC stuff for python3.5 - added DCC examples at https://github.com/gawel/irc3/tree/master/examples 0.8.5 (2015-12-22) ================== - ${hash} is now replaced by # in config files. This allow to set real channel names. 0.8.4 (2015-11-29) ================== - added basic support for IRCv3.2 tags - fixed #78: plugin can be old style classes - fixed #75: Ensure we send the PING and PONG data as trailing - fixed #71: need to pass host and ip to dcc 0.8.3 (2015-11-04) ================== - fix wheel metadata - public command was not public if you're using a guard 0.8.2 (2015-11-01) ================== - Added !help nonexistant error message - Allow to hide commands from !help - Don't reject commands with trailing spaces - Allow to use coroutine guards - Make commands case insensitive - Add basic casefolding plugin - Prevent keyerror when setting keys that don't exist in cache. 0.8.1 (2015-05-14) ================== - Fixes bug in userlist plugin `#59 `_ - Strip out self.context.config.cmd from !help arg. Allow to use !help !cmd `#57 `_ 0.8.0 (2015-04-19) ================== - Added dcc send/get/chat implementation - Improved storage: can now test the existence of a key - irc.plugins.storage: `db['foo']` now will raise a `KeyError` if the key does not exist to match dictionary behaviour. This will **break** existing implementations that make use of this. - irc.plugins.storage now supports `db.get(key)` that will return either `None` or the value of an optional `default` argument. - irc3.plugins.feeds is now full async 0.7.1 (2015-02-26) ================== - Storage plugin documentation - Support python 3.4.1 again 0.7.0 (2015-02-24) ================== - the cron plugin now require `aiocron `_ - Add `irc3.plugins.async`; Allow to `yield from bot.async.whois('gawel')` - commands and events can now be coroutines 0.6.0 (2015-02-15) ================== - Allow to reload modules/plugins - Add storage plugin - Fixed #34 Avoid newline injection. 0.5.3 (2014-12-09) ================== - Bugfix release. Fixed #27 and #30 0.5.2 (2014-11-16) ================== - Basic irc3d server - Modules reorganisation - Add S3 logger 0.5.1 (2014-07-21) ================== - Fixed #13: venusian 1.0 compat - Add antiflood option for the command plugin - commands accept unicode 0.5.0 (2014-06-01) ================== - Added ``bot.kick()`` and ``bot.mode()`` - Rewrite ctcp plugin so we can ignore flood requests - Trigger ``{plugin}.server_ready()`` at the end of MOTD - Fixed #9: The ``command`` plugin uses ``cmd``, not ``cmdchar``. - Fixed #10. Store server config. Use STATUSMSG config if any in ``userlist`` - ``userlist`` plugin now also store user modes per channel. - Rename ``add_event`` to ``attach_events`` and added ``detach_events``. This allow to add/remove events on the fly. - The autojoin plugin now detach motd related events after triggering one of them. - Fix compatibility with trollius 0.3 0.4.10 (2014-05-21) =================== - Fixed #5: autojoin on no motd - allow to show date/times in console log 0.4.9 (2014-05-08) ================== - Allow to trigger event on output with ``event(iotype='out')`` - Add a channel logger plugin - autojoins is now a separate plugin - userlist plugin take care of kicks - social plugin is now officially supported and tested 0.4.7 (2014-04-03) ================== - IrcString use unicode with py2 0.4.6 (2014-03-11) ================== - Bug fix. The cron need a loop sooner as possible. 0.4.5 (2014-02-25) ================== - Bug fix. An event was run twice if more than one where using the same regexp 0.4.4 (2014-02-15) ================== - Add cron plugin - Improve the command plugin. Fix some security issue. - Add ``--help-page`` option to generate commands help pages 0.4.3 (2014-01-10) ================== - Fix a bug on connection_lost. - Send realname in USER command instead of nickname 0.4.2 (2014-01-09) ================== - python2.7 support. - add some plugins (ctcp, uptime, feeds, search) - add some examples/ (twitter, asterisk) - improve some internals 0.4.1 (2013-12-30) ================== - Depends on venusian 1.0a8 0.1 (2013-11-30) ================ - Initial release ================================================ FILE: CONTRIBUTING.rst ================================================ Contribute ========== First, if you want to add a cool plugin, consider submit a pull request to the `irc3_plugins `_ instead of irc3 itself. Feel free to clone the project on `GitHub `_. To test your change you can run irc3 in debug mode using:: $ irc3 --debug path-to-your-conf.ini Once you made a change, try to add a test for your feature/fix. At least assume that you have'nt broke anything by running tox:: $ tox ... py27: commands succeeded py32: commands succeeded py33: commands succeeded py34: commands succeeded flake8: commands succeeded docs: commands succeeded congratulations :) You can run tests for a specific version:: $ tox -e py34 The `irc3.rfc` module is auto generated from `irc3/rfc1459.txt`. If you want to hack this file, you need to hack the parser in `irc3/_parse_rfc.py` (warning, it's ugly) You can regenerate the module and docs by running:: $ tox -e build You can also build the docs with:: $ tox -e docs And check the result:: $ firefox .tox/docs/tmp/html/index.html The project uses ``setuptools``, you can test-install it using `pip`: $ pip install . $ irc3 -h ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Gael Pasgrimaud Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MANIFEST.in ================================================ graft docs prune docs/_build prune .github/ graft examples graft irc3 graft irc3d graft tests include Makefile *.rst *.cfg *.ini *.txt global-exclude *.pyc *.swp global-exclude __pycache__ include conftest.py exclude .installed.cfg exclude .coveragerc exclude update.sh exclude Dockerfile exclude docker exclude docker/Dockerfile exclude docker/Makefile include LICENSE ================================================ FILE: Makefile ================================================ APP:=$(shell basename `pwd`) HOSTNAME:=$(shell hostname) HOST:=amandine PYTHON?=$(HOME)/.venvs/py3/bin/python3 build: docker build -t gawel/irc3 . venv: $(PYTHON) -m venv venv ./venv/bin/pip install -e .[test,web] run: venv ./venv/bin/irc3 config.ini upgrade: venv ifeq ($(HOSTNAME), $(HOST)) git pull origin master ~/apps/bin/circusctl restart $(APP) else git push origin master ssh $(HOST) "cd ~/apps/$(APP) && make upgrade" endif ================================================ FILE: README.rst ================================================ ============================================================ irc3. pluggable irc client library based on python's asyncio ============================================================ .. image:: https://github.com/gawel/irc3/actions/workflows/tox.yml/badge.svg :target: https://github.com/gawel/irc3/actions/workflows/tox.yml .. image:: https://coveralls.io/repos/gawel/irc3/badge.png?branch=master :target: https://coveralls.io/r/gawel/irc3?branch=master A pluggable irc client library based on python's `asyncio `_. .. image:: https://raw.githubusercontent.com/gawel/irc3/master/docs/_static/logo.png :width: 100 :height: 100 :align: center :alt: Ceative Commons – Attribution (CC BY 3.0) - Hydra designed by Huu Nguyen from the Noun Project - http://thenounproject.com/term/hydra/46963/ :target: http://thenounproject.com/term/hydra/46963/ Requires python 3.11+ Python 2 is no longer supported, but if you don't have a choice you can use an older version:: $ pip install "irc3<0.9" Source: https://github.com/gawel/irc3/ Docs: https://irc3.readthedocs.io/ Irc: irc://irc.freenode.net/irc3 (`www `_) I've spent hours writing this software, with love. Please consider tipping if you like it: BTC: 1PruQAwByDndFZ7vTeJhyWefAghaZx9RZg ETH: 0xb6418036d8E06c60C4D91c17d72Df6e1e5b15CE6 LTC: LY6CdZcDbxnBX9GFBJ45TqVj8NykBBqsmT ================================================ FILE: conftest.py ================================================ # -*- coding: utf-8 -*- from irc3 import testing import pytest import sys import os try: from redis.exceptions import ConnectionError from redis.client import StrictRedis StrictRedis().flushdb() except (ImportError, ConnectionError): import re req_redis = re.compile(r".*#[^#]*\s*require?\s*redis\s*$", re.IGNORECASE) def pytest_runtest_setup(item): if getattr(item, 'dtest', None): if any(req_redis.match(e.source) for e in item.dtest.examples): pytest.skip("No redis server is running") dirname = os.path.dirname(__file__) sys.path.append(os.path.join(dirname, 'examples')) @pytest.fixture(scope="function") def cls_event_loop(request, event_loop): request.cls.loop = event_loop request.cls.config['loop'] = event_loop yield request.cls.config.pop('loop') @pytest.fixture(scope="function") def irc3_bot_factory(request, event_loop): def _bot(**config): config['loop'] = event_loop _b['b'] = testing.IrcBot(**config) return _b['b'] _b = {} yield _bot _b['b'].SIGINT() ================================================ FILE: docker/Dockerfile ================================================ FROM python:3.7 RUN adduser --disabled-password --gecos '' irc3 RUN echo build \ && cd /usr/src && git clone https://github.com/gawel/irc3.git \ && cd /usr/src/irc3 && pip install ipython && pip install -e .[test] \ && mkdir -p /usr/src/bot && chown -R irc3:irc3 /usr/src/bot WORKDIR /usr/src/bot ONBUILD COPY . /usr/src/bot USER irc3 CMD ["/usr/local/bin/irc3", "config.ini"] ================================================ FILE: docker/Makefile ================================================ build: docker build --rm=true -f Dockerfile -t irc3:latest . run: docker run -v $(PWD)/../:/usr/src/bot -v $(HOME)/.irc3:/home/irc3/.irc3 irc3:latest irc3 examples/dev.ini ================================================ FILE: docs/Makefile ================================================ # Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = ../bin/sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/irc3.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/irc3.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/irc3" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/irc3" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ================================================ FILE: docs/conf.py ================================================ # -*- coding: utf-8 -*- # # irc3 documentation build configuration file, created by # sphinx-quickstart on Mon Nov 25 01:03:01 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'irc3' copyright = '2013, Gael Pasgrimaud' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'irc3doc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'irc3.tex', 'irc3 Documentation', 'Gael Pasgrimaud', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'irc3', 'irc3 Documentation', ['Gael Pasgrimaud'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'irc3', 'irc3 Documentation', 'Gael Pasgrimaud', 'irc3', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' from importlib.metadata import version version = version("irc3") release = version ================================================ FILE: docs/dcc.rst ================================================ ======================== :mod:`irc3.dcc` DCC ======================== See :func:`~irc3.IrcBot.dcc_chat`, :func:`~irc3.IrcBot.dcc_get` and :func:`~irc3.IrcBot.dcc_send` Here is a simple plugin to send a generated file: .. literalinclude:: ../examples/dcc_send.py API === .. automodule:: irc3.dcc .. autoclass:: DCCManager :members: .. autoclass:: DCCChat :members: .. autoclass:: DCCSend :members: .. autoclass:: DCCGet :members: ================================================ FILE: docs/dec.rst ================================================ ============================ :mod:`irc3.dec` decorators ============================ .. automodule:: irc3.dec plugin ====== .. autofunction:: plugin bot event ========= .. autoclass:: event bot extend ========== .. autofunction:: extend ================================================ FILE: docs/hack.rst ================================================ .. include:: ../CONTRIBUTING.rst ================================================ FILE: docs/index.rst ================================================ .. include:: ../README.rst Installation ============== Using pip:: $ pip install irc3 Quick start =========== irc3 provides a basic template to help you to quickly test a bot. Here is how to create a bot named ``mybot``. Create a new directory and cd to it: .. code-block:: sh $ mkdir mybot $ cd mybot Then use the template: .. code-block:: sh $ python -m irc3.template mybot This will create an almost ready to use ``config.ini`` file and a simple plugin named ``mybot_plugin.py`` that says «Hi» when the bot or someone else joins a channel and includes an ``echo`` command. Here is what the config file will looks like: .. literalinclude:: ../examples/config.ini :language: ini And here is the plugin: .. literalinclude:: ../examples/mybot_plugin.py Have a look at those file and edit the config file for your needs. You may have to edit: - the autojoin channel - your irc mask in the ``irc3.plugins.command.mask`` section Once you're done with editing, run: .. code-block:: sh $ irc3 config.ini Check the help of the ``irc3`` command. .. code-block:: sh $ irc3 -h If you're enjoying it, you can check for more detailed docs below. And some more examples here: https://github.com/gawel/irc3/tree/master/examples Documentation ============= .. toctree:: :maxdepth: 2 :glob: dec utils rfc dcc reloadable plugins/* hack Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ================================================ FILE: docs/irc3.rst ================================================ ============================ :mod:`irc3` Irc bot ============================ .. automodule:: irc3 IrcBot ====== .. autoclass:: IrcBot :members: :inherited-members: .. autoattribute:: defaults IrcConnection ============= .. autoclass:: IrcConnection :members: ================================================ FILE: docs/plugins/asynchronious.rst ================================================ .. automodule:: irc3.plugins.asynchronious ================================================ FILE: docs/plugins/autocommand.rst ================================================ .. automodule:: irc3.plugins.autocommand ================================================ FILE: docs/plugins/autojoins.rst ================================================ .. automodule:: irc3.plugins.autojoins ================================================ FILE: docs/plugins/casefold.rst ================================================ .. automodule:: irc3.plugins.casefold ================================================ FILE: docs/plugins/command.rst ================================================ .. automodule:: irc3.plugins.command ================================================ FILE: docs/plugins/core.rst ================================================ .. automodule:: irc3.plugins.core ================================================ FILE: docs/plugins/cron.rst ================================================ .. automodule:: irc3.plugins.cron ================================================ FILE: docs/plugins/ctcp.rst ================================================ .. automodule:: irc3.plugins.ctcp ================================================ FILE: docs/plugins/dcc.rst ================================================ .. automodule:: irc3.plugins.dcc ================================================ FILE: docs/plugins/feeds.rst ================================================ .. automodule:: irc3.plugins.feeds ================================================ FILE: docs/plugins/fifo.rst ================================================ .. automodule:: irc3.plugins.fifo ================================================ FILE: docs/plugins/human.rst ================================================ .. automodule:: irc3.plugins.human ================================================ FILE: docs/plugins/log.rst ================================================ .. automodule:: irc3.plugins.log ================================================ FILE: docs/plugins/logger.rst ================================================ .. automodule:: irc3.plugins.logger ================================================ FILE: docs/plugins/pager.rst ================================================ .. automodule:: irc3.plugins.pager ================================================ FILE: docs/plugins/quakenet.rst ================================================ .. automodule:: irc3.plugins.quakenet ================================================ FILE: docs/plugins/sasl.rst ================================================ .. automodule:: irc3.plugins.sasl ================================================ FILE: docs/plugins/search.rst ================================================ .. automodule:: irc3.plugins.search ================================================ FILE: docs/plugins/shell_command.rst ================================================ .. automodule:: irc3.plugins.shell_command ================================================ FILE: docs/plugins/slack.rst ================================================ .. automodule:: irc3.plugins.slack ================================================ FILE: docs/plugins/social.rst ================================================ .. automodule:: irc3.plugins.social ================================================ FILE: docs/plugins/storage.rst ================================================ .. automodule:: irc3.plugins.storage ================================================ FILE: docs/plugins/uptime.rst ================================================ .. automodule:: irc3.plugins.uptime ================================================ FILE: docs/plugins/userlist.rst ================================================ .. automodule:: irc3.plugins.userlist ================================================ FILE: docs/plugins/web.rst ================================================ .. automodule:: irc3.plugins.web ================================================ FILE: docs/reloadable.rst ================================================ Reloadable plugins ================== .. note:: if you just want the bot to restart when you change a file during development you can use `hupper `_:: $ pip install hupper $ hupper -m irc3 config.ini irc3 provides a way to reload plugins without restarting the bot. To do that, your plugin should provide a ``reload`` class method:: class Plugin(object): def __init__(self, bot): self.bot = bot @classmethod def reload(cls, old): """this method should return a ready to use plugin instance. cls is the newly reloaded class. old is the old instance. """ return cls(old.bot) Plugins can also implement a few hooks to help take care of reloads:: class Plugin(object): def __init__(self, bot): self.bot = bot def before_reload(self): """Do stuff before reload""" def after_reload(self): """Do stuff after reload""" .. >>> from irc3.testing import IrcBot >>> bot = IrcBot(includes=['mycommands']) To reload a plugin, just call the :func:`~irc3.IrcBot.reload` method with module name(s) to reload:: >>> bot.reload('mycommands') ================================================ FILE: docs/rfc.rst ================================================ ======================== :mod:`irc3.rfc` RFC1459 ======================== Replies (REPL) ============== 259 - RPL_ADMINEMAIL -------------------- Format ``:{srv} 259 {nick} :{admin_info}`` Match ``^:(?P\S+) 259 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ADMINEMAIL) def myevent(bot, srv=None, me=None, data=None): # do something 257 - RPL_ADMINLOC1 ------------------- Format ``:{srv} 257 {nick} :{admin_info}`` Match ``^:(?P\S+) 257 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ADMINLOC1) def myevent(bot, srv=None, me=None, data=None): # do something 258 - RPL_ADMINLOC2 ------------------- Format ``:{srv} 258 {nick} :{admin_info}`` Match ``^:(?P\S+) 258 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ADMINLOC2) def myevent(bot, srv=None, me=None, data=None): # do something 256 - RPL_ADMINME ----------------- Format ``:{srv} 256 {nick} {server} :Administrative info`` Match ``^:(?P\S+) 256 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ADMINME) def myevent(bot, srv=None, me=None, server=None, data=None): # do something 301 - RPL_AWAY -------------- Format ``:{srv} 301 {nick} {nick} :{away_message}`` Match ``^:(?P\S+) 301 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_AWAY) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 367 - RPL_BANLIST ----------------- Format ``:{srv} 367 {nick} {channel} {banid}`` Match ``^:(?P\S+) 367 (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_BANLIST) def myevent(bot, srv=None, me=None, channel=None, banid=None): # do something 324 - RPL_CHANNELMODEIS ----------------------- Format ``:{srv} 324 {nick} {channel} {mode} {mode_params}`` Match ``^:(?P\S+) 324 (?P\S+) (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_CHANNELMODEIS) def myevent(bot, srv=None, me=None, channel=None, mode=None, mode_params=None): # do something 368 - RPL_ENDOFBANLIST ---------------------- Format ``:{srv} 368 {nick} {channel} :End of channel ban list`` Match ``^:(?P\S+) 368 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFBANLIST) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 374 - RPL_ENDOFINFO ------------------- Format ``:{srv} 374 {nick} :End of /INFO list`` Match ``^:(?P\S+) 374 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFINFO) def myevent(bot, srv=None, me=None, data=None): # do something 365 - RPL_ENDOFLINKS -------------------- Format ``:{srv} 365 {nick} {mask} :End of /LINKS list`` Match ``^:(?P\S+) 365 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFLINKS) def myevent(bot, srv=None, me=None, mask=None, data=None): # do something 376 - RPL_ENDOFMOTD ------------------- Format ``:{srv} 376 {nick} :End of /MOTD command`` Match ``^:(?P\S+) 376 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFMOTD) def myevent(bot, srv=None, me=None, data=None): # do something 366 - RPL_ENDOFNAMES -------------------- Format ``:{srv} 366 {nick} {channel} :End of /NAMES list`` Match ``^:(?P\S+) 366 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFNAMES) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 219 - RPL_ENDOFSTATS -------------------- Format ``:{srv} 219 {nick} {stats_letter} :End of /STATS report`` Match ``^:(?P\S+) 219 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFSTATS) def myevent(bot, srv=None, me=None, stats_letter=None, data=None): # do something 394 - RPL_ENDOFUSERS -------------------- Format ``:{srv} 394 {nick} :End of users`` Match ``^:(?P\S+) 394 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFUSERS) def myevent(bot, srv=None, me=None, data=None): # do something 315 - RPL_ENDOFWHO ------------------ Format ``:{srv} 315 {nick} {nick} :End of /WHO list`` Match ``^:(?P\S+) 315 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFWHO) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 318 - RPL_ENDOFWHOIS -------------------- Format ``:{srv} 318 {nick} {nick} :End of /WHOIS list`` Match ``^:(?P\S+) 318 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFWHOIS) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 369 - RPL_ENDOFWHOWAS --------------------- Format ``:{srv} 369 {nick} {nick} :End of WHOWAS`` Match ``^:(?P\S+) 369 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ENDOFWHOWAS) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 371 - RPL_INFO -------------- Format ``:{srv} 371 {nick} :{string}`` Match ``^:(?P\S+) 371 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_INFO) def myevent(bot, srv=None, me=None, data=None): # do something 341 - RPL_INVITING ------------------ Format ``:{srv} 341 {nick} {channel} {nick}`` Match ``^:(?P\S+) 341 (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_INVITING) def myevent(bot, srv=None, me=None, channel=None, nick=None): # do something 303 - RPL_ISON -------------- Format ``:{srv} 303 {nick} :{nicknames}`` Match ``^:(?P\S+) 303 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_ISON) def myevent(bot, srv=None, me=None, data=None): # do something 364 - RPL_LINKS --------------- Format ``:{srv} 364 {nick} {mask} {server} :{hopcount} {server_info}`` Match ``^:(?P\S+) 364 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LINKS) def myevent(bot, srv=None, me=None, mask=None, server=None, data=None): # do something 322 - RPL_LIST -------------- Format ``:{srv} 322 {nick} {channel} {visible} :{topic}`` Match ``^:(?P\S+) 322 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LIST) def myevent(bot, srv=None, me=None, channel=None, visible=None, data=None): # do something 323 - RPL_LISTEND ----------------- Format ``:{srv} 323 {nick} :End of /LIST`` Match ``^:(?P\S+) 323 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LISTEND) def myevent(bot, srv=None, me=None, data=None): # do something 321 - RPL_LISTSTART ------------------- Format ``:{srv} 321 {nick} Channel :Users Name`` Match ``^:(?P\S+) 321 (?P\S+) Channel :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LISTSTART) def myevent(bot, srv=None, me=None, data=None): # do something 254 - RPL_LUSERCHANNELS ----------------------- Format ``:{srv} 254 {nick} {x} :channels formed`` Match ``^:(?P\S+) 254 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LUSERCHANNELS) def myevent(bot, srv=None, me=None, x=None, data=None): # do something 251 - RPL_LUSERCLIENT --------------------- Format ``:{srv} 251 {nick} :There are {x} users and {y} invisible on {z} servers`` Match ``^:(?P\S+) 251 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LUSERCLIENT) def myevent(bot, srv=None, me=None, data=None): # do something 255 - RPL_LUSERME ----------------- Format ``:{srv} 255 {nick} :I have {x} clients and {y}`` Match ``^:(?P\S+) 255 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LUSERME) def myevent(bot, srv=None, me=None, data=None): # do something 252 - RPL_LUSEROP ----------------- Format ``:{srv} 252 {nick} {x} :operator(s) online`` Match ``^:(?P\S+) 252 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LUSEROP) def myevent(bot, srv=None, me=None, x=None, data=None): # do something 253 - RPL_LUSERUNKNOWN ---------------------- Format ``:{srv} 253 {nick} {x} :unknown connection(s)`` Match ``^:(?P\S+) 253 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_LUSERUNKNOWN) def myevent(bot, srv=None, me=None, x=None, data=None): # do something 372 - RPL_MOTD -------------- Format ``:{srv} 372 {nick} :- {text}`` Match ``^:(?P\S+) 372 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_MOTD) def myevent(bot, srv=None, me=None, data=None): # do something 375 - RPL_MOTDSTART ------------------- Format ``:{srv} 375 {nick} :- {server} Message of the day -`` Match ``^:(?P\S+) 375 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_MOTDSTART) def myevent(bot, srv=None, me=None, data=None): # do something 353 - RPL_NAMREPLY ------------------ Format ``:{srv} 353 {nick} {m} {channel} :{nicknames}`` Match ``^:(?P\S+) 353 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_NAMREPLY) def myevent(bot, srv=None, me=None, m=None, channel=None, data=None): # do something 331 - RPL_NOTOPIC ----------------- Format ``:{srv} 331 {nick} {channel} :No topic is set`` Match ``^:(?P\S+) 331 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_NOTOPIC) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 395 - RPL_NOUSERS ----------------- Format ``:{srv} 395 {nick} :Nobody logged in`` Match ``^:(?P\S+) 395 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_NOUSERS) def myevent(bot, srv=None, me=None, data=None): # do something 306 - RPL_NOWAWAY ----------------- Format ``:{srv} 306 {nick} :You have been marked as being away`` Match ``^:(?P\S+) 306 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_NOWAWAY) def myevent(bot, srv=None, me=None, data=None): # do something 382 - RPL_REHASHING ------------------- Format ``:{srv} 382 {nick} {config_file} :Rehashing`` Match ``^:(?P\S+) 382 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_REHASHING) def myevent(bot, srv=None, me=None, config_file=None, data=None): # do something 213 - RPL_STATSCLINE -------------------- Format ``:{srv} 213 {nick} C {host} * {nick} {port} {class}`` Match ``^:(?P\S+) 213 (?P\S+) C (?P\S+) . (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSCLINE) def myevent(bot, srv=None, me=None, host=None, nick=None, port=None, class=None): # do something 212 - RPL_STATSCOMMANDS ----------------------- Format ``:{srv} 212 {nick} {cmd} {count}`` Match ``^:(?P\S+) 212 (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSCOMMANDS) def myevent(bot, srv=None, me=None, cmd=None, count=None): # do something 244 - RPL_STATSHLINE -------------------- Format ``:{srv} 244 {nick} H {hostmask} * {servername}`` Match ``^:(?P\S+) 244 (?P\S+) H (?P\S+) . (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSHLINE) def myevent(bot, srv=None, me=None, hostmask=None, servername=None): # do something 215 - RPL_STATSILINE -------------------- Format ``:{srv} 215 {nick} I {host} * {host1} {port} {class}`` Match ``^:(?P\S+) 215 (?P\S+) I (?P\S+) . (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSILINE) def myevent(bot, srv=None, me=None, host=None, host1=None, port=None, class=None): # do something 216 - RPL_STATSKLINE -------------------- Format ``:{srv} 216 {nick} K {host} * {username} {port} {class}`` Match ``^:(?P\S+) 216 (?P\S+) K (?P\S+) . (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSKLINE) def myevent(bot, srv=None, me=None, host=None, username=None, port=None, class=None): # do something 211 - RPL_STATSLINKINFO ----------------------- Format ``:{srv} 211 {nick} :{linkname} {sendq} {sent_messages} {received_bytes} {time_open}`` Match ``^:(?P\S+) 211 (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSLINKINFO) def myevent(bot, srv=None, me=None, linkname=None, sendq=None, sent_messages=None, received_bytes=None, time_open=None): # do something 241 - RPL_STATSLLINE -------------------- Format ``:{srv} 241 {nick} L {hostmask} * {servername} {maxdepth}`` Match ``^:(?P\S+) 241 (?P\S+) L (?P\S+) . (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSLLINE) def myevent(bot, srv=None, me=None, hostmask=None, servername=None, maxdepth=None): # do something 214 - RPL_STATSNLINE -------------------- Format ``:{srv} 214 {nick} N {host} * {nick} {port} {class}`` Match ``^:(?P\S+) 214 (?P\S+) N (?P\S+) . (?P\S+) (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSNLINE) def myevent(bot, srv=None, me=None, host=None, nick=None, port=None, class=None): # do something 243 - RPL_STATSOLINE -------------------- Format ``:{srv} 243 {nick} O {hostmask} * {nick}`` Match ``^:(?P\S+) 243 (?P\S+) O (?P\S+) . (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSOLINE) def myevent(bot, srv=None, me=None, hostmask=None, nick=None): # do something 242 - RPL_STATSUPTIME --------------------- Format ``:{srv} 242 {nick} :Server Up{days}days {hours}`` Match ``^:(?P\S+) 242 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSUPTIME) def myevent(bot, srv=None, me=None, data=None): # do something 218 - RPL_STATSYLINE -------------------- Format ``:{srv} 218 {nick} frequency> {max_sendq}`` Match ``^:(?P\S+) 218 (?P\S+) frequency> (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_STATSYLINE) def myevent(bot, srv=None, me=None, max_sendq=None): # do something 342 - RPL_SUMMONING ------------------- Format ``:{srv} 342 {nick} {nick} :Summoning user to IRC`` Match ``^:(?P\S+) 342 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_SUMMONING) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 391 - RPL_TIME -------------- Format ``:{srv} 391 {nick} {server} :{string_showing_server's_local_time}`` Match ``^:(?P\S+) 391 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TIME) def myevent(bot, srv=None, me=None, server=None, data=None): # do something 332 - RPL_TOPIC --------------- Format ``:{srv} 332 {nick} {channel} :{topic}`` Match ``^:(?P\S+) 332 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TOPIC) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 201 - RPL_TRACECONNECTING ------------------------- Format ``:{srv} 201 {nick} Try. {class} {server}`` Match ``^:(?P\S+) 201 (?P\S+) Try. (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACECONNECTING) def myevent(bot, srv=None, me=None, class=None, server=None): # do something 202 - RPL_TRACEHANDSHAKE ------------------------ Format ``:{srv} 202 {nick} H.S. {class} {server}`` Match ``^:(?P\S+) 202 (?P\S+) H.S. (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACEHANDSHAKE) def myevent(bot, srv=None, me=None, class=None, server=None): # do something 200 - RPL_TRACELINK ------------------- Format ``:{srv} 200 {nick} {next_server}`` Match ``^:(?P\S+) 200 (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACELINK) def myevent(bot, srv=None, me=None, next_server=None): # do something 261 - RPL_TRACELOG ------------------ Format ``:{srv} 261 {nick} File {logfile} {debug_level}`` Match ``^:(?P\S+) 261 (?P\S+) File (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACELOG) def myevent(bot, srv=None, me=None, logfile=None, debug_level=None): # do something 208 - RPL_TRACENEWTYPE ---------------------- Format ``:{srv} 208 {nick} {newtype} 0 {client}`` Match ``^:(?P\S+) 208 (?P\S+) (?P\S+) 0 (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACENEWTYPE) def myevent(bot, srv=None, me=None, newtype=None, client=None): # do something 204 - RPL_TRACEOPERATOR ----------------------- Format ``:{srv} 204 {nick} Oper {class} {nick}`` Match ``^:(?P\S+) 204 (?P\S+) Oper (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACEOPERATOR) def myevent(bot, srv=None, me=None, class=None, nick=None): # do something 206 - RPL_TRACESERVER --------------------- Format ``:{srv} 206 {nick} {mask}`` Match ``^:(?P\S+) 206 (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACESERVER) def myevent(bot, srv=None, me=None, mask=None): # do something 203 - RPL_TRACEUNKNOWN ---------------------- Format ``:{srv} 203 {nick} ???? {class} [{clientip}]`` Match ``^:(?P\S+) 203 (?P\S+) \S+ (?P\S+) [(?P\S+)]`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACEUNKNOWN) def myevent(bot, srv=None, me=None, class=None, clientip=None): # do something 205 - RPL_TRACEUSER ------------------- Format ``:{srv} 205 {nick} User {class} {nick}`` Match ``^:(?P\S+) 205 (?P\S+) User (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_TRACEUSER) def myevent(bot, srv=None, me=None, class=None, nick=None): # do something 221 - RPL_UMODEIS ----------------- Format ``:{srv} 221 {nick} {user_mode_string}`` Match ``^:(?P\S+) 221 (?P\S+) (?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.RPL_UMODEIS) def myevent(bot, srv=None, me=None, user_mode_string=None): # do something 305 - RPL_UNAWAY ---------------- Format ``:{srv} 305 {nick} :You are no longer marked as being away`` Match ``^:(?P\S+) 305 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_UNAWAY) def myevent(bot, srv=None, me=None, data=None): # do something 302 - RPL_USERHOST ------------------ Format ``:{srv} 302 {nick} :[{reply}{{space}{reply}}]`` Match ``^:(?P\S+) 302 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_USERHOST) def myevent(bot, srv=None, me=None, data=None): # do something 393 - RPL_USERS --------------- Format ``:{srv} 393 {nick} {x} {y} {z}`` Match ``^:(?P\S+) 393 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_USERS) def myevent(bot, srv=None, me=None, data=None): # do something 392 - RPL_USERSSTART -------------------- Format ``:{srv} 392 {nick} :UserID Terminal Host`` Match ``^:(?P\S+) 392 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_USERSSTART) def myevent(bot, srv=None, me=None, data=None): # do something 351 - RPL_VERSION ----------------- Format ``:{srv} 351 {nick} {version}.{debuglevel} {server} :{comments}`` Match ``^:(?P\S+) 351 (?P\S+) (?P\S+).(?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_VERSION) def myevent(bot, srv=None, me=None, version=None, debuglevel=None, server=None, data=None): # do something 319 - RPL_WHOISCHANNELS ----------------------- Format ``:{srv} 319 {nick} :{channels}`` Match ``^:(?P\S+) 319 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOISCHANNELS) def myevent(bot, srv=None, me=None, data=None): # do something 317 - RPL_WHOISIDLE ------------------- Format ``:{srv} 317 {nick} {nick} {x} :seconds idle`` Match ``^:(?P\S+) 317 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOISIDLE) def myevent(bot, srv=None, me=None, nick=None, x=None, data=None): # do something 313 - RPL_WHOISOPERATOR ----------------------- Format ``:{srv} 313 {nick} {nick} :is an IRC operator`` Match ``^:(?P\S+) 313 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOISOPERATOR) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 312 - RPL_WHOISSERVER --------------------- Format ``:{srv} 312 {nick} {nick} {server} :{server_info}`` Match ``^:(?P\S+) 312 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOISSERVER) def myevent(bot, srv=None, me=None, nick=None, server=None, data=None): # do something 311 - RPL_WHOISUSER ------------------- Format ``:{srv} 311 {nick} {nick} {username} {host} {m} :{realname}`` Match ``^:(?P\S+) 311 (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOISUSER) def myevent(bot, srv=None, me=None, nick=None, username=None, host=None, m=None, data=None): # do something 352 - RPL_WHOREPLY ------------------ Format ``:{srv} 352 {nick} :{channel} {username} {host} {server} {nick} {modes} :{hopcount} {realname}`` Match ``^:(?P\S+) 352 (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOREPLY) def myevent(bot, srv=None, me=None, channel=None, username=None, host=None, server=None, nick=None, modes=None, data=None): # do something 314 - RPL_WHOWASUSER -------------------- Format ``:{srv} 314 {nick} {nick} {username} {host} * :{realname}`` Match ``^:(?P\S+) 314 (?P\S+) (?P\S+) (?P\S+) (?P\S+) . :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_WHOWASUSER) def myevent(bot, srv=None, me=None, nick=None, username=None, host=None, data=None): # do something 381 - RPL_YOUREOPER ------------------- Format ``:{srv} 381 {nick} :You are now an IRC operator`` Match ``^:(?P\S+) 381 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.RPL_YOUREOPER) def myevent(bot, srv=None, me=None, data=None): # do something Errors (ERR) ============ 462 - ERR_ALREADYREGISTRED -------------------------- Format ``:{srv} 462 {nick} :You may not reregister`` Match ``^:(?P\S+) 462 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_ALREADYREGISTRED) def myevent(bot, srv=None, me=None, data=None): # do something 475 - ERR_BADCHANNELKEY ----------------------- Format ``:{srv} 475 {nick} {channel} :Cannot join channel (+k)`` Match ``^:(?P\S+) 475 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_BADCHANNELKEY) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 474 - ERR_BANNEDFROMCHAN ------------------------ Format ``:{srv} 474 {nick} {channel} :Cannot join channel (+b)`` Match ``^:(?P\S+) 474 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_BANNEDFROMCHAN) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 404 - ERR_CANNOTSENDTOCHAN -------------------------- Format ``:{srv} 404 {nick} {channel} :Cannot send to channel`` Match ``^:(?P\S+) 404 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_CANNOTSENDTOCHAN) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 483 - ERR_CANTKILLSERVER ------------------------ Format ``:{srv} 483 {nick} :You cant kill a server!`` Match ``^:(?P\S+) 483 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_CANTKILLSERVER) def myevent(bot, srv=None, me=None, data=None): # do something 471 - ERR_CHANNELISFULL ----------------------- Format ``:{srv} 471 {nick} {channel} :Cannot join channel (+l)`` Match ``^:(?P\S+) 471 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_CHANNELISFULL) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 482 - ERR_CHANOPRIVSNEEDED -------------------------- Format ``:{srv} 482 {nick} {channel} :You're not channel operator`` Match ``^:(?P\S+) 482 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_CHANOPRIVSNEEDED) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 432 - ERR_ERRONEUSNICKNAME -------------------------- Format ``:{srv} 432 {nick} {nick} :Erroneus nickname`` Match ``^:(?P\S+) 432 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_ERRONEUSNICKNAME) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 473 - ERR_INVITEONLYCHAN ------------------------ Format ``:{srv} 473 {nick} {channel} :Cannot join channel (+i)`` Match ``^:(?P\S+) 473 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_INVITEONLYCHAN) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 467 - ERR_KEYSET ---------------- Format ``:{srv} 467 {nick} {channel} :Channel key already set`` Match ``^:(?P\S+) 467 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_KEYSET) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 461 - ERR_NEEDMOREPARAMS ------------------------ Format ``:{srv} 461 {nick} {cmd} :Not enough parameters`` Match ``^:(?P\S+) 461 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NEEDMOREPARAMS) def myevent(bot, srv=None, me=None, cmd=None, data=None): # do something ERR_NICK -------- Match ``^(@(?P\S+) )?:(?P\S+) (?P(432|433|436)) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NICK) def myevent(bot, srv=None, retcode=None, me=None, nick=None, data=None, tags=None): # do something 436 - ERR_NICKCOLLISION ----------------------- Format ``:{srv} 436 {nick} {nick} :Nickname collision KILL`` Match ``^:(?P\S+) 436 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NICKCOLLISION) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 433 - ERR_NICKNAMEINUSE ----------------------- Format ``:{srv} 433 {nick} {nick} :Nickname is already in use`` Match ``^:(?P\S+) 433 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NICKNAMEINUSE) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 423 - ERR_NOADMININFO --------------------- Format ``:{srv} 423 {nick} {server} :No administrative info available`` Match ``^:(?P\S+) 423 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOADMININFO) def myevent(bot, srv=None, me=None, server=None, data=None): # do something 444 - ERR_NOLOGIN ----------------- Format ``:{srv} 444 {nick} {nick} :User not logged in`` Match ``^:(?P\S+) 444 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOLOGIN) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 422 - ERR_NOMOTD ---------------- Format ``:{srv} 422 {nick} :MOTD File is missing`` Match ``^:(?P\S+) 422 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOMOTD) def myevent(bot, srv=None, me=None, data=None): # do something 431 - ERR_NONICKNAMEGIVEN ------------------------- Format ``:{srv} 431 {nick} :No nickname given`` Match ``^:(?P\S+) 431 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NONICKNAMEGIVEN) def myevent(bot, srv=None, me=None, data=None): # do something 491 - ERR_NOOPERHOST -------------------- Format ``:{srv} 491 {nick} :No O-lines for your host`` Match ``^:(?P\S+) 491 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOOPERHOST) def myevent(bot, srv=None, me=None, data=None): # do something 409 - ERR_NOORIGIN ------------------ Format ``:{srv} 409 {nick} :No origin specified`` Match ``^:(?P\S+) 409 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOORIGIN) def myevent(bot, srv=None, me=None, data=None): # do something 463 - ERR_NOPERMFORHOST ----------------------- Format ``:{srv} 463 {nick} :Your host isn't among the privileged`` Match ``^:(?P\S+) 463 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOPERMFORHOST) def myevent(bot, srv=None, me=None, data=None): # do something 481 - ERR_NOPRIVILEGES ---------------------- Format ``:{srv} 481 {nick} :Permission Denied- You're not an IRC operator`` Match ``^:(?P\S+) 481 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOPRIVILEGES) def myevent(bot, srv=None, me=None, data=None): # do something 411 - ERR_NORECIPIENT --------------------- Format ``:{srv} 411 {nick} :No recipient given ({cmd})`` Match ``^:(?P\S+) 411 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NORECIPIENT) def myevent(bot, srv=None, me=None, data=None): # do something 403 - ERR_NOSUCHCHANNEL ----------------------- Format ``:{srv} 403 {nick} {channel} :No such channel`` Match ``^:(?P\S+) 403 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOSUCHCHANNEL) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 401 - ERR_NOSUCHNICK -------------------- Format ``:{srv} 401 {nick} {nick} :No such nick/channel`` Match ``^:(?P\S+) 401 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOSUCHNICK) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 402 - ERR_NOSUCHSERVER ---------------------- Format ``:{srv} 402 {nick} {server} :No such server`` Match ``^:(?P\S+) 402 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOSUCHSERVER) def myevent(bot, srv=None, me=None, server=None, data=None): # do something 412 - ERR_NOTEXTTOSEND ---------------------- Format ``:{srv} 412 {nick} :No text to send`` Match ``^:(?P\S+) 412 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOTEXTTOSEND) def myevent(bot, srv=None, me=None, data=None): # do something 442 - ERR_NOTONCHANNEL ---------------------- Format ``:{srv} 442 {nick} {channel} :You're not on that channel`` Match ``^:(?P\S+) 442 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOTONCHANNEL) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 413 - ERR_NOTOPLEVEL -------------------- Format ``:{srv} 413 {nick} {mask} :No toplevel domain specified`` Match ``^:(?P\S+) 413 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOTOPLEVEL) def myevent(bot, srv=None, me=None, mask=None, data=None): # do something 451 - ERR_NOTREGISTERED ----------------------- Format ``:{srv} 451 {nick} :You have not registered`` Match ``^:(?P\S+) 451 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_NOTREGISTERED) def myevent(bot, srv=None, me=None, data=None): # do something 464 - ERR_PASSWDMISMATCH ------------------------ Format ``:{srv} 464 {nick} :Password incorrect`` Match ``^:(?P\S+) 464 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_PASSWDMISMATCH) def myevent(bot, srv=None, me=None, data=None): # do something 445 - ERR_SUMMONDISABLED ------------------------ Format ``:{srv} 445 {nick} :SUMMON has been disabled`` Match ``^:(?P\S+) 445 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_SUMMONDISABLED) def myevent(bot, srv=None, me=None, data=None): # do something 405 - ERR_TOOMANYCHANNELS ------------------------- Format ``:{srv} 405 {nick} {channel} :You have joined too many channels`` Match ``^:(?P\S+) 405 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_TOOMANYCHANNELS) def myevent(bot, srv=None, me=None, channel=None, data=None): # do something 407 - ERR_TOOMANYTARGETS ------------------------ Format ``:{srv} 407 {nick} {target} :Duplicate recipients. No message delivered`` Match ``^:(?P\S+) 407 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_TOOMANYTARGETS) def myevent(bot, srv=None, me=None, target=None, data=None): # do something 501 - ERR_UMODEUNKNOWNFLAG -------------------------- Format ``:{srv} 501 {nick} :Unknown MODE flag`` Match ``^:(?P\S+) 501 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_UMODEUNKNOWNFLAG) def myevent(bot, srv=None, me=None, data=None): # do something 421 - ERR_UNKNOWNCOMMAND ------------------------ Format ``:{srv} 421 {nick} {cmd} :Unknown command`` Match ``^:(?P\S+) 421 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_UNKNOWNCOMMAND) def myevent(bot, srv=None, me=None, cmd=None, data=None): # do something 472 - ERR_UNKNOWNMODE --------------------- Format ``:{srv} 472 {nick} {char} :is unknown mode char to me`` Match ``^:(?P\S+) 472 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_UNKNOWNMODE) def myevent(bot, srv=None, me=None, char=None, data=None): # do something 441 - ERR_USERNOTINCHANNEL -------------------------- Format ``:{srv} 441 {nick} {nick} {channel} :They aren't on that channel`` Match ``^:(?P\S+) 441 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_USERNOTINCHANNEL) def myevent(bot, srv=None, me=None, nick=None, channel=None, data=None): # do something 443 - ERR_USERONCHANNEL ----------------------- Format ``:{srv} 443 {nick} {nick} {channel} :is already on channel`` Match ``^:(?P\S+) 443 (?P\S+) (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_USERONCHANNEL) def myevent(bot, srv=None, me=None, nick=None, channel=None, data=None): # do something 446 - ERR_USERSDISABLED ----------------------- Format ``:{srv} 446 {nick} :USERS has been disabled`` Match ``^:(?P\S+) 446 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_USERSDISABLED) def myevent(bot, srv=None, me=None, data=None): # do something 502 - ERR_USERSDONTMATCH ------------------------ Format ``:{srv} 502 {nick} :Cant change mode for other users`` Match ``^:(?P\S+) 502 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_USERSDONTMATCH) def myevent(bot, srv=None, me=None, data=None): # do something 406 - ERR_WASNOSUCHNICK ----------------------- Format ``:{srv} 406 {nick} {nick} :There was no such nickname`` Match ``^:(?P\S+) 406 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_WASNOSUCHNICK) def myevent(bot, srv=None, me=None, nick=None, data=None): # do something 414 - ERR_WILDTOPLEVEL ---------------------- Format ``:{srv} 414 {nick} {mask} :Wildcard in toplevel domain`` Match ``^:(?P\S+) 414 (?P\S+) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_WILDTOPLEVEL) def myevent(bot, srv=None, me=None, mask=None, data=None): # do something 465 - ERR_YOUREBANNEDCREEP -------------------------- Format ``:{srv} 465 {nick} :You are banned from this server`` Match ``^:(?P\S+) 465 (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.ERR_YOUREBANNEDCREEP) def myevent(bot, srv=None, me=None, data=None): # do something Misc ==== CONNECTED --------- Match ``^:(?P\S+) (376|422) (?P\S+) :(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.CONNECTED) def myevent(bot, srv=None, me=None, data=None): # do something CTCP ---- Match ``^(@(?P\S+) )?:(?P\S+!\S+@\S+) (?P(PRIVMSG|NOTICE)) {nick} :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.CTCP) def myevent(bot, mask=None, event=None, ctcp=None, tags=None): # do something Out Match ``^(?P(PRIVMSG|NOTICE)) (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.CTCP, iotype="out") def myevent(bot, event=None, target=None, ctcp=None): # do something INVITE ------ Match ``^(@(?P\S+) )?:(?P\S+!\S+@\S+) INVITE {nick} :?(?P\S+)$`` Example: .. code-block:: python @irc3.event(rfc.INVITE) def myevent(bot, mask=None, channel=None, tags=None): # do something JOIN ---- Match ``^(@(?P\S+) )?:(?P\S+) JOIN :?(?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.JOIN) def myevent(bot, mask=None, channel=None, tags=None): # do something Out Match ``^JOIN :?(?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.JOIN, iotype="out") def myevent(bot, channel=None): # do something JOIN_PART_QUIT -------------- Match ``^(@(?P\S+) )?:(?P\S+) (?PJOIN|PART|QUIT)\s*:*(?P\S*)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.JOIN_PART_QUIT) def myevent(bot, mask=None, event=None, channel=None, data=None, tags=None): # do something Out Match ``^(?PJOIN|PART|QUIT)\s*:*(?P\S*)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.JOIN_PART_QUIT, iotype="out") def myevent(bot, event=None, channel=None, data=None): # do something KICK ---- Match ``^(@(?P\S+) )?:(?P\S+) (?PKICK)\s+(?P\S+)\s*(?P\S+)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.KICK) def myevent(bot, mask=None, event=None, channel=None, target=None, data=None, tags=None): # do something Out Match ``^(?PKICK)\s+(?P\S+)\s*(?P\S+)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.KICK, iotype="out") def myevent(bot, event=None, channel=None, target=None, data=None): # do something MODE ---- Match ``^(@(?P\S+) )?:(?P\S+) (?PMODE)\s+(?P\S+)\s+(?P\S+)(\s+(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.MODE) def myevent(bot, mask=None, event=None, target=None, modes=None, data=None, tags=None): # do something Out Match ``^(?PMODE)\s+(?P\S+)\s+(?P\S+)(\s+(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.MODE, iotype="out") def myevent(bot, event=None, target=None, modes=None, data=None): # do something MY_PRIVMSG ---------- Match ``^(@(?P\S+) )?:(?P\S+!\S+@\S+) (?P(PRIVMSG|NOTICE)) (?P(#\S+|{nick})) :{nick}[:,\s]\s*(?P\S+.*)$`` Example: .. code-block:: python @irc3.event(rfc.MY_PRIVMSG) def myevent(bot, mask=None, event=None, target=None, data=None, tags=None): # do something Out Match ``^(?P(PRIVMSG|NOTICE)) (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.MY_PRIVMSG, iotype="out") def myevent(bot, event=None, target=None, data=None): # do something NEW_NICK -------- Match ``^(@(?P\S+) )?:(?P\S+) NICK :?(?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.NEW_NICK) def myevent(bot, nick=None, new_nick=None, tags=None): # do something Out Match ``^NICK :?(?P\S+)`` Example: .. code-block:: python @irc3.event(rfc.NEW_NICK, iotype="out") def myevent(bot, new_nick=None): # do something PART ---- Match ``^(@(?P\S+) )?:(?P\S+) PART (?P\S+)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.PART) def myevent(bot, mask=None, channel=None, data=None, tags=None): # do something Out Match ``PART (?P\S+)(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.PART, iotype="out") def myevent(bot, channel=None, data=None): # do something PING ---- Match ``^PING :?(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.PING) def myevent(bot, data=None): # do something PONG ---- Match ``^(@(?P\S+) )?:(?P\S+) PONG (?P=server) :?(?P.*)`` Example: .. code-block:: python @irc3.event(rfc.PONG) def myevent(bot, server=None, data=None, tags=None): # do something PRIVMSG ------- Match ``^(@(?P\S+) )?:(?P\S+!\S+@\S+) (?P(PRIVMSG|NOTICE)) (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.PRIVMSG) def myevent(bot, mask=None, event=None, target=None, data=None, tags=None): # do something Out Match ``^(?P(PRIVMSG|NOTICE)) (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.PRIVMSG, iotype="out") def myevent(bot, event=None, target=None, data=None): # do something QUIT ---- Match ``^(@(?P\S+) )?:(?P\S+) QUIT(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.QUIT) def myevent(bot, mask=None, data=None, tags=None): # do something Out Match ``^QUIT(\s+:(?P.*)|$)`` Example: .. code-block:: python @irc3.event(rfc.QUIT, iotype="out") def myevent(bot, data=None): # do something TOPIC ----- Match ``^(@(?P\S+) )?:(?P\S+!\S+@\S+) TOPIC (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.TOPIC) def myevent(bot, mask=None, channel=None, data=None, tags=None): # do something Out Match ``^TOPIC (?P\S+) :(?P.*)$`` Example: .. code-block:: python @irc3.event(rfc.TOPIC, iotype="out") def myevent(bot, channel=None, data=None): # do something ================================================ FILE: docs/utils.rst ================================================ ======================== :mod:`irc3.utils` Utils ======================== .. automodule:: irc3.utils .. autoclass:: IrcString :members: .. autofunction:: as_list .. autofunction:: as_channel .. autofunction:: split_message .. autoclass:: Logger :members: .. autoclass:: Config .. autofunction:: parse_config .. autofunction:: extract_config .. autofunction:: maybedotted ================================================ FILE: examples/async_command.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.command import command from irc3.compat import Queue import irc3 @irc3.plugin class AsyncCommands(object): """Async commands example. This is what it's look like on irc:: !get !put item items added to queue item """ def __init__(self, bot): self.bot = bot self.queue = Queue() @command def put(self, mask, target, args): """Put items in queue %%put ... """ for w in args['']: self.queue.put_nowait(w) yield 'items added to queue' @command async def get(self, mask, target, args): """Async get items from the queue %%get """ messages = [] message = await self.queue.get() messages.append(message) while not self.queue.empty(): message = await self.queue.get() messages.append(message) return messages ================================================ FILE: examples/benches.ini ================================================ [bot] nick = malkovitch username = malkovitch host = localhost port = 6667 name = malkovitch user = malkovitch realname = I'm the real John Malkovitch channel = head includes = benches [server] servername = malkovitch client_amount = 500 includes = irc3d.plugins.core [opers] gawel=passwd ================================================ FILE: examples/benches.py ================================================ # -*- coding: utf-8 -*- import random from irc3.compat import asyncio from irc3d import IrcServer import irc3 @irc3.plugin class Plugin(object): def __init__(self, context): self.log = context.log self.context = context self.name = context.config.name self.channel = '#' + context.config.channel @irc3.event(irc3.rfc.CONNECTED) def connected(self, **kw): self.context.join(self.channel) def msg(self): s = ' '.join([self.name for i in range(random.randint(1, 10))]) s += random.choice([' ?', '', ' !', ' #@!', ' ']) self.context.privmsg(self.channel, s) self.context.loop.call_later( random.randint(3, 20), self.msg) @irc3.event(irc3.rfc.JOIN) def join(self, mask=None, **kw): if mask.nick == self.context.nick: self.context.loop.call_later( random.randint(3, 20), self.msg) def main(): loop = asyncio.get_event_loop() server = IrcServer.from_argv(loop=loop) bot = irc3.IrcBot.from_argv(loop=loop) def factory(i): irc3.IrcBot.from_argv( loop=loop, i=i, nick=bot.nick + str(i), realname=bot.config.realname + str(i), ) for i in range(1, server.config.client_amount): loop.call_later(.1 * i, factory, i) loop.run_forever() ================================================ FILE: examples/bot.ini ================================================ [bot] nick = irc3 username = irc3 host = irc.freenode.net port = 7000 ssl = true includes = irc3.plugins.core irc3.plugins.ctcp irc3.plugins.cron irc3.plugins.autojoins irc3.plugins.userlist irc3.plugins.command irc3.plugins.human irc3.plugins.search irc3.plugins.uptime irc3.plugins.feeds irc3.plugins.social irc3.plugins.fifo freenode_irc3 nickserv # channels to join autojoins = irc3 [irc3.plugins.command] # command plugin configuration # set command char cmd = ! # enable antiflood on commands antiflood = true # set guard policy guard = irc3.plugins.command.mask_based_policy [irc3.plugins.command.masks] *!~gael@amandine.bearstech.com = all_permissions [irc3.plugins.feeds] # feeds plugin configuration directory = ~/.irc3/feeds/ hook = freenode_irc3.FeedsHook channels = irc3 delay = 5 github/irc3 = https://github.com/gawel/irc3/commits/master.atom github/irc3.fmt = [{feed.name}] New commit by {entry.author}: {entry.title} - {entry.link} github/irc3.delay = 5 travis/irc3 = https://api.travis-ci.org/repos/gawel/irc3/builds.atom travis/irc3.fmt = [{feed.name}] {entry.title} - {entry.link} travis/irc3.channels = irc3 travis/irc3.delay = 10 pypi/irc3 = https://pypi.python.org/pypi?:action=rss pypi/irc3.fmt = {entry.title} is out! - {entry.link} pypi/irc3.delay = 60 [irc3.plugins.fifo] runpath = /tmp/run/irc3 ================================================ FILE: examples/commands.rst ================================================ =============================================== Available Commands for irc3 at irc.freenode.net =============================================== .. contents:: irc3.plugins.command ==================== help ---- Show help ``!help []`` ping ---- ping/pong ``!ping`` *Require admin permission.* *Only available in private.* irc3.plugins.search =================== ddg --- Search using https://duckduckgo.com/api ``!ddg ...`` *Require view permission.* irc3.plugins.social =================== retweet ------- Retweet ``!retweet [--id=] `` *Require edit permission.* tweet ----- Post to social networks ``!tweet [--id=] ...`` *Require edit permission.* irc3.plugins.uptime =================== uptime ------ Show uptimes ``!uptime`` *Require view permission.* ================================================ FILE: examples/config.ini ================================================ [bot] nick = mybot username = mybot host = localhost port = 6667 # uncomment this if you want ssl support # ssl = true # uncomment this if you don't want to check the certificate # ssl_verify = CERT_NONE # uncomment this if you want to use sasl authentication # sasl_username = mybot # sasl_password = yourpassword includes = irc3.plugins.command # irc3.plugins.uptime # irc3.plugins.ctcp mybot_plugin # the bot will join #mybot_channel # ${#} is replaced by the # char autojoins = ${#}mybot_channel # Autojoin delay, disabled by default # float or int value # autojoin_delay = 3.1 # The maximum amount of lines irc3 sends at once. # Default to 4, set to 0 to disable # flood_burst = 10 # The number of lines per $flood_rate_delay seconds irc3 sends after reaching # the $flood_burst limit. # Default to 1 # flood_rate = 2 # The bot will send $flood_rate messages per $flood_rate_delay seconds # Default to 1 # flood_rate_delay = 5 [irc3.plugins.command] # command plugin configuration # set command char cmd = ! # set guard policy guard = irc3.plugins.command.mask_based_policy [irc3.plugins.command.masks] # this section is used by the guard to secure the bot's command # change your nickname and uncomment the line below # mynick!*@* = all_permissions * = view ================================================ FILE: examples/dcc_chat.py ================================================ # -*- coding: utf-8 -*- from irc3.compat import asyncio from irc3d import IrcServer import irc3 @irc3.plugin class Plugin(object): def __init__(self, context): self.log = context.log self.context = context @irc3.event(irc3.rfc.CONNECTED) def connected(self, **kw): self.context.join('#dcc') @irc3.event(irc3.rfc.JOIN) async def join(self, mask=None, **kw): if mask.nick != self.context.nick and mask.nick == 'receiver': # receiver joined the chan. offer a chat conn = await self.context.dcc_chat(mask) # wait for my buddy await conn.started # say hi conn.send_line('Hi!') await conn.closed self.context.log.info('chat with %s closed', mask.nick) @irc3.event(irc3.rfc.CTCP) async def on_ctcp(self, mask=None, **kwargs): # parse ctcp message print(kwargs) host, port = kwargs['ctcp'].split()[3:] self.context.log.info('%s is offering a chat', mask.nick) # open the chat conn = await self.context.dcc_chat(mask, host, port) conn.send_line('youhou') # end the loop after a few seconds self.context.loop.call_later(1, self.context.config.end_chat.set_result, True) await conn.closed self.context.log.info('chat with %s closed', mask.nick) @irc3.dcc_event(r'(?P.*)') def on_dcc(self, client=None, data=None): """event to catch everything in dcc chats""" self.context.log.info('%r sent %s', client, data) def main(): loop = asyncio.get_event_loop() # run a test server server = IrcServer.from_config(dict( loop=loop, servername='test', includes=['irc3d.plugins.core'], )) server.run(forever=False) end_chat = asyncio.Future() cfg = dict( host='localhost', port=6667, nick='sender', includes=['irc3.plugins.dcc', __name__], loop=loop, end_chat=end_chat, ) # this bot will send the file sender = irc3.IrcBot.from_config(cfg) sender.run(forever=False) def f(): # this bot will receive the file receiver.run(forever=False) # assume receiver is created *after* sender receiver = irc3.IrcBot.from_config(cfg, nick='receiver') loop.call_later(.2, receiver.run, False) loop.run_until_complete(end_chat) if __name__ == '__main__': main() ================================================ FILE: examples/dcc_send.py ================================================ # -*- coding: utf-8 -*- import os import string import tempfile import irc3 from irc3.plugins.command import command @irc3.plugin class DCC(object): filename = os.path.join(tempfile.gettempdir(), 'to_send') def __init__(self, bot): self.bot = bot if not os.path.isfile(self.filename): # create a file to send with open(self.filename, 'wb') as fd: for i in range(64 * 2048): fd.write(string.ascii_letters.encode('utf8')) @command async def send(self, mask, target, args): """ DCC SEND command %%send """ conn = await self.bot.dcc_send(mask, self.filename) self.bot.log.debug('%s ready', conn) ================================================ FILE: examples/dcc_send_and_get.py ================================================ # -*- coding: utf-8 -*- from irc3.compat import asyncio from irc3d import IrcServer import irc3 @irc3.plugin class Plugin(object): def __init__(self, context): self.log = context.log self.context = context @irc3.event(irc3.rfc.CONNECTED) def connected(self, **kw): self.context.join('#dcc') @irc3.event(irc3.rfc.JOIN) async def join(self, mask=None, **kw): if mask.nick != self.context.nick and mask.nick == 'receiver': # receiver joined the chan. offer a file conn = await self.context.dcc_send(mask, __file__) await conn.closed self.context.log.info('file sent to %s', mask.nick) @irc3.event(irc3.rfc.CTCP) async def on_ctcp(self, mask=None, **kwargs): # parse ctcp message name, host, port, size = kwargs['ctcp'].split()[2:] self.context.log.info('%s is offering %s', mask.nick, name) # get the file conn = await self.context.create_task(self.context.dcc_get( mask, host, port, '/tmp/sent.py', int(size))) await conn.closed self.context.log.info('file received from %s', mask.nick) # end loop by setting future's result self.context.config.file_received.set_result(True) def main(): loop = asyncio.get_event_loop() # run a test server server = IrcServer.from_config(dict( loop=loop, servername='test', includes=['irc3d.plugins.core'], )) server.run(forever=False) cfg = dict( host='localhost', port=6667, nick='sender', includes=[__name__], loop=loop, ) # this bot will send the file sender = irc3.IrcBot.from_config(cfg) sender.run(forever=False) file_received = asyncio.Future() def f(): # this bot will receive the file receiver.run(forever=False) # assume receiver is created *after* sender receiver = irc3.IrcBot.from_config(cfg, nick='receiver', file_received=file_received) loop.call_later(.2, receiver.run, False) loop.run_until_complete(file_received) if __name__ == '__main__': main() ================================================ FILE: examples/dev.ini ================================================ [bot] nick = irc3_dev username = irc3 host = irc.freenode.net port = 7000 ssl = true includes = irc3.plugins.core irc3.plugins.userlist irc3.plugins.ctcp irc3.plugins.autojoins irc3.plugins.log irc3.plugins.command irc3.plugins.human irc3.plugins.search irc3.plugins.uptime irc3.plugins.cron irc3.plugins.social irc3.plugins.feeds irc3.plugins.fifo freenode_irc3 paginate topic mybot # nickserv cmd = . autojoins = #irc3_dev [irc3.plugins.command] antiflood = true guard = irc3.plugins.command.mask_based_policy [irc3.plugins.command.masks] gawel!*@*=all_permissions [irc3.plugins.feeds] delay = 1 directory = ~/.irc3/feeds/ hook = freenode_irc3.FeedsHook channels = irc3_dev github/irc3 = https://github.com/gawel/irc3/commits/master.atom github/irc3.fmt = [{feed.name}] New commit by {entry.author}: {entry.title} - {entry.link} github/irc3.delay = 1 travis/irc3 = https://api.travis-ci.org/repos/gawel/irc3/builds.atom travis/irc3.fmt = [{feed.name}] {entry.title} - {entry.link} travis/irc3.channels = #irc3-dev travis/irc3.delay = 1 pypi/irc3 = https://pypi.python.org/pypi?:action=rss pypi/irc3.fmt = {entry.title} is out! - {entry.link} pypi/irc3.delay = 3 [irc3.plugins.fifo] runpath = /tmp/run/irc3 ================================================ FILE: examples/freenode_irc3.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.cron import cron import os class FeedsHook(object): """Custom hook for irc3.plugins.feeds""" def __init__(self, bot): self.bot = bot self.packages = [ 'asyncio', 'irc3', 'panoramisk', 'requests', 'trollius', 'webtest', 'pyramid', ] def filter_travis(self, entry): """Only show the latest entry iif this entry is in a new state""" fstate = entry.filename + '.state' if os.path.isfile(fstate): with open(fstate) as fd: state = fd.read().strip() else: state = None if 'failed' in entry.summary: nstate = 'failed' else: nstate = 'success' with open(fstate, 'w') as fd: fd.write(nstate) if state != nstate: build = entry.title.split('#')[1] entry['title'] = 'Build #{0} {1}'.format(build, nstate) return True def filter_pypi(self, entry): """Show only usefull packages""" for package in self.packages: if entry.title.lower().startswith(package): return entry def __call__(self, entries): travis = {} for entry in entries: if entry.feed.name.startswith('travis/'): travis[entry.feed.name] = entry elif entry.feed.name.startswith('pypi/'): yield self.filter_pypi(entry) else: yield entry for entry in travis.values(): if self.filter_travis(entry): yield entry @cron('*/15 * * * *') def auto_retweet(bot): """retweet author tweets about irc3 and pypi releases""" conn = bot.get_social_connection(id='twitter') dirname = os.path.expanduser('~/.irc3/twitter/{nick}'.format(**bot.config)) if not os.path.isdir(dirname): os.makedirs(dirname) filename = os.path.join(dirname, 'retweeted') if os.path.isfile(filename): with open(filename) as fd: retweeted = [i.strip() for i in fd.readlines()] else: retweeted = [] for user in ('pypi', 'gawel_'): results = conn.search.tweets( q=user + ' AND irc3', result_type='recent') for item in results.get('statuses', []): if item['user']['screen_name'] == user: if item['id_str'] not in retweeted: res = conn(getattr(conn.statuses.retweet, item['id_str'])) if 'id' in res: with open(filename, 'a+') as fd: fd.write(item['id_str'] + '\n') @cron('*/2 * * * *', venusian_category='irc3.debug') def test_cron(bot): bot.log.info('Running test_cron') @cron('*/3 * * * *', venusian_category='irc3.debug') def test_cron_raise(bot): raise OSError('test_cron_raise') ================================================ FILE: examples/humans.py ================================================ # -*- coding: utf-8 -*- import asyncio import irc3 @irc3.event(irc3.rfc.JOIN) def greetings(bot, mask=None, channel=None, **kw): if not mask.nick.startswith(bot.nick): bot.privmsg(channel, '%s: Hi dude!' % mask.nick) def main(): loop = asyncio.get_event_loop() config = dict( autojoins=['#irc3'], host='irc.freenode.net', port=7000, ssl=True, timeout=30, includes=[ 'irc3.plugins.core', 'irc3.plugins.human', __name__, # this register this module ], loop=loop) # instanciate two bot irc3.IrcBot(nick='bobirc', **config).run(forever=False) irc3.IrcBot(nick='jackyrc', **config).run(forever=False) loop.run_forever() if __name__ == '__main__': main() ================================================ FILE: examples/mybot.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.command import command import irc3 @irc3.plugin class MyPlugin: """A plugin is a class which take the IrcBot as argument """ requires = [ 'irc3.plugins.core', 'irc3.plugins.userlist', 'irc3.plugins.command', 'irc3.plugins.human', ] def __init__(self, bot): self.bot = bot self.log = self.bot.log def connection_made(self): """triggered when connection is up""" def server_ready(self): """triggered after the server sent the MOTD (require core plugin)""" def connection_lost(self): """triggered when connection is lost""" @irc3.event(irc3.rfc.JOIN) def welcome(self, mask, channel, **kw): """Welcome people who join a channel""" if mask.nick != self.bot.nick: self.bot.call_with_human_delay( self.bot.privmsg, channel, 'Welcome %s!' % mask.nick) else: self.bot.call_with_human_delay( self.bot.privmsg, channel, "Hi guys!") @command def echo(self, mask, target, args): """Echo command %%echo ... """ self.bot.privmsg(mask.nick, ' '.join(args[''])) @command def stats(self, mask, target, args): """Show stats of the channel using the userlist plugin %%stats [] """ if args['']: channel = args[''] target = mask.nick else: channel = target if channel in self.bot.channels: channel = self.bot.channels[channel] message = '{0} users'.format(len(channel)) for mode, nicknames in sorted(channel.modes.items()): message += ' - {0}({1})'.format(mode, len(nicknames)) self.bot.privmsg(target, message) @irc3.extend def my_usefull_method(self): """The extend decorator will allow you to call:: bot.my_usefull_method() """ def main(): # instanciate a bot config = dict( nick='irc3', autojoins=['#irc3'], host='irc.undernet.org', port=6667, ssl=False, includes=[ 'irc3.plugins.core', 'irc3.plugins.command', 'irc3.plugins.human', __name__, # this register MyPlugin ] ) bot = irc3.IrcBot.from_config(config) bot.run(forever=True) if __name__ == '__main__': main() ================================================ FILE: examples/mybot_plugin.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.command import command import irc3 @irc3.plugin class Plugin: def __init__(self, bot): self.bot = bot @irc3.event(irc3.rfc.JOIN) def say_hi(self, mask, channel, **kw): """Say hi when someone join a channel""" if mask.nick != self.bot.nick: self.bot.privmsg(channel, 'Hi %s!' % mask.nick) else: self.bot.privmsg(channel, 'Hi!') @command(permission='view') def echo(self, mask, target, args): """Echo %%echo ... """ yield ' '.join(args['']) ================================================ FILE: examples/mycommands.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.command import command @command def echo(bot, mask, target, args): """Echo command %%echo ... """ yield ' '.join(args['']) @command(permission='admin', public=False) def adduser(bot, mask, target, args): """Add a user %%adduser """ bot.privmsg(mask.nick, 'User added') @command(show_in_help_list=False) def my_secret_operation(bot, mask, target, args): """Do something you don't want in !help all the time %%my_secret_operation """ yield "I like turtles" ================================================ FILE: examples/mycrons.py ================================================ # -*- coding: utf-8 -*- from irc3.plugins.cron import cron @cron('30 8 * * *') def wakeup(bot): bot.privmsg('#irc3', "It's time to wake up!") @cron('0 */2 * * *') def take_a_break(bot): bot.privmsg('#irc3', "It's time to take a break!") ================================================ FILE: examples/myextends.py ================================================ # -*- coding: utf-8 -*- import irc3 @irc3.extend def my_usefull_function(bot, *args): return 'my_usefull_function(*%s)' % (args,) @irc3.plugin class MyPlugin(object): def __init__(self, bot): self.bot = bot @irc3.extend def my_usefull_method(self, *args): return 'my_usefull_method(*%s)' % (args,) ================================================ FILE: examples/nickserv.py ================================================ # -*- coding: utf-8 -*- import irc3 @irc3.event(r'(@(?P\S+) )?:(?PNickServ)!NickServ@services.' r' NOTICE (?Pirc3) :This nickname is registered.*') def register(bot, ns=None, nick=None, **kw): try: password = bot.config[bot.config.host][nick] except KeyError: pass else: bot.privmsg(ns, 'identify %s %s' % (nick, password)) ================================================ FILE: examples/paginate.py ================================================ # -*- coding: utf-8 -*- import irc3 import requests from irc3.plugins.command import command @irc3.plugin class SendFile(object): requires = [ 'irc3.plugins.command', 'irc3.plugins.pager', ] def __init__(self, bot): self.bot = bot @command def cat(self, mask, target, args): """Cat a file with pagination %%cat """ fd = open(__file__) for msg in self.bot.paginate(mask, fd, lines_per_page=10): yield msg @command def url(self, mask, target, args): """Cat an url with pagination %%url """ def iterator(url): resp = requests.get(url) for chunk in resp.iter_content(255): yield chunk.decode('utf8') for msg in self.bot.paginate(mask, iterator(args[''])): yield msg ================================================ FILE: examples/proxy.py ================================================ # -*- coding: utf-8 -*- import socks import irc3 def sock_factory(bot, host, port): sock = socks.socksocket() sock.set_proxy(socks.SOCKS5, "localhost", 6969) sock.connect((host, 6667)) return sock def main(): bot = irc3.IrcBot.from_argv(sock_factory=sock_factory) bot.run() if __name__ == '__main__': main() ================================================ FILE: examples/slack.ini ================================================ [bot] nick = host = chat.freenode.net port = 6697 ssl = true sasl_username = sasl_password = includes = irc3.plugins.slack [irc3.plugins.slack] token = [irc3.plugins.slack.channels] = ${#} ${#} = ${#} ================================================ FILE: examples/spy.ini ================================================ [bot] nick = spyer username = spyer host = localhost port = 6667 channel = gov includes = spy [bot_chater] nick = chater username = chater channel = irc3 [server] servername = spy ================================================ FILE: examples/spy.py ================================================ # -*- coding: utf-8 -*- import irc3 @irc3.plugin class Plugin(object): chater = None def __init__(self, context): self.log = context.log self.context = context self.channel = context.config.channel @irc3.event(irc3.rfc.CONNECTED) def connected(self, **kw): chater = self.context.config.botnet['bot_chater'] if chater is self.context: self.chater = None self.log.info("I'm a chater") else: self.chater = chater self.log.info("I'm a spyer") self.context.join(self.channel) @irc3.event(irc3.rfc.PRIVMSG) def on_privmsg(self, mask=None, data=None, **kw): print(self.chater, mask, data) if self.chater: self.chater.privmsg(self.chater.config.channel, '{0}: {1}'.format(mask.nick, data)) ================================================ FILE: examples/topic.py ================================================ # -*- coding: utf-8 -*- import irc3 from irc3.plugins.command import command from irc3.plugins.cron import cron @irc3.plugin class TopicPlugin: requires = ['irc3.plugins.async'] def __init__(self, bot): self.bot = bot @irc3.event(irc3.rfc.TOPIC) @irc3.event(irc3.rfc.RPL_TOPIC) def get_topic(self, channel=None, data=None, **kwargs): """check the topic on join or on user action""" self.bot.log.warn('Topic for %s is %s', channel, data) @cron('* * * * *') async def cron_topic(self): """check the topic each minute""" result = await self.bot.async_cmds.topic('#irc3_dev') self.bot.log.warn('Topic for #irc3_dev is %(topic)s', result) @command def topic(self, mask, target, args): """Set topic %%topic ... """ if target.is_channel: self.bot.topic(target, ' '.join(args[''])) @command async def aiotopic(self, mask, target, args): """Set topic and get result the async way %%aiotopic [...] """ if target.is_channel: result = await self.bot.async_cmds.topic( target, ' '.join(args[''])) return result['topic'] ================================================ FILE: examples/wsgiapp.py ================================================ # -*- coding: utf-8 -*- import asyncio from aiohttp_wsgi import wsgi from irc3 import plugin import json @plugin class Webapp: requires = ['irc3.plugins.userlist'] def __init__(self, bot): def server(): return wsgi.WSGIServerHttpProtocol(self.wsgi) self.bot = bot loop = asyncio.get_event_loop() self.bot.log.info('Starting webapp') asyncio.Task(loop.create_server( server, '127.0.0.1', 5000)) def wsgi(self, environ, start_response): start_response('200 OK', [('Content-Type', 'application/json')]) plugin = self.bot.get_plugin('userlist') data = json.dumps(list(plugin.channels.keys())) return [data.encode('utf8')] ================================================ FILE: irc3/__init__.py ================================================ # -*- coding: utf-8 -*- from urllib.request import urlopen from ipaddress import ip_address from collections import deque from .dcc import DCCManager from .dcc import DCCChat from .dec import dcc_event from .dec import event from .dec import extend from .dec import plugin from . import config from . import utils from . import rfc from . import base from .compat import asyncio from .compat import Queue import venusian import time class IrcConnection(asyncio.Protocol): """asyncio protocol to handle an irc connection""" def connection_made(self, transport): self.transport = transport self.closed = False self.queue = deque() def decode(self, data): """Decode data with bot's encoding""" encoding = getattr(self, 'encoding', 'ascii') return data.decode(encoding, 'ignore') def data_received(self, data): data = self.decode(data) if self.queue: data = self.queue.popleft() + data lines = data.split('\r\n') self.queue.append(lines.pop(-1)) for line in lines: self.factory.dispatch(line) def write(self, data): if data is not None: data = data.encode(self.encoding) if not data.endswith(b'\r\n'): data = data + b'\r\n' self.transport.write(data) def connection_lost(self, exc): self.factory.log.critical('connection lost (%s): %r', id(self.transport), exc) self.factory.notify('connection_lost') if not self.closed: self.close() # wait a few before reconnect self.factory.loop.call_later( 2, self.factory.create_connection) def close(self): if not self.closed: self.factory.log.critical('closing old transport (%r)', id(self.transport)) try: self.transport.close() finally: self.closed = True class IrcBot(base.IrcObject): """An IRC bot""" _pep8 = [dcc_event, event, extend, plugin, rfc, config] venusian = venusian venusian_categories = [ 'irc3', 'irc3.dcc', 'irc3.extend', 'irc3.rfc1459', 'irc3.plugins.cron', 'irc3.plugins.command', ] logging_config = config.LOGGING defaults = dict( base.IrcObject.defaults, nick='irc3', username='irc3', realname='Irc bot based on irc3 http://irc3.readthedocs.io', host='localhost', mode=0, url='https://irc3.readthedocs.io/', passwords={}, flood_burst=4, flood_rate=1, flood_rate_delay=1, ctcp=dict( version='irc3 {version} - {url}', userinfo='{realname}', time='{now:%c}', ), # freenode config as default for testing server_config=dict( STATUSMSG='+@', PREFIX='(ov)@+', CHANTYPES='#', CHANMODES='eIbq,k,flj,CFLMPQScgimnprstz', ), connection=IrcConnection, ) def __init__(self, *ini, **config): update_config_needed = False if 'userinfo' in config or \ ('realname' in config and 'username' not in config): update_config_needed = True # pragma: no cover super(IrcBot, self).__init__(*ini, **config) if update_config_needed: # pragma: no cover # Backward compat. Remove me in 2017 self.log.fatal('realname has been renamed to username.') self.log.fatal('userinfo has been renamed to realname.') self.log.fatal('Please update your config with something like:.') if 'realname' in self.config: self.log.fatal('username = %(realname)s', self.config) if 'userinfo' in self.config: self.log.fatal('realname = %(userinfo)s', self.config) import sys sys.exit(-1) self.queue = None if self.config.asynchronous: self.queue = Queue(loop=self.loop) self.awaiting_queue = self.create_task(self.process_queue()) self._ip = self._dcc = None # auto include the sasl plugin if needed if 'sasl_username' in self.config and \ 'irc3.plugins.sasl' not in self.registry.includes: self.include('irc3.plugins.sasl') # auto include the autojoins plugin if needed (for backward compat) if 'autojoins' in self.config and \ 'irc3.plugins.autojoins' not in self.registry.includes: self.include('irc3.plugins.autojoins') @property def server_config(self): """return server configuration (rfc rpl 005):: >>> bot = IrcBot() >>> print(bot.server_config['STATUSMSG']) +@ The real values are only available after the server sent them. """ return self.config.server_config def connection_made(self, f): # pragma: no cover if getattr(self, 'protocol', None): self.protocol.close() try: transport, protocol = f.result() except Exception as e: self.log.exception(e) self.loop.call_later(3, self.create_connection) else: self.log.debug('Connected') self.protocol = protocol self.protocol.queue = deque() self.protocol.factory = self self.protocol.encoding = self.encoding if self.config.get('password'): self._send('PASS {password}'.format(**self.config)) self.notify('connection_ready') self.send(( 'USER {username} {mode} * :{realname}\r\n' 'NICK {nick}\r\n' ).format(**self.config)) self.notify('connection_made') def send_line(self, data, nowait=False): """send a line to the server. replace CR by spaces""" data = data.replace('\n', ' ').replace('\r', ' ') f = self.loop.create_future() if self.queue is not None and nowait is False: self.queue.put_nowait((f, data)) else: self.send(data.replace('\n', ' ').replace('\r', ' ')) f.set_result(True) return f async def process_queue(self): flood_burst = self.config.flood_burst delay = float(self.config.flood_rate_delay) flood_rate = delay / float(self.config.flood_rate) while True: if flood_burst == 0: future, data = await self.queue.get() future.set_result(True) self.send(data) await asyncio.sleep(.001, loop=self.loop) else: lines = [] for i in range(flood_burst): future, data = await self.queue.get() future.set_result(True) lines.append(data) if self.queue.empty(): break if lines: self.send('\r\n'.join(lines)) while not self.queue.empty(): await asyncio.sleep(flood_rate, loop=self.loop) future, data = await self.queue.get() future.set_result(True) self.send(data) def send(self, data): """send data to the server""" self._send(data) def _send(self, data): self.protocol.write(data) self.dispatch(data, iotype='out') def privmsg(self, target, message, nowait=False): """send a privmsg to target""" if message: is_dcc = isinstance(target, DCCChat) prefix = '' if is_dcc else 'PRIVMSG %s :' % target messages = utils.split_message( message, self.config.max_length, self.encoding, prefix=prefix, ) if is_dcc: for message in messages: target.send_line(message) elif target: f = None for message in messages: f = self.send_line(prefix + message, nowait=nowait) return f def action(self, target, message, nowait=False): return self.privmsg(target, '\x01ACTION %s\x01' % message, nowait=nowait) def notice(self, target, message, nowait=False): """send a notice to target""" if message: is_dcc = isinstance(target, DCCChat) prefix = '' if is_dcc else 'NOTICE %s :' % target messages = utils.split_message( message, self.config.max_length, self.encoding, prefix=prefix, ) if is_dcc: for message in messages: target.send_line(message) elif target: f = None for message in messages: f = self.send_line(prefix + message, nowait=nowait) return f def ctcp(self, target, message, nowait=False): """send a ctcp to target""" if target and message: messages = utils.split_message( message, self.config.max_length, self.encoding, ) f = None for message in messages: f = self.send_line('PRIVMSG %s :\x01%s\x01' % (target, message), nowait=nowait) return f def ctcp_reply(self, target, message, nowait=False): """send a ctcp reply to target""" if target and message: messages = utils.split_message( message, self.config.max_length, self.encoding, ) f = None for message in messages: f = self.send_line('NOTICE %s :\x01%s\x01' % (target, message), nowait=nowait) return f def mode(self, target, *data): """set user or channel mode""" self.send_line('MODE %s %s' % (target, ' '.join(data)), nowait=True) def join(self, target): """join a channel""" password = self.config.passwords.get( target.strip(self.server_config['CHANTYPES'])) if password: target += ' ' + password self.send_line('JOIN %s' % target) def part(self, target, reason=None): """quit a channel""" if reason: target += ' :' + reason self.send_line('PART %s' % target) def kick(self, channel, target, reason=None): """kick target from channel""" if reason: target += ' :' + reason self.send_line('KICK %s %s' % (channel, target), nowait=True) def invite(self, target, channel): """invite target to a channel""" self.send_line('INVITE %s %s' % (target, channel)) def topic(self, channel, topic=None): """change or request the topic of a channel""" if topic: channel += ' :' + topic self.send_line('TOPIC %s' % channel) def away(self, message=None): """mark ourself as away""" cmd = 'AWAY' if message: cmd += ' :' + message self.send_line(cmd) def unaway(self): """mask ourself as no longer away""" self.away() def quit(self, reason=None): """disconnect""" if not reason: reason = 'bye' else: reason = reason self.send_line('QUIT :%s' % reason) def get_nick(self): return self.config.nick def set_nick(self, nick): self.send_line('NICK ' + nick, nowait=True) nick = property(get_nick, set_nick, doc='nickname get/set') @property def ip(self): """return bot's ip as an ``ip_address`` object""" if not self._ip: if 'ip' in self.config: ip = self.config['ip'] else: ip = self.protocol.transport.get_extra_info('sockname')[0] ip = ip_address(ip) if ip.version == 4: self._ip = ip else: # pragma: no cover response = urlopen('http://ipv4.icanhazip.com/') ip = response.read().strip().decode() ip = ip_address(ip) self._ip = ip return self._ip @property def dcc(self): """return the :class:`~irc3.dcc.DCCManager`""" if self._dcc is None: self._dcc = DCCManager(self) return self._dcc async def dcc_chat(self, mask, host=None, port=None): """Open a DCC CHAT whith mask. If host/port are specified then connect to a server. Else create a server""" conn = self.dcc.create( 'chat', mask, host=host, port=port) await conn.ready return conn async def dcc_get(self, mask, host, port, filepath, filesize=None): """DCC GET a file from mask. filepath must be an absolute path with an existing directory. filesize is the expected file size.""" conn = self.dcc.create( 'get', mask, filepath=filepath, filesize=filesize, host=host, port=port) await conn.ready return conn async def dcc_send(self, mask, filepath): """DCC SEND a file to mask. filepath must be an absolute path to existing file""" conn = self.dcc.create('send', mask, filepath=filepath) await conn.ready return conn async def dcc_accept(self, mask, filepath, port, pos): """accept a DCC RESUME for an axisting DCC SEND. filepath is the filename to sent. port is the port opened on the server. pos is the expected offset""" return self.dcc.resume(mask, filepath, port, pos) def SIGHUP(self): self.reload() def SIGINT(self): self.notify('SIGINT') if getattr(self, 'protocol', None): self.quit('INT') time.sleep(1) self.loop.stop() def run(argv=None): bots = {} bot = IrcBot.from_argv(argv, botnet=bots) bots['bot'] = bot for section in list(bot.config): if section.startswith('bot_'): config = bot.config.pop(section) bots[section] = IrcBot.from_argv(argv, botnet=bots, **config) bot.loop.run_forever() return bots ================================================ FILE: irc3/__main__.py ================================================ from irc3 import run if __name__ == '__main__': run() ================================================ FILE: irc3/_gen_doc.py ================================================ # -*- coding: utf-8 -*- from . import rfc from . import template import os def render_attrs(title, attrs, out): out.write(title + '\n') out.write(len(title) * '=' + '\n') out.write('\n') for attr in attrs: name = attr.name title = name if isinstance(attr, int): title = '%s - %s' % (attr, title) out.write(title + '\n') out.write(len(title) * '-' + '\n\n') if hasattr(attr, 'tpl'): out.write('Format ``%s``\n\n' % attr.tpl.replace('{c.', '{')) out.write('Match ``%s``\n\n' % attr.re) out.write('Example:\n\n') out.write('.. code-block:: python\n\n') out.write(' @irc3.event(rfc.%s)\n' % name) params = getattr(attr, 'params', []) if params: params = '=None, '.join(params) out.write(' def myevent(bot, %s=None):\n' % params) else: out.write(' def myevent(bot):\n' % params) out.write(' # do something\n') out.write('\n') re_out = getattr(attr, 're_out', None) if re_out is not None: out.write('Out Match ``%s``\n\n' % re_out.re) out.write('Example:\n\n') out.write('.. code-block:: python\n\n') out.write(' @irc3.event(rfc.%s, iotype="out")\n' % name) params = getattr(re_out, 'params', []) if params: params = '=None, '.join(params) out.write(' def myevent(bot, %s=None):\n' % params) else: raise RuntimeError('regexp %s as no params' % re_out) out.write(' # do something\n') out.write('\n') def main(): print('Generate docs...') attrs = [getattr(rfc, attr) for attr in dir(rfc) if attr.isupper() and attr not in ('RETCODES',)] repls = [attr for attr in attrs if attr.name.startswith('RPL_')] errs = [attr for attr in attrs if attr.name.startswith('ERR_')] misc = [attr for attr in attrs if not attr.name.startswith(('ERR_', 'RPL_'))] out = open('docs/rfc.rst', 'w') out.write('========================\n') out.write(':mod:`irc3.rfc` RFC1459\n') out.write('========================\n\n') render_attrs('Replies (REPL)', repls, out) render_attrs('Errors (ERR)', errs, out) render_attrs('Misc', misc, out) try: os.makedirs('docs/plugins') except OSError: pass for filename in os.listdir('irc3/plugins'): if filename.startswith('_'): continue if not filename.endswith('.py'): continue filename = filename.replace('.py', '') modname = 'irc3.plugins.%s' % filename out = open('docs/plugins/' + filename + '.rst', 'w') out.write('.. automodule:: ' + modname + '\n') out.write('\n') template.main(nick='mybot', dest=os.path.join(os.getcwd(), 'examples')) if __name__ == '__main__': main() ================================================ FILE: irc3/_parse_rfc.py ================================================ # -*- coding: utf-8 -*- from collections import defaultdict import pprint import re _re_num = re.compile(r'\s(?P\d+)\s+(?P(RPL|ERR)_\w+)\s*(?P<_>.*)') _re_mask = re.compile(r'^\s{24,25}(?P<_>("(<|:).*|\S.*"$))') def main(): print('Parsing rfc file...') item = None items = [] out = open('irc3/_rfc.py', 'w') with open('irc3/rfc1459.txt') as fd: for line in fd: line = line.replace(' * ', ' * ') line = line.replace('<# visible>', '') line = line.replace('[*][@|+]', '') line = line.replace('@', '') match = _re_num.search(line) if match is not None: if item: items.append((int(item['num']), item)) item = defaultdict(list) match = match.groupdict() if '_' in match: match.pop('_') item.update(match) match = _re_mask.search(line) if match is not None: item['mask'].append(match.groupdict()['_']) _re_sub = re.compile('(?P<[^>]+>)') out.write(''' class retcode(int): name = None re = None '''.lstrip()) valids = set() for i, item in sorted(items): mask = item['mask'] if mask: num = item['num'] valids.add(i) out.write('\n') out.write('%(name)s = retcode(%(num)s)\n' % item) out.write('%(name)s.name = "%(name)s"\n' % item) mask = [s.strip('"\\ ') for s in mask] omask = ' '.join(mask) params = [] def repl(v): v = v.lower() v = v.replace('nickname', 'nick') v = v.replace('nicks', 'nicknames') for c in '!@*': v = v.replace(c, '') for c in '| ': v = v.replace(c, '_') v = v.strip(' _') if v.endswith('_name'): v = v[:-5] if v == 'client_ip_address_in_dot_form': v = 'clientip' if v == 'integer': for k in 'xyz': if k not in params: v = k break if v == 'command': v = 'cmd' if v == 'real': v = 'realname' if v == 'name' and 'nick' not in params: v = 'nick' if v == 'user': if 'nick' not in params and num not in ('352',): v = 'nick' else: v = 'username' return v def tsub(m): v = m.groupdict()['m'].strip('<>') v = repl(v) params.append(v) return '{%s}' % v if item['num'] == '303': omask = ':' elif item['num'] == '311': omask = omask.replace('*', '') elif item['num'] == '319': omask = ':' elif item['num'] == '353': omask = ' :' tpl = _re_sub.sub(tsub, omask) for v in ((' %d ', '{days}'), ('%d:%02d:%02d', '{hours}'), (':%-8s %-9s %-8s', '{x} {y} {z}')): tpl = tpl.replace(*v) tpl_ = [':{c.srv} ' + item['num'] + ' {c.nick} '] if len(tpl) > 60: tpl_.extend([':' + s for s in tpl.split(':', 1)]) else: tpl_.append(tpl) tpl = '\n '.join([repr(v) for v in tpl_]) params = [] def msub(m): v = m.groupdict()['m'].strip('<>') v = repl(v) params.append(v) return r'(?P<%s>\S+)' % v mask = _re_sub.sub(msub, omask) if '???? ' in mask: mask = mask.replace('???? ', r'\S+ ') if ' * ' in mask: mask = mask.replace(' * ', r' . ') if ':' in mask: mask = mask.split(':', 1)[0] mask += ':(?P.*)' mask = r'(?P\S+) ' + str(i) + ' (?P\\S+) "\n r"' + mask mask = mask.replace( r' (?P\S+)', ' "\n r"(?P\\S+)') mask = mask.replace( r' (?P\S+)', ' "\n r"(?P\\S+)') item['mask'] = mask params = [p for p in params if '<%s>' % p in mask] if '' in mask and 'data' not in params: params.append('data') out.write('%(name)s.re = (\n r"^:%(mask)s")\n' % item) params = pprint.pformat( ['srv', 'me'] + params, width=60, indent=4) if len(params) > 60: params = params.replace('[', '[\n ') out.write('%(name)s.tpl = (\n' % dict(item)) out.write(' %s)\n' % tpl) out.write('%(name)s.params = %(p)s\n' % dict(item, p=params)) out.write('\n') out.write('RETCODES = {\n') for i, item in sorted(items): if i in valids: out.write(' %(num)s: %(name)s,\n' % item) out.write('}\n') out.close() if __name__ == '__main__': main() ================================================ FILE: irc3/_rfc.py ================================================ class retcode(int): name = None re = None RPL_TRACELINK = retcode(200) RPL_TRACELINK.name = "RPL_TRACELINK" RPL_TRACELINK.re = ( r"^:(?P\S+) 200 (?P\S+) " r"(?P\S+)") RPL_TRACELINK.tpl = ( ':{c.srv} 200 {c.nick} ' '{next_server}') RPL_TRACELINK.params = ['srv', 'me', 'next_server'] RPL_TRACECONNECTING = retcode(201) RPL_TRACECONNECTING.name = "RPL_TRACECONNECTING" RPL_TRACECONNECTING.re = ( r"^:(?P\S+) 201 (?P\S+) " r"Try. (?P\S+) " r"(?P\S+)") RPL_TRACECONNECTING.tpl = ( ':{c.srv} 201 {c.nick} ' 'Try. {class} {server}') RPL_TRACECONNECTING.params = ['srv', 'me', 'class', 'server'] RPL_TRACEHANDSHAKE = retcode(202) RPL_TRACEHANDSHAKE.name = "RPL_TRACEHANDSHAKE" RPL_TRACEHANDSHAKE.re = ( r"^:(?P\S+) 202 (?P\S+) " r"H.S. (?P\S+) " r"(?P\S+)") RPL_TRACEHANDSHAKE.tpl = ( ':{c.srv} 202 {c.nick} ' 'H.S. {class} {server}') RPL_TRACEHANDSHAKE.params = ['srv', 'me', 'class', 'server'] RPL_TRACEUNKNOWN = retcode(203) RPL_TRACEUNKNOWN.name = "RPL_TRACEUNKNOWN" RPL_TRACEUNKNOWN.re = ( r"^:(?P\S+) 203 (?P\S+) " r"\S+ (?P\S+) [(?P\S+)]") RPL_TRACEUNKNOWN.tpl = ( ':{c.srv} 203 {c.nick} ' '???? {class} [{clientip}]') RPL_TRACEUNKNOWN.params = ['srv', 'me', 'class', 'clientip'] RPL_TRACEOPERATOR = retcode(204) RPL_TRACEOPERATOR.name = "RPL_TRACEOPERATOR" RPL_TRACEOPERATOR.re = ( r"^:(?P\S+) 204 (?P\S+) " r"Oper (?P\S+) (?P\S+)") RPL_TRACEOPERATOR.tpl = ( ':{c.srv} 204 {c.nick} ' 'Oper {class} {nick}') RPL_TRACEOPERATOR.params = ['srv', 'me', 'class', 'nick'] RPL_TRACEUSER = retcode(205) RPL_TRACEUSER.name = "RPL_TRACEUSER" RPL_TRACEUSER.re = ( r"^:(?P\S+) 205 (?P\S+) " r"User (?P\S+) (?P\S+)") RPL_TRACEUSER.tpl = ( ':{c.srv} 205 {c.nick} ' 'User {class} {nick}') RPL_TRACEUSER.params = ['srv', 'me', 'class', 'nick'] RPL_TRACESERVER = retcode(206) RPL_TRACESERVER.name = "RPL_TRACESERVER" RPL_TRACESERVER.re = ( r"^:(?P\S+) 206 (?P\S+) " r"(?P\S+)") RPL_TRACESERVER.tpl = ( ':{c.srv} 206 {c.nick} ' '{mask}') RPL_TRACESERVER.params = ['srv', 'me', 'mask'] RPL_TRACENEWTYPE = retcode(208) RPL_TRACENEWTYPE.name = "RPL_TRACENEWTYPE" RPL_TRACENEWTYPE.re = ( r"^:(?P\S+) 208 (?P\S+) " r"(?P\S+) 0 (?P\S+)") RPL_TRACENEWTYPE.tpl = ( ':{c.srv} 208 {c.nick} ' '{newtype} 0 {client}') RPL_TRACENEWTYPE.params = ['srv', 'me', 'newtype', 'client'] RPL_STATSLINKINFO = retcode(211) RPL_STATSLINKINFO.name = "RPL_STATSLINKINFO" RPL_STATSLINKINFO.re = ( r"^:(?P\S+) 211 (?P\S+) " r"(?P\S+) (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+)") RPL_STATSLINKINFO.tpl = ( ':{c.srv} 211 {c.nick} ' ':{linkname} {sendq} {sent_messages} {received_bytes} {time_open}') RPL_STATSLINKINFO.params = [ 'srv', 'me', 'linkname', 'sendq', 'sent_messages', 'received_bytes', 'time_open'] RPL_STATSCOMMANDS = retcode(212) RPL_STATSCOMMANDS.name = "RPL_STATSCOMMANDS" RPL_STATSCOMMANDS.re = ( r"^:(?P\S+) 212 (?P\S+) " r"(?P\S+) (?P\S+)") RPL_STATSCOMMANDS.tpl = ( ':{c.srv} 212 {c.nick} ' '{cmd} {count}') RPL_STATSCOMMANDS.params = ['srv', 'me', 'cmd', 'count'] RPL_STATSCLINE = retcode(213) RPL_STATSCLINE.name = "RPL_STATSCLINE" RPL_STATSCLINE.re = ( r"^:(?P\S+) 213 (?P\S+) " r"C (?P\S+) . (?P\S+) (?P\S+) (?P\S+)") RPL_STATSCLINE.tpl = ( ':{c.srv} 213 {c.nick} ' 'C {host} * {nick} {port} {class}') RPL_STATSCLINE.params = ['srv', 'me', 'host', 'nick', 'port', 'class'] RPL_STATSNLINE = retcode(214) RPL_STATSNLINE.name = "RPL_STATSNLINE" RPL_STATSNLINE.re = ( r"^:(?P\S+) 214 (?P\S+) " r"N (?P\S+) . (?P\S+) (?P\S+) (?P\S+)") RPL_STATSNLINE.tpl = ( ':{c.srv} 214 {c.nick} ' 'N {host} * {nick} {port} {class}') RPL_STATSNLINE.params = ['srv', 'me', 'host', 'nick', 'port', 'class'] RPL_STATSILINE = retcode(215) RPL_STATSILINE.name = "RPL_STATSILINE" RPL_STATSILINE.re = ( r"^:(?P\S+) 215 (?P\S+) " r"I (?P\S+) . (?P\S+) (?P\S+) (?P\S+)") RPL_STATSILINE.tpl = ( ':{c.srv} 215 {c.nick} ' 'I {host} * {host1} {port} {class}') RPL_STATSILINE.params = ['srv', 'me', 'host', 'host1', 'port', 'class'] RPL_STATSKLINE = retcode(216) RPL_STATSKLINE.name = "RPL_STATSKLINE" RPL_STATSKLINE.re = ( r"^:(?P\S+) 216 (?P\S+) " r"K (?P\S+) . (?P\S+) (?P\S+) (?P\S+)") RPL_STATSKLINE.tpl = ( ':{c.srv} 216 {c.nick} ' 'K {host} * {username} {port} {class}') RPL_STATSKLINE.params = ['srv', 'me', 'host', 'username', 'port', 'class'] RPL_STATSYLINE = retcode(218) RPL_STATSYLINE.name = "RPL_STATSYLINE" RPL_STATSYLINE.re = ( r"^:(?P\S+) 218 (?P\S+) " r"frequency> (?P\S+)") RPL_STATSYLINE.tpl = ( ':{c.srv} 218 {c.nick} ' 'frequency> {max_sendq}') RPL_STATSYLINE.params = ['srv', 'me', 'max_sendq'] RPL_ENDOFSTATS = retcode(219) RPL_ENDOFSTATS.name = "RPL_ENDOFSTATS" RPL_ENDOFSTATS.re = ( r"^:(?P\S+) 219 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFSTATS.tpl = ( ':{c.srv} 219 {c.nick} ' '{stats_letter} :End of /STATS report') RPL_ENDOFSTATS.params = ['srv', 'me', 'stats_letter', 'data'] RPL_UMODEIS = retcode(221) RPL_UMODEIS.name = "RPL_UMODEIS" RPL_UMODEIS.re = ( r"^:(?P\S+) 221 (?P\S+) " r"(?P\S+)") RPL_UMODEIS.tpl = ( ':{c.srv} 221 {c.nick} ' '{user_mode_string}') RPL_UMODEIS.params = ['srv', 'me', 'user_mode_string'] RPL_STATSLLINE = retcode(241) RPL_STATSLLINE.name = "RPL_STATSLLINE" RPL_STATSLLINE.re = ( r"^:(?P\S+) 241 (?P\S+) " r"L (?P\S+) . (?P\S+) (?P\S+)") RPL_STATSLLINE.tpl = ( ':{c.srv} 241 {c.nick} ' 'L {hostmask} * {servername} {maxdepth}') RPL_STATSLLINE.params = ['srv', 'me', 'hostmask', 'servername', 'maxdepth'] RPL_STATSUPTIME = retcode(242) RPL_STATSUPTIME.name = "RPL_STATSUPTIME" RPL_STATSUPTIME.re = ( r"^:(?P\S+) 242 (?P\S+) " r":(?P.*)") RPL_STATSUPTIME.tpl = ( ':{c.srv} 242 {c.nick} ' ':Server Up{days}days {hours}') RPL_STATSUPTIME.params = ['srv', 'me', 'data'] RPL_STATSOLINE = retcode(243) RPL_STATSOLINE.name = "RPL_STATSOLINE" RPL_STATSOLINE.re = ( r"^:(?P\S+) 243 (?P\S+) " r"O (?P\S+) . (?P\S+)") RPL_STATSOLINE.tpl = ( ':{c.srv} 243 {c.nick} ' 'O {hostmask} * {nick}') RPL_STATSOLINE.params = ['srv', 'me', 'hostmask', 'nick'] RPL_STATSHLINE = retcode(244) RPL_STATSHLINE.name = "RPL_STATSHLINE" RPL_STATSHLINE.re = ( r"^:(?P\S+) 244 (?P\S+) " r"H (?P\S+) . (?P\S+)") RPL_STATSHLINE.tpl = ( ':{c.srv} 244 {c.nick} ' 'H {hostmask} * {servername}') RPL_STATSHLINE.params = ['srv', 'me', 'hostmask', 'servername'] RPL_LUSERCLIENT = retcode(251) RPL_LUSERCLIENT.name = "RPL_LUSERCLIENT" RPL_LUSERCLIENT.re = ( r"^:(?P\S+) 251 (?P\S+) " r":(?P.*)") RPL_LUSERCLIENT.tpl = ( ':{c.srv} 251 {c.nick} ' ':There are {x} users and {y} invisible on {z} servers') RPL_LUSERCLIENT.params = ['srv', 'me', 'data'] RPL_LUSEROP = retcode(252) RPL_LUSEROP.name = "RPL_LUSEROP" RPL_LUSEROP.re = ( r"^:(?P\S+) 252 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_LUSEROP.tpl = ( ':{c.srv} 252 {c.nick} ' '{x} :operator(s) online') RPL_LUSEROP.params = ['srv', 'me', 'x', 'data'] RPL_LUSERUNKNOWN = retcode(253) RPL_LUSERUNKNOWN.name = "RPL_LUSERUNKNOWN" RPL_LUSERUNKNOWN.re = ( r"^:(?P\S+) 253 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_LUSERUNKNOWN.tpl = ( ':{c.srv} 253 {c.nick} ' '{x} :unknown connection(s)') RPL_LUSERUNKNOWN.params = ['srv', 'me', 'x', 'data'] RPL_LUSERCHANNELS = retcode(254) RPL_LUSERCHANNELS.name = "RPL_LUSERCHANNELS" RPL_LUSERCHANNELS.re = ( r"^:(?P\S+) 254 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_LUSERCHANNELS.tpl = ( ':{c.srv} 254 {c.nick} ' '{x} :channels formed') RPL_LUSERCHANNELS.params = ['srv', 'me', 'x', 'data'] RPL_LUSERME = retcode(255) RPL_LUSERME.name = "RPL_LUSERME" RPL_LUSERME.re = ( r"^:(?P\S+) 255 (?P\S+) " r":(?P.*)") RPL_LUSERME.tpl = ( ':{c.srv} 255 {c.nick} ' ':I have {x} clients and {y}') RPL_LUSERME.params = ['srv', 'me', 'data'] RPL_ADMINME = retcode(256) RPL_ADMINME.name = "RPL_ADMINME" RPL_ADMINME.re = ( r"^:(?P\S+) 256 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ADMINME.tpl = ( ':{c.srv} 256 {c.nick} ' '{server} :Administrative info') RPL_ADMINME.params = ['srv', 'me', 'server', 'data'] RPL_ADMINLOC1 = retcode(257) RPL_ADMINLOC1.name = "RPL_ADMINLOC1" RPL_ADMINLOC1.re = ( r"^:(?P\S+) 257 (?P\S+) " r":(?P.*)") RPL_ADMINLOC1.tpl = ( ':{c.srv} 257 {c.nick} ' ':{admin_info}') RPL_ADMINLOC1.params = ['srv', 'me', 'data'] RPL_ADMINLOC2 = retcode(258) RPL_ADMINLOC2.name = "RPL_ADMINLOC2" RPL_ADMINLOC2.re = ( r"^:(?P\S+) 258 (?P\S+) " r":(?P.*)") RPL_ADMINLOC2.tpl = ( ':{c.srv} 258 {c.nick} ' ':{admin_info}') RPL_ADMINLOC2.params = ['srv', 'me', 'data'] RPL_ADMINEMAIL = retcode(259) RPL_ADMINEMAIL.name = "RPL_ADMINEMAIL" RPL_ADMINEMAIL.re = ( r"^:(?P\S+) 259 (?P\S+) " r":(?P.*)") RPL_ADMINEMAIL.tpl = ( ':{c.srv} 259 {c.nick} ' ':{admin_info}') RPL_ADMINEMAIL.params = ['srv', 'me', 'data'] RPL_TRACELOG = retcode(261) RPL_TRACELOG.name = "RPL_TRACELOG" RPL_TRACELOG.re = ( r"^:(?P\S+) 261 (?P\S+) " r"File (?P\S+) (?P\S+)") RPL_TRACELOG.tpl = ( ':{c.srv} 261 {c.nick} ' 'File {logfile} {debug_level}') RPL_TRACELOG.params = ['srv', 'me', 'logfile', 'debug_level'] RPL_AWAY = retcode(301) RPL_AWAY.name = "RPL_AWAY" RPL_AWAY.re = ( r"^:(?P\S+) 301 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_AWAY.tpl = ( ':{c.srv} 301 {c.nick} ' '{nick} :{away_message}') RPL_AWAY.params = ['srv', 'me', 'nick', 'data'] RPL_USERHOST = retcode(302) RPL_USERHOST.name = "RPL_USERHOST" RPL_USERHOST.re = ( r"^:(?P\S+) 302 (?P\S+) " r":(?P.*)") RPL_USERHOST.tpl = ( ':{c.srv} 302 {c.nick} ' ':[{reply}{{space}{reply}}]') RPL_USERHOST.params = ['srv', 'me', 'data'] RPL_ISON = retcode(303) RPL_ISON.name = "RPL_ISON" RPL_ISON.re = ( r"^:(?P\S+) 303 (?P\S+) " r":(?P.*)") RPL_ISON.tpl = ( ':{c.srv} 303 {c.nick} ' ':{nicknames}') RPL_ISON.params = ['srv', 'me', 'data'] RPL_UNAWAY = retcode(305) RPL_UNAWAY.name = "RPL_UNAWAY" RPL_UNAWAY.re = ( r"^:(?P\S+) 305 (?P\S+) " r":(?P.*)") RPL_UNAWAY.tpl = ( ':{c.srv} 305 {c.nick} ' ':You are no longer marked as being away') RPL_UNAWAY.params = ['srv', 'me', 'data'] RPL_NOWAWAY = retcode(306) RPL_NOWAWAY.name = "RPL_NOWAWAY" RPL_NOWAWAY.re = ( r"^:(?P\S+) 306 (?P\S+) " r":(?P.*)") RPL_NOWAWAY.tpl = ( ':{c.srv} 306 {c.nick} ' ':You have been marked as being away') RPL_NOWAWAY.params = ['srv', 'me', 'data'] RPL_WHOISUSER = retcode(311) RPL_WHOISUSER.name = "RPL_WHOISUSER" RPL_WHOISUSER.re = ( r"^:(?P\S+) 311 (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) (?P\S+) :(?P.*)") RPL_WHOISUSER.tpl = ( ':{c.srv} 311 {c.nick} ' '{nick} {username} {host} {m} :{realname}') RPL_WHOISUSER.params = ['srv', 'me', 'nick', 'username', 'host', 'm', 'data'] RPL_WHOISSERVER = retcode(312) RPL_WHOISSERVER.name = "RPL_WHOISSERVER" RPL_WHOISSERVER.re = ( r"^:(?P\S+) 312 (?P\S+) " r"(?P\S+) " r"(?P\S+) :(?P.*)") RPL_WHOISSERVER.tpl = ( ':{c.srv} 312 {c.nick} ' '{nick} {server} :{server_info}') RPL_WHOISSERVER.params = ['srv', 'me', 'nick', 'server', 'data'] RPL_WHOISOPERATOR = retcode(313) RPL_WHOISOPERATOR.name = "RPL_WHOISOPERATOR" RPL_WHOISOPERATOR.re = ( r"^:(?P\S+) 313 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_WHOISOPERATOR.tpl = ( ':{c.srv} 313 {c.nick} ' '{nick} :is an IRC operator') RPL_WHOISOPERATOR.params = ['srv', 'me', 'nick', 'data'] RPL_WHOWASUSER = retcode(314) RPL_WHOWASUSER.name = "RPL_WHOWASUSER" RPL_WHOWASUSER.re = ( r"^:(?P\S+) 314 (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) . :(?P.*)") RPL_WHOWASUSER.tpl = ( ':{c.srv} 314 {c.nick} ' '{nick} {username} {host} * :{realname}') RPL_WHOWASUSER.params = ['srv', 'me', 'nick', 'username', 'host', 'data'] RPL_ENDOFWHO = retcode(315) RPL_ENDOFWHO.name = "RPL_ENDOFWHO" RPL_ENDOFWHO.re = ( r"^:(?P\S+) 315 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFWHO.tpl = ( ':{c.srv} 315 {c.nick} ' '{nick} :End of /WHO list') RPL_ENDOFWHO.params = ['srv', 'me', 'nick', 'data'] RPL_WHOISIDLE = retcode(317) RPL_WHOISIDLE.name = "RPL_WHOISIDLE" RPL_WHOISIDLE.re = ( r"^:(?P\S+) 317 (?P\S+) " r"(?P\S+) (?P\S+) :(?P.*)") RPL_WHOISIDLE.tpl = ( ':{c.srv} 317 {c.nick} ' '{nick} {x} :seconds idle') RPL_WHOISIDLE.params = ['srv', 'me', 'nick', 'x', 'data'] RPL_ENDOFWHOIS = retcode(318) RPL_ENDOFWHOIS.name = "RPL_ENDOFWHOIS" RPL_ENDOFWHOIS.re = ( r"^:(?P\S+) 318 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFWHOIS.tpl = ( ':{c.srv} 318 {c.nick} ' '{nick} :End of /WHOIS list') RPL_ENDOFWHOIS.params = ['srv', 'me', 'nick', 'data'] RPL_WHOISCHANNELS = retcode(319) RPL_WHOISCHANNELS.name = "RPL_WHOISCHANNELS" RPL_WHOISCHANNELS.re = ( r"^:(?P\S+) 319 (?P\S+) " r":(?P.*)") RPL_WHOISCHANNELS.tpl = ( ':{c.srv} 319 {c.nick} ' ':{channels}') RPL_WHOISCHANNELS.params = ['srv', 'me', 'data'] RPL_LISTSTART = retcode(321) RPL_LISTSTART.name = "RPL_LISTSTART" RPL_LISTSTART.re = ( r"^:(?P\S+) 321 (?P\S+) " r"Channel :(?P.*)") RPL_LISTSTART.tpl = ( ':{c.srv} 321 {c.nick} ' 'Channel :Users Name') RPL_LISTSTART.params = ['srv', 'me', 'data'] RPL_LIST = retcode(322) RPL_LIST.name = "RPL_LIST" RPL_LIST.re = ( r"^:(?P\S+) 322 (?P\S+) " r"(?P\S+) (?P\S+) :(?P.*)") RPL_LIST.tpl = ( ':{c.srv} 322 {c.nick} ' '{channel} {visible} :{topic}') RPL_LIST.params = ['srv', 'me', 'channel', 'visible', 'data'] RPL_LISTEND = retcode(323) RPL_LISTEND.name = "RPL_LISTEND" RPL_LISTEND.re = ( r"^:(?P\S+) 323 (?P\S+) " r":(?P.*)") RPL_LISTEND.tpl = ( ':{c.srv} 323 {c.nick} ' ':End of /LIST') RPL_LISTEND.params = ['srv', 'me', 'data'] RPL_CHANNELMODEIS = retcode(324) RPL_CHANNELMODEIS.name = "RPL_CHANNELMODEIS" RPL_CHANNELMODEIS.re = ( r"^:(?P\S+) 324 (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+)") RPL_CHANNELMODEIS.tpl = ( ':{c.srv} 324 {c.nick} ' '{channel} {mode} {mode_params}') RPL_CHANNELMODEIS.params = ['srv', 'me', 'channel', 'mode', 'mode_params'] RPL_NOTOPIC = retcode(331) RPL_NOTOPIC.name = "RPL_NOTOPIC" RPL_NOTOPIC.re = ( r"^:(?P\S+) 331 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_NOTOPIC.tpl = ( ':{c.srv} 331 {c.nick} ' '{channel} :No topic is set') RPL_NOTOPIC.params = ['srv', 'me', 'channel', 'data'] RPL_TOPIC = retcode(332) RPL_TOPIC.name = "RPL_TOPIC" RPL_TOPIC.re = ( r"^:(?P\S+) 332 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_TOPIC.tpl = ( ':{c.srv} 332 {c.nick} ' '{channel} :{topic}') RPL_TOPIC.params = ['srv', 'me', 'channel', 'data'] RPL_INVITING = retcode(341) RPL_INVITING.name = "RPL_INVITING" RPL_INVITING.re = ( r"^:(?P\S+) 341 (?P\S+) " r"(?P\S+) (?P\S+)") RPL_INVITING.tpl = ( ':{c.srv} 341 {c.nick} ' '{channel} {nick}') RPL_INVITING.params = ['srv', 'me', 'channel', 'nick'] RPL_SUMMONING = retcode(342) RPL_SUMMONING.name = "RPL_SUMMONING" RPL_SUMMONING.re = ( r"^:(?P\S+) 342 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_SUMMONING.tpl = ( ':{c.srv} 342 {c.nick} ' '{nick} :Summoning user to IRC') RPL_SUMMONING.params = ['srv', 'me', 'nick', 'data'] RPL_VERSION = retcode(351) RPL_VERSION.name = "RPL_VERSION" RPL_VERSION.re = ( r"^:(?P\S+) 351 (?P\S+) " r"(?P\S+).(?P\S+) " r"(?P\S+) :(?P.*)") RPL_VERSION.tpl = ( ':{c.srv} 351 {c.nick} ' '{version}.{debuglevel} {server} :{comments}') RPL_VERSION.params = ['srv', 'me', 'version', 'debuglevel', 'server', 'data'] RPL_WHOREPLY = retcode(352) RPL_WHOREPLY.name = "RPL_WHOREPLY" RPL_WHOREPLY.re = ( r"^:(?P\S+) 352 (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) :(?P.*)") RPL_WHOREPLY.tpl = ( ':{c.srv} 352 {c.nick} ' ':{channel} {username} {host} {server} {nick} {modes} ' ':{hopcount} {realname}') RPL_WHOREPLY.params = [ 'srv', 'me', 'channel', 'username', 'host', 'server', 'nick', 'modes', 'data'] RPL_NAMREPLY = retcode(353) RPL_NAMREPLY.name = "RPL_NAMREPLY" RPL_NAMREPLY.re = ( r"^:(?P\S+) 353 (?P\S+) " r"(?P\S+) (?P\S+) :(?P.*)") RPL_NAMREPLY.tpl = ( ':{c.srv} 353 {c.nick} ' '{m} {channel} :{nicknames}') RPL_NAMREPLY.params = ['srv', 'me', 'm', 'channel', 'data'] RPL_LINKS = retcode(364) RPL_LINKS.name = "RPL_LINKS" RPL_LINKS.re = ( r"^:(?P\S+) 364 (?P\S+) " r"(?P\S+) " r"(?P\S+) :(?P.*)") RPL_LINKS.tpl = ( ':{c.srv} 364 {c.nick} ' '{mask} {server} :{hopcount} {server_info}') RPL_LINKS.params = ['srv', 'me', 'mask', 'server', 'data'] RPL_ENDOFLINKS = retcode(365) RPL_ENDOFLINKS.name = "RPL_ENDOFLINKS" RPL_ENDOFLINKS.re = ( r"^:(?P\S+) 365 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFLINKS.tpl = ( ':{c.srv} 365 {c.nick} ' '{mask} :End of /LINKS list') RPL_ENDOFLINKS.params = ['srv', 'me', 'mask', 'data'] RPL_ENDOFNAMES = retcode(366) RPL_ENDOFNAMES.name = "RPL_ENDOFNAMES" RPL_ENDOFNAMES.re = ( r"^:(?P\S+) 366 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFNAMES.tpl = ( ':{c.srv} 366 {c.nick} ' '{channel} :End of /NAMES list') RPL_ENDOFNAMES.params = ['srv', 'me', 'channel', 'data'] RPL_BANLIST = retcode(367) RPL_BANLIST.name = "RPL_BANLIST" RPL_BANLIST.re = ( r"^:(?P\S+) 367 (?P\S+) " r"(?P\S+) (?P\S+)") RPL_BANLIST.tpl = ( ':{c.srv} 367 {c.nick} ' '{channel} {banid}') RPL_BANLIST.params = ['srv', 'me', 'channel', 'banid'] RPL_ENDOFBANLIST = retcode(368) RPL_ENDOFBANLIST.name = "RPL_ENDOFBANLIST" RPL_ENDOFBANLIST.re = ( r"^:(?P\S+) 368 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFBANLIST.tpl = ( ':{c.srv} 368 {c.nick} ' '{channel} :End of channel ban list') RPL_ENDOFBANLIST.params = ['srv', 'me', 'channel', 'data'] RPL_ENDOFWHOWAS = retcode(369) RPL_ENDOFWHOWAS.name = "RPL_ENDOFWHOWAS" RPL_ENDOFWHOWAS.re = ( r"^:(?P\S+) 369 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_ENDOFWHOWAS.tpl = ( ':{c.srv} 369 {c.nick} ' '{nick} :End of WHOWAS') RPL_ENDOFWHOWAS.params = ['srv', 'me', 'nick', 'data'] RPL_INFO = retcode(371) RPL_INFO.name = "RPL_INFO" RPL_INFO.re = ( r"^:(?P\S+) 371 (?P\S+) " r":(?P.*)") RPL_INFO.tpl = ( ':{c.srv} 371 {c.nick} ' ':{string}') RPL_INFO.params = ['srv', 'me', 'data'] RPL_MOTD = retcode(372) RPL_MOTD.name = "RPL_MOTD" RPL_MOTD.re = ( r"^:(?P\S+) 372 (?P\S+) " r":(?P.*)") RPL_MOTD.tpl = ( ':{c.srv} 372 {c.nick} ' ':- {text}') RPL_MOTD.params = ['srv', 'me', 'data'] RPL_ENDOFINFO = retcode(374) RPL_ENDOFINFO.name = "RPL_ENDOFINFO" RPL_ENDOFINFO.re = ( r"^:(?P\S+) 374 (?P\S+) " r":(?P.*)") RPL_ENDOFINFO.tpl = ( ':{c.srv} 374 {c.nick} ' ':End of /INFO list') RPL_ENDOFINFO.params = ['srv', 'me', 'data'] RPL_MOTDSTART = retcode(375) RPL_MOTDSTART.name = "RPL_MOTDSTART" RPL_MOTDSTART.re = ( r"^:(?P\S+) 375 (?P\S+) " r":(?P.*)") RPL_MOTDSTART.tpl = ( ':{c.srv} 375 {c.nick} ' ':- {server} Message of the day -') RPL_MOTDSTART.params = ['srv', 'me', 'data'] RPL_ENDOFMOTD = retcode(376) RPL_ENDOFMOTD.name = "RPL_ENDOFMOTD" RPL_ENDOFMOTD.re = ( r"^:(?P\S+) 376 (?P\S+) " r":(?P.*)") RPL_ENDOFMOTD.tpl = ( ':{c.srv} 376 {c.nick} ' ':End of /MOTD command') RPL_ENDOFMOTD.params = ['srv', 'me', 'data'] RPL_YOUREOPER = retcode(381) RPL_YOUREOPER.name = "RPL_YOUREOPER" RPL_YOUREOPER.re = ( r"^:(?P\S+) 381 (?P\S+) " r":(?P.*)") RPL_YOUREOPER.tpl = ( ':{c.srv} 381 {c.nick} ' ':You are now an IRC operator') RPL_YOUREOPER.params = ['srv', 'me', 'data'] RPL_REHASHING = retcode(382) RPL_REHASHING.name = "RPL_REHASHING" RPL_REHASHING.re = ( r"^:(?P\S+) 382 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_REHASHING.tpl = ( ':{c.srv} 382 {c.nick} ' '{config_file} :Rehashing') RPL_REHASHING.params = ['srv', 'me', 'config_file', 'data'] RPL_TIME = retcode(391) RPL_TIME.name = "RPL_TIME" RPL_TIME.re = ( r"^:(?P\S+) 391 (?P\S+) " r"(?P\S+) :(?P.*)") RPL_TIME.tpl = ( ':{c.srv} 391 {c.nick} ' "{server} :{string_showing_server's_local_time}") RPL_TIME.params = ['srv', 'me', 'server', 'data'] RPL_USERSSTART = retcode(392) RPL_USERSSTART.name = "RPL_USERSSTART" RPL_USERSSTART.re = ( r"^:(?P\S+) 392 (?P\S+) " r":(?P.*)") RPL_USERSSTART.tpl = ( ':{c.srv} 392 {c.nick} ' ':UserID Terminal Host') RPL_USERSSTART.params = ['srv', 'me', 'data'] RPL_USERS = retcode(393) RPL_USERS.name = "RPL_USERS" RPL_USERS.re = ( r"^:(?P\S+) 393 (?P\S+) " r":(?P.*)") RPL_USERS.tpl = ( ':{c.srv} 393 {c.nick} ' '{x} {y} {z}') RPL_USERS.params = ['srv', 'me', 'data'] RPL_ENDOFUSERS = retcode(394) RPL_ENDOFUSERS.name = "RPL_ENDOFUSERS" RPL_ENDOFUSERS.re = ( r"^:(?P\S+) 394 (?P\S+) " r":(?P.*)") RPL_ENDOFUSERS.tpl = ( ':{c.srv} 394 {c.nick} ' ':End of users') RPL_ENDOFUSERS.params = ['srv', 'me', 'data'] RPL_NOUSERS = retcode(395) RPL_NOUSERS.name = "RPL_NOUSERS" RPL_NOUSERS.re = ( r"^:(?P\S+) 395 (?P\S+) " r":(?P.*)") RPL_NOUSERS.tpl = ( ':{c.srv} 395 {c.nick} ' ':Nobody logged in') RPL_NOUSERS.params = ['srv', 'me', 'data'] ERR_NOSUCHNICK = retcode(401) ERR_NOSUCHNICK.name = "ERR_NOSUCHNICK" ERR_NOSUCHNICK.re = ( r"^:(?P\S+) 401 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOSUCHNICK.tpl = ( ':{c.srv} 401 {c.nick} ' '{nick} :No such nick/channel') ERR_NOSUCHNICK.params = ['srv', 'me', 'nick', 'data'] ERR_NOSUCHSERVER = retcode(402) ERR_NOSUCHSERVER.name = "ERR_NOSUCHSERVER" ERR_NOSUCHSERVER.re = ( r"^:(?P\S+) 402 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOSUCHSERVER.tpl = ( ':{c.srv} 402 {c.nick} ' '{server} :No such server') ERR_NOSUCHSERVER.params = ['srv', 'me', 'server', 'data'] ERR_NOSUCHCHANNEL = retcode(403) ERR_NOSUCHCHANNEL.name = "ERR_NOSUCHCHANNEL" ERR_NOSUCHCHANNEL.re = ( r"^:(?P\S+) 403 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOSUCHCHANNEL.tpl = ( ':{c.srv} 403 {c.nick} ' '{channel} :No such channel') ERR_NOSUCHCHANNEL.params = ['srv', 'me', 'channel', 'data'] ERR_CANNOTSENDTOCHAN = retcode(404) ERR_CANNOTSENDTOCHAN.name = "ERR_CANNOTSENDTOCHAN" ERR_CANNOTSENDTOCHAN.re = ( r"^:(?P\S+) 404 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_CANNOTSENDTOCHAN.tpl = ( ':{c.srv} 404 {c.nick} ' '{channel} :Cannot send to channel') ERR_CANNOTSENDTOCHAN.params = ['srv', 'me', 'channel', 'data'] ERR_TOOMANYCHANNELS = retcode(405) ERR_TOOMANYCHANNELS.name = "ERR_TOOMANYCHANNELS" ERR_TOOMANYCHANNELS.re = ( r"^:(?P\S+) 405 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_TOOMANYCHANNELS.tpl = ( ':{c.srv} 405 {c.nick} ' '{channel} :You have joined too many channels') ERR_TOOMANYCHANNELS.params = ['srv', 'me', 'channel', 'data'] ERR_WASNOSUCHNICK = retcode(406) ERR_WASNOSUCHNICK.name = "ERR_WASNOSUCHNICK" ERR_WASNOSUCHNICK.re = ( r"^:(?P\S+) 406 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_WASNOSUCHNICK.tpl = ( ':{c.srv} 406 {c.nick} ' '{nick} :There was no such nickname') ERR_WASNOSUCHNICK.params = ['srv', 'me', 'nick', 'data'] ERR_TOOMANYTARGETS = retcode(407) ERR_TOOMANYTARGETS.name = "ERR_TOOMANYTARGETS" ERR_TOOMANYTARGETS.re = ( r"^:(?P\S+) 407 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_TOOMANYTARGETS.tpl = ( ':{c.srv} 407 {c.nick} ' '{target} :Duplicate recipients. No message delivered') ERR_TOOMANYTARGETS.params = ['srv', 'me', 'target', 'data'] ERR_NOORIGIN = retcode(409) ERR_NOORIGIN.name = "ERR_NOORIGIN" ERR_NOORIGIN.re = ( r"^:(?P\S+) 409 (?P\S+) " r":(?P.*)") ERR_NOORIGIN.tpl = ( ':{c.srv} 409 {c.nick} ' ':No origin specified') ERR_NOORIGIN.params = ['srv', 'me', 'data'] ERR_NORECIPIENT = retcode(411) ERR_NORECIPIENT.name = "ERR_NORECIPIENT" ERR_NORECIPIENT.re = ( r"^:(?P\S+) 411 (?P\S+) " r":(?P.*)") ERR_NORECIPIENT.tpl = ( ':{c.srv} 411 {c.nick} ' ':No recipient given ({cmd})') ERR_NORECIPIENT.params = ['srv', 'me', 'data'] ERR_NOTEXTTOSEND = retcode(412) ERR_NOTEXTTOSEND.name = "ERR_NOTEXTTOSEND" ERR_NOTEXTTOSEND.re = ( r"^:(?P\S+) 412 (?P\S+) " r":(?P.*)") ERR_NOTEXTTOSEND.tpl = ( ':{c.srv} 412 {c.nick} ' ':No text to send') ERR_NOTEXTTOSEND.params = ['srv', 'me', 'data'] ERR_NOTOPLEVEL = retcode(413) ERR_NOTOPLEVEL.name = "ERR_NOTOPLEVEL" ERR_NOTOPLEVEL.re = ( r"^:(?P\S+) 413 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOTOPLEVEL.tpl = ( ':{c.srv} 413 {c.nick} ' '{mask} :No toplevel domain specified') ERR_NOTOPLEVEL.params = ['srv', 'me', 'mask', 'data'] ERR_WILDTOPLEVEL = retcode(414) ERR_WILDTOPLEVEL.name = "ERR_WILDTOPLEVEL" ERR_WILDTOPLEVEL.re = ( r"^:(?P\S+) 414 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_WILDTOPLEVEL.tpl = ( ':{c.srv} 414 {c.nick} ' '{mask} :Wildcard in toplevel domain') ERR_WILDTOPLEVEL.params = ['srv', 'me', 'mask', 'data'] ERR_UNKNOWNCOMMAND = retcode(421) ERR_UNKNOWNCOMMAND.name = "ERR_UNKNOWNCOMMAND" ERR_UNKNOWNCOMMAND.re = ( r"^:(?P\S+) 421 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_UNKNOWNCOMMAND.tpl = ( ':{c.srv} 421 {c.nick} ' '{cmd} :Unknown command') ERR_UNKNOWNCOMMAND.params = ['srv', 'me', 'cmd', 'data'] ERR_NOMOTD = retcode(422) ERR_NOMOTD.name = "ERR_NOMOTD" ERR_NOMOTD.re = ( r"^:(?P\S+) 422 (?P\S+) " r":(?P.*)") ERR_NOMOTD.tpl = ( ':{c.srv} 422 {c.nick} ' ':MOTD File is missing') ERR_NOMOTD.params = ['srv', 'me', 'data'] ERR_NOADMININFO = retcode(423) ERR_NOADMININFO.name = "ERR_NOADMININFO" ERR_NOADMININFO.re = ( r"^:(?P\S+) 423 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOADMININFO.tpl = ( ':{c.srv} 423 {c.nick} ' '{server} :No administrative info available') ERR_NOADMININFO.params = ['srv', 'me', 'server', 'data'] ERR_NONICKNAMEGIVEN = retcode(431) ERR_NONICKNAMEGIVEN.name = "ERR_NONICKNAMEGIVEN" ERR_NONICKNAMEGIVEN.re = ( r"^:(?P\S+) 431 (?P\S+) " r":(?P.*)") ERR_NONICKNAMEGIVEN.tpl = ( ':{c.srv} 431 {c.nick} ' ':No nickname given') ERR_NONICKNAMEGIVEN.params = ['srv', 'me', 'data'] ERR_ERRONEUSNICKNAME = retcode(432) ERR_ERRONEUSNICKNAME.name = "ERR_ERRONEUSNICKNAME" ERR_ERRONEUSNICKNAME.re = ( r"^:(?P\S+) 432 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_ERRONEUSNICKNAME.tpl = ( ':{c.srv} 432 {c.nick} ' '{nick} :Erroneus nickname') ERR_ERRONEUSNICKNAME.params = ['srv', 'me', 'nick', 'data'] ERR_NICKNAMEINUSE = retcode(433) ERR_NICKNAMEINUSE.name = "ERR_NICKNAMEINUSE" ERR_NICKNAMEINUSE.re = ( r"^:(?P\S+) 433 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NICKNAMEINUSE.tpl = ( ':{c.srv} 433 {c.nick} ' '{nick} :Nickname is already in use') ERR_NICKNAMEINUSE.params = ['srv', 'me', 'nick', 'data'] ERR_NICKCOLLISION = retcode(436) ERR_NICKCOLLISION.name = "ERR_NICKCOLLISION" ERR_NICKCOLLISION.re = ( r"^:(?P\S+) 436 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NICKCOLLISION.tpl = ( ':{c.srv} 436 {c.nick} ' '{nick} :Nickname collision KILL') ERR_NICKCOLLISION.params = ['srv', 'me', 'nick', 'data'] ERR_USERNOTINCHANNEL = retcode(441) ERR_USERNOTINCHANNEL.name = "ERR_USERNOTINCHANNEL" ERR_USERNOTINCHANNEL.re = ( r"^:(?P\S+) 441 (?P\S+) " r"(?P\S+) (?P\S+) :(?P.*)") ERR_USERNOTINCHANNEL.tpl = ( ':{c.srv} 441 {c.nick} ' "{nick} {channel} :They aren't on that channel") ERR_USERNOTINCHANNEL.params = ['srv', 'me', 'nick', 'channel', 'data'] ERR_NOTONCHANNEL = retcode(442) ERR_NOTONCHANNEL.name = "ERR_NOTONCHANNEL" ERR_NOTONCHANNEL.re = ( r"^:(?P\S+) 442 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOTONCHANNEL.tpl = ( ':{c.srv} 442 {c.nick} ' "{channel} :You're not on that channel") ERR_NOTONCHANNEL.params = ['srv', 'me', 'channel', 'data'] ERR_USERONCHANNEL = retcode(443) ERR_USERONCHANNEL.name = "ERR_USERONCHANNEL" ERR_USERONCHANNEL.re = ( r"^:(?P\S+) 443 (?P\S+) " r"(?P\S+) (?P\S+) :(?P.*)") ERR_USERONCHANNEL.tpl = ( ':{c.srv} 443 {c.nick} ' '{nick} {channel} :is already on channel') ERR_USERONCHANNEL.params = ['srv', 'me', 'nick', 'channel', 'data'] ERR_NOLOGIN = retcode(444) ERR_NOLOGIN.name = "ERR_NOLOGIN" ERR_NOLOGIN.re = ( r"^:(?P\S+) 444 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NOLOGIN.tpl = ( ':{c.srv} 444 {c.nick} ' '{nick} :User not logged in') ERR_NOLOGIN.params = ['srv', 'me', 'nick', 'data'] ERR_SUMMONDISABLED = retcode(445) ERR_SUMMONDISABLED.name = "ERR_SUMMONDISABLED" ERR_SUMMONDISABLED.re = ( r"^:(?P\S+) 445 (?P\S+) " r":(?P.*)") ERR_SUMMONDISABLED.tpl = ( ':{c.srv} 445 {c.nick} ' ':SUMMON has been disabled') ERR_SUMMONDISABLED.params = ['srv', 'me', 'data'] ERR_USERSDISABLED = retcode(446) ERR_USERSDISABLED.name = "ERR_USERSDISABLED" ERR_USERSDISABLED.re = ( r"^:(?P\S+) 446 (?P\S+) " r":(?P.*)") ERR_USERSDISABLED.tpl = ( ':{c.srv} 446 {c.nick} ' ':USERS has been disabled') ERR_USERSDISABLED.params = ['srv', 'me', 'data'] ERR_NOTREGISTERED = retcode(451) ERR_NOTREGISTERED.name = "ERR_NOTREGISTERED" ERR_NOTREGISTERED.re = ( r"^:(?P\S+) 451 (?P\S+) " r":(?P.*)") ERR_NOTREGISTERED.tpl = ( ':{c.srv} 451 {c.nick} ' ':You have not registered') ERR_NOTREGISTERED.params = ['srv', 'me', 'data'] ERR_NEEDMOREPARAMS = retcode(461) ERR_NEEDMOREPARAMS.name = "ERR_NEEDMOREPARAMS" ERR_NEEDMOREPARAMS.re = ( r"^:(?P\S+) 461 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_NEEDMOREPARAMS.tpl = ( ':{c.srv} 461 {c.nick} ' '{cmd} :Not enough parameters') ERR_NEEDMOREPARAMS.params = ['srv', 'me', 'cmd', 'data'] ERR_ALREADYREGISTRED = retcode(462) ERR_ALREADYREGISTRED.name = "ERR_ALREADYREGISTRED" ERR_ALREADYREGISTRED.re = ( r"^:(?P\S+) 462 (?P\S+) " r":(?P.*)") ERR_ALREADYREGISTRED.tpl = ( ':{c.srv} 462 {c.nick} ' ':You may not reregister') ERR_ALREADYREGISTRED.params = ['srv', 'me', 'data'] ERR_NOPERMFORHOST = retcode(463) ERR_NOPERMFORHOST.name = "ERR_NOPERMFORHOST" ERR_NOPERMFORHOST.re = ( r"^:(?P\S+) 463 (?P\S+) " r":(?P.*)") ERR_NOPERMFORHOST.tpl = ( ':{c.srv} 463 {c.nick} ' ":Your host isn't among the privileged") ERR_NOPERMFORHOST.params = ['srv', 'me', 'data'] ERR_PASSWDMISMATCH = retcode(464) ERR_PASSWDMISMATCH.name = "ERR_PASSWDMISMATCH" ERR_PASSWDMISMATCH.re = ( r"^:(?P\S+) 464 (?P\S+) " r":(?P.*)") ERR_PASSWDMISMATCH.tpl = ( ':{c.srv} 464 {c.nick} ' ':Password incorrect') ERR_PASSWDMISMATCH.params = ['srv', 'me', 'data'] ERR_YOUREBANNEDCREEP = retcode(465) ERR_YOUREBANNEDCREEP.name = "ERR_YOUREBANNEDCREEP" ERR_YOUREBANNEDCREEP.re = ( r"^:(?P\S+) 465 (?P\S+) " r":(?P.*)") ERR_YOUREBANNEDCREEP.tpl = ( ':{c.srv} 465 {c.nick} ' ':You are banned from this server') ERR_YOUREBANNEDCREEP.params = ['srv', 'me', 'data'] ERR_KEYSET = retcode(467) ERR_KEYSET.name = "ERR_KEYSET" ERR_KEYSET.re = ( r"^:(?P\S+) 467 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_KEYSET.tpl = ( ':{c.srv} 467 {c.nick} ' '{channel} :Channel key already set') ERR_KEYSET.params = ['srv', 'me', 'channel', 'data'] ERR_CHANNELISFULL = retcode(471) ERR_CHANNELISFULL.name = "ERR_CHANNELISFULL" ERR_CHANNELISFULL.re = ( r"^:(?P\S+) 471 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_CHANNELISFULL.tpl = ( ':{c.srv} 471 {c.nick} ' '{channel} :Cannot join channel (+l)') ERR_CHANNELISFULL.params = ['srv', 'me', 'channel', 'data'] ERR_UNKNOWNMODE = retcode(472) ERR_UNKNOWNMODE.name = "ERR_UNKNOWNMODE" ERR_UNKNOWNMODE.re = ( r"^:(?P\S+) 472 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_UNKNOWNMODE.tpl = ( ':{c.srv} 472 {c.nick} ' '{char} :is unknown mode char to me') ERR_UNKNOWNMODE.params = ['srv', 'me', 'char', 'data'] ERR_INVITEONLYCHAN = retcode(473) ERR_INVITEONLYCHAN.name = "ERR_INVITEONLYCHAN" ERR_INVITEONLYCHAN.re = ( r"^:(?P\S+) 473 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_INVITEONLYCHAN.tpl = ( ':{c.srv} 473 {c.nick} ' '{channel} :Cannot join channel (+i)') ERR_INVITEONLYCHAN.params = ['srv', 'me', 'channel', 'data'] ERR_BANNEDFROMCHAN = retcode(474) ERR_BANNEDFROMCHAN.name = "ERR_BANNEDFROMCHAN" ERR_BANNEDFROMCHAN.re = ( r"^:(?P\S+) 474 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_BANNEDFROMCHAN.tpl = ( ':{c.srv} 474 {c.nick} ' '{channel} :Cannot join channel (+b)') ERR_BANNEDFROMCHAN.params = ['srv', 'me', 'channel', 'data'] ERR_BADCHANNELKEY = retcode(475) ERR_BADCHANNELKEY.name = "ERR_BADCHANNELKEY" ERR_BADCHANNELKEY.re = ( r"^:(?P\S+) 475 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_BADCHANNELKEY.tpl = ( ':{c.srv} 475 {c.nick} ' '{channel} :Cannot join channel (+k)') ERR_BADCHANNELKEY.params = ['srv', 'me', 'channel', 'data'] ERR_NOPRIVILEGES = retcode(481) ERR_NOPRIVILEGES.name = "ERR_NOPRIVILEGES" ERR_NOPRIVILEGES.re = ( r"^:(?P\S+) 481 (?P\S+) " r":(?P.*)") ERR_NOPRIVILEGES.tpl = ( ':{c.srv} 481 {c.nick} ' ":Permission Denied- You're not an IRC operator") ERR_NOPRIVILEGES.params = ['srv', 'me', 'data'] ERR_CHANOPRIVSNEEDED = retcode(482) ERR_CHANOPRIVSNEEDED.name = "ERR_CHANOPRIVSNEEDED" ERR_CHANOPRIVSNEEDED.re = ( r"^:(?P\S+) 482 (?P\S+) " r"(?P\S+) :(?P.*)") ERR_CHANOPRIVSNEEDED.tpl = ( ':{c.srv} 482 {c.nick} ' "{channel} :You're not channel operator") ERR_CHANOPRIVSNEEDED.params = ['srv', 'me', 'channel', 'data'] ERR_CANTKILLSERVER = retcode(483) ERR_CANTKILLSERVER.name = "ERR_CANTKILLSERVER" ERR_CANTKILLSERVER.re = ( r"^:(?P\S+) 483 (?P\S+) " r":(?P.*)") ERR_CANTKILLSERVER.tpl = ( ':{c.srv} 483 {c.nick} ' ':You cant kill a server!') ERR_CANTKILLSERVER.params = ['srv', 'me', 'data'] ERR_NOOPERHOST = retcode(491) ERR_NOOPERHOST.name = "ERR_NOOPERHOST" ERR_NOOPERHOST.re = ( r"^:(?P\S+) 491 (?P\S+) " r":(?P.*)") ERR_NOOPERHOST.tpl = ( ':{c.srv} 491 {c.nick} ' ':No O-lines for your host') ERR_NOOPERHOST.params = ['srv', 'me', 'data'] ERR_UMODEUNKNOWNFLAG = retcode(501) ERR_UMODEUNKNOWNFLAG.name = "ERR_UMODEUNKNOWNFLAG" ERR_UMODEUNKNOWNFLAG.re = ( r"^:(?P\S+) 501 (?P\S+) " r":(?P.*)") ERR_UMODEUNKNOWNFLAG.tpl = ( ':{c.srv} 501 {c.nick} ' ':Unknown MODE flag') ERR_UMODEUNKNOWNFLAG.params = ['srv', 'me', 'data'] ERR_USERSDONTMATCH = retcode(502) ERR_USERSDONTMATCH.name = "ERR_USERSDONTMATCH" ERR_USERSDONTMATCH.re = ( r"^:(?P\S+) 502 (?P\S+) " r":(?P.*)") ERR_USERSDONTMATCH.tpl = ( ':{c.srv} 502 {c.nick} ' ':Cant change mode for other users') ERR_USERSDONTMATCH.params = ['srv', 'me', 'data'] RETCODES = { 200: RPL_TRACELINK, 201: RPL_TRACECONNECTING, 202: RPL_TRACEHANDSHAKE, 203: RPL_TRACEUNKNOWN, 204: RPL_TRACEOPERATOR, 205: RPL_TRACEUSER, 206: RPL_TRACESERVER, 208: RPL_TRACENEWTYPE, 211: RPL_STATSLINKINFO, 212: RPL_STATSCOMMANDS, 213: RPL_STATSCLINE, 214: RPL_STATSNLINE, 215: RPL_STATSILINE, 216: RPL_STATSKLINE, 218: RPL_STATSYLINE, 219: RPL_ENDOFSTATS, 221: RPL_UMODEIS, 241: RPL_STATSLLINE, 242: RPL_STATSUPTIME, 243: RPL_STATSOLINE, 244: RPL_STATSHLINE, 251: RPL_LUSERCLIENT, 252: RPL_LUSEROP, 253: RPL_LUSERUNKNOWN, 254: RPL_LUSERCHANNELS, 255: RPL_LUSERME, 256: RPL_ADMINME, 257: RPL_ADMINLOC1, 258: RPL_ADMINLOC2, 259: RPL_ADMINEMAIL, 261: RPL_TRACELOG, 301: RPL_AWAY, 302: RPL_USERHOST, 303: RPL_ISON, 305: RPL_UNAWAY, 306: RPL_NOWAWAY, 311: RPL_WHOISUSER, 312: RPL_WHOISSERVER, 313: RPL_WHOISOPERATOR, 314: RPL_WHOWASUSER, 315: RPL_ENDOFWHO, 317: RPL_WHOISIDLE, 318: RPL_ENDOFWHOIS, 319: RPL_WHOISCHANNELS, 321: RPL_LISTSTART, 322: RPL_LIST, 323: RPL_LISTEND, 324: RPL_CHANNELMODEIS, 331: RPL_NOTOPIC, 332: RPL_TOPIC, 341: RPL_INVITING, 342: RPL_SUMMONING, 351: RPL_VERSION, 352: RPL_WHOREPLY, 353: RPL_NAMREPLY, 364: RPL_LINKS, 365: RPL_ENDOFLINKS, 366: RPL_ENDOFNAMES, 367: RPL_BANLIST, 368: RPL_ENDOFBANLIST, 369: RPL_ENDOFWHOWAS, 371: RPL_INFO, 372: RPL_MOTD, 374: RPL_ENDOFINFO, 375: RPL_MOTDSTART, 376: RPL_ENDOFMOTD, 381: RPL_YOUREOPER, 382: RPL_REHASHING, 391: RPL_TIME, 392: RPL_USERSSTART, 393: RPL_USERS, 394: RPL_ENDOFUSERS, 395: RPL_NOUSERS, 401: ERR_NOSUCHNICK, 402: ERR_NOSUCHSERVER, 403: ERR_NOSUCHCHANNEL, 404: ERR_CANNOTSENDTOCHAN, 405: ERR_TOOMANYCHANNELS, 406: ERR_WASNOSUCHNICK, 407: ERR_TOOMANYTARGETS, 409: ERR_NOORIGIN, 411: ERR_NORECIPIENT, 412: ERR_NOTEXTTOSEND, 413: ERR_NOTOPLEVEL, 414: ERR_WILDTOPLEVEL, 421: ERR_UNKNOWNCOMMAND, 422: ERR_NOMOTD, 423: ERR_NOADMININFO, 431: ERR_NONICKNAMEGIVEN, 432: ERR_ERRONEUSNICKNAME, 433: ERR_NICKNAMEINUSE, 436: ERR_NICKCOLLISION, 441: ERR_USERNOTINCHANNEL, 442: ERR_NOTONCHANNEL, 443: ERR_USERONCHANNEL, 444: ERR_NOLOGIN, 445: ERR_SUMMONDISABLED, 446: ERR_USERSDISABLED, 451: ERR_NOTREGISTERED, 461: ERR_NEEDMOREPARAMS, 462: ERR_ALREADYREGISTRED, 463: ERR_NOPERMFORHOST, 464: ERR_PASSWDMISMATCH, 465: ERR_YOUREBANNEDCREEP, 467: ERR_KEYSET, 471: ERR_CHANNELISFULL, 472: ERR_UNKNOWNMODE, 473: ERR_INVITEONLYCHAN, 474: ERR_BANNEDFROMCHAN, 475: ERR_BADCHANNELKEY, 481: ERR_NOPRIVILEGES, 482: ERR_CHANOPRIVSNEEDED, 483: ERR_CANTKILLSERVER, 491: ERR_NOOPERHOST, 501: ERR_UMODEUNKNOWNFLAG, 502: ERR_USERSDONTMATCH, } ================================================ FILE: irc3/asynchronous.py ================================================ # -*- coding: utf-8 -*- from .compat import asyncio import re class event: iotype = 'in' iscoroutine = True def __init__(self, **kwargs): # kwargs get interpolated into the regex. # Any kwargs not ending in _re get escaped self.meta = kwargs.get('meta') regexp = self.meta['match'].format(**{ k: v if k.endswith('_re') else re.escape(v) for (k, v) in kwargs.items() if k != 'meta' }) self.regexp = regexp regexp = getattr(self.regexp, 're', self.regexp) self.cregexp = re.compile(regexp).match def compile(self, *args, **kwargs): return self.cregexp def __repr__(self): s = getattr(self.regexp, 'name', self.regexp) name = self.__class__.__name__ return ''.format(name, s) def __call__(self, callback): async def wrapper(*args, **kwargs): return await callback(self, *args, **kwargs) self.callback = wrapper return self def default_result_processor(self, results=None, **value): # pragma: no cover value['results'] = results if len(results) == 1: value.update(results[0]) return value def async_events(context, events, send_line=None, process_results=default_result_processor, timeout=30, **params): loop = context.loop task = loop.create_future() # async result results = [] # store events results events_ = [] # reference registered events # async timeout timeout = asyncio.ensure_future( asyncio.sleep(timeout, loop=loop), loop=loop) def end(t=None): """t can be a future (timeout done) or False (result success)""" if not task.done(): # cancel timeout if needed if t is False: timeout.cancel() # detach events context.detach_events(*events_) # clean refs events_[:] = [] # set results task.set_result(process_results(results=results, timeout=bool(t))) # end on timeout timeout.add_done_callback(end) async def callback(e, **kw): """common callback for all events""" results.append(kw) if e.meta.get('multi') is not True: context.detach_events(e) events_.remove(e) if e.meta.get('final') is True: # end on success end(False) events_.extend([event(meta=kw, **params)(callback) for kw in events]) context.attach_events(*events_, insert=True) if send_line: context.send_line(send_line.format(**params)) return task class AsyncEvents: """Asynchronious events""" timeout = 30 send_line = None events = [] def __init__(self, context): self.context = context def process_results(self, results=None, **value): # pragma: no cover """Process results. results is a list of dict catched during event. value is a dict containing some metadata (like timeout=(True/False). """ return default_result_processor(results=results, **value) def __call__(self, **kwargs): """Register events; and callbacks then return a `asyncio.Future`. Events regexp are compiled with `params`""" kwargs.setdefault('timeout', self.timeout) kwargs.setdefault('send_line', self.send_line) kwargs['process_results'] = self.process_results return async_events(self.context, self.events, **kwargs) ================================================ FILE: irc3/base.py ================================================ # -*- coding: utf-8 -*- import os import sys import ssl import signal import logging import logging.config from importlib import metadata from . import utils from . import config from .compat import asyncio from .compat import reload_module from collections import defaultdict version = metadata.version('irc3') class Registry: """Store (and hide from api) plugins events and stuff""" def __init__(self): self.reset(reloading=False) def reset(self, reloading=True): self.events_re = { 'in': [], 'out': [], 'dcc_in': [], 'dcc_out': [], } self.events = { 'in': defaultdict(list), 'out': defaultdict(list), 'dcc_in': defaultdict(list), 'dcc_out': defaultdict(list), } self.scanned = [] self.includes = set() if reloading: self.reloading = self.plugins.copy() else: self.reloading = {} self.plugins = {} def get_event_matches(self, data, iotype='in'): events = self.events[iotype] for regexp, cregexp in self.events_re[iotype]: match = cregexp(data) if match is not None: yield match, events[regexp] class IrcObject: nick = None server = False plugin_category = '__irc3_plugin__' logging_config = config.LOGGING defaults = dict( port=6667, timeout=320, max_lag=60, asynchronous=True, max_length=512, testing=False, ssl=False, ssl_verify=False, encoding='utf8', loop=None, ) def __init__(self, *ini, **config): config['version'] = version self.config = utils.Config(dict(self.defaults, *ini, **config)) logging.config.dictConfig(self.logging_config) if self.server: self.log = logging.getLogger('irc3d') else: self.log = logging.getLogger('irc3.' + (self.nick or 'd')) self.original_nick = self.nick if config.get('verbose') or config.get('debug'): logging.getLogger('irc3').setLevel(logging.DEBUG) logging.getLogger('irc3d').setLevel(logging.DEBUG) else: level = config.get('level') if level is not None: level = getattr(logging, str(level), level) self.log.setLevel(level) self.encoding = self.config['encoding'] self.loop = self.config.loop if self.loop is None: try: self.loop = asyncio.get_event_loop() except RuntimeError: self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.create_task = self.loop.create_task self.registry = Registry() self.include(*self.config.get('includes', [])) def get_plugin(self, ob): plugins = self.registry.plugins includes = self.registry.includes reloading = self.registry.reloading if isinstance(ob, str): ob_name = ob ob = utils.maybedotted(ob_name) if ob_name not in plugins: names = list(plugins) raise LookupError( 'Plugin %s not found in %s' % (ob_name, names)) else: ob_name = ob.__module__ + '.' + ob.__name__ if ob_name not in plugins: self.log.debug("Register plugin '%s'", ob_name) for dotted in getattr(ob, 'requires', []): if dotted not in includes: self.include(dotted) plugins[ob_name] = ob(self) elif ob_name in reloading and hasattr(ob, 'reload'): instance = reloading.pop(ob_name) if instance.__class__ is not ob: self.log.debug("Reloading plugin '%s'", ob_name) plugins[ob_name] = ob.reload(instance) return plugins[ob_name] def recompile(self): events_re = self.registry.events_re for iotype in ('in', 'out'): events = self.registry.events[iotype] for i, (regexp, cregexp) in enumerate(events_re[iotype]): e = events[regexp][0] events_re[i] = (regexp, e.compile(self.config)) def attach_events(self, *events, **kwargs): """Attach one or more events to the bot instance""" reg = self.registry insert = 'insert' in kwargs for e in events: cregexp = e.compile(self.config) regexp = getattr(e.regexp, 're', e.regexp) if regexp not in reg.events[e.iotype]: if insert: reg.events_re[e.iotype].insert(0, (regexp, cregexp)) else: reg.events_re[e.iotype].append((regexp, cregexp)) if insert: reg.events[e.iotype][regexp].insert(0, e) else: reg.events[e.iotype][regexp].append(e) def detach_events(self, *events): """Detach one or more events from the bot instance""" reg = self.registry delete = defaultdict(list) # remove from self.events all_events = reg.events for e in events: regexp = getattr(e.regexp, 're', e.regexp) iotype = e.iotype if e in all_events[iotype].get(regexp, []): all_events[iotype][regexp].remove(e) if not all_events[iotype][regexp]: del all_events[iotype][regexp] # need to delete from self.events_re delete[iotype].append(regexp) # delete from events_re for iotype, regexps in delete.items(): reg.events_re[iotype] = [r for r in reg.events_re[iotype] if r[0] not in regexps] def include(self, *modules, **kwargs): reg = self.registry categories = kwargs.get('venusian_categories', self.venusian_categories) scanner = self.venusian.Scanner(context=self) for module in modules: if module in reg.includes: self.log.warning('%s included twice', module) else: reg.includes.add(module) try: module = utils.maybedotted(module) except LookupError as exc: try: (module,) = metadata.entry_points(group='irc3.loader', name=module) module = module.load() except (ImportError, ValueError): raise exc # we have to manualy check for plugins. venusian no longer # support to attach both a class and methods for klass in list(module.__dict__.values()): if not isinstance(klass, type): continue if klass.__module__ == module.__name__: if getattr(klass, self.plugin_category, False) is True: self.get_plugin(klass) reg.scanned.append((module.__name__, categories)) scanner.scan(module, categories=categories) def reload(self, *modules): """Reload one or more plugins""" self.notify('before_reload') if 'configfiles' in self.config: # reload configfiles self.log.info('Reloading configuration...') cfg = utils.parse_config( self.server and 'server' or 'bot', *self.config['configfiles']) self.config.update(cfg) self.log.info('Reloading python code...') if not modules: modules = self.registry.includes scanned = list(reversed(self.registry.scanned)) # reset includes and events self.registry.reset() to_scan = [] for module_name, categories in scanned: if module_name in modules: module = utils.maybedotted(module_name) reload_module(module) to_scan.append((module_name, categories)) # rescan all modules for module_name, categories in to_scan: self.include(module_name, venusian_categories=categories) self.registry.reloading = {} self.notify('after_reload') def notify(self, event, exc=None, client=None): for p in self.registry.plugins.values(): meth = getattr(p, event, None) if meth is not None: if client is not None: meth(client=client) else: meth() def dispatch(self, data, iotype='in', client=None): str = utils.IrcString create_task = self.create_task call_soon = self.loop.call_soon for match, events in self.registry.get_event_matches(data, iotype): match = match.groupdict() for key, value in match.items(): if value is not None: match[key] = str(value) # backwards compatibility fix for IRCv3.2 tag support: # If no tags (None-value), exclude from dictionary if match.get("tags", True) is None: del match["tags"] if client is not None: # server / dcc chat match['client'] = client for e in events: if e.iscoroutine is True: create_task(e.callback(**match)) else: call_soon(e.async_callback, match) def call_many(self, callback, args): """callback is run with each arg but run a call per second""" if isinstance(callback, str): callback = getattr(self, callback) f = None for arg in args: f = callback(*arg) return f def get_ssl_context(self): if self.config.ssl: # pragma: no cover try: create_default_context = ssl.create_default_context except AttributeError: # py < 2.7.9 return True else: if self.server: context = create_default_context(ssl.Purpose.CLIENT_AUTH) else: context = create_default_context(ssl.Purpose.SERVER_AUTH) verify_mode = self.config.ssl_verify if verify_mode is not False: if not isinstance(verify_mode, int): # CERT_NONE / CERT_OPTIONAL / CERT_REQUIRED verify_mode = getattr(ssl, verify_mode.upper()) if verify_mode == ssl.CERT_NONE: context.check_hostname = False context.verify_mode = verify_mode return context return None def create_connection(self): protocol = utils.maybedotted(self.config.connection) protocol = type(protocol.__name__, (protocol,), {'factory': self}) if self.server: # pragma: no cover self.log.debug('Starting {servername}...'.format(**self.config)) factory = self.loop.create_server else: self.log.debug('Starting {nick}...'.format(**self.config)) factory = self.loop.create_connection if self.config.get('sock_factory'): sock_factory = utils.maybedotted(self.config.sock_factory) args = dict( sock=sock_factory(self, self.config.host, self.config.port), ssl=self.get_ssl_context(), ) if args.get('ssl'): args["server_hostname"] = self.config.host else: args = dict( host=self.config.host, port=self.config.port, ssl=self.get_ssl_context() ) if self.config.get('vhost'): args["local_addr"] = (self.config.vhost, 0) t = asyncio.Task(factory(protocol, **args), loop=self.loop) t.add_done_callback(self.connection_made) return self.loop def add_signal_handlers(self): """Register handlers for UNIX signals (SIGHUP/SIGINT)""" try: self.loop.add_signal_handler(signal.SIGHUP, self.SIGHUP) except (RuntimeError, AttributeError): # pragma: no cover # windows pass try: self.loop.add_signal_handler(signal.SIGINT, self.SIGINT) except (RuntimeError, NotImplementedError): # pragma: no cover # annaconda pass def run(self, forever=True): """start the bot""" loop = self.create_connection() self.add_signal_handlers() if forever: loop.run_forever() @classmethod def from_config(cls, cfg, **kwargs): """return an instance configured with the ``cfg`` dict""" cfg = dict(cfg, **kwargs) pythonpath = cfg.get('pythonpath', []) if 'here' in cfg: pythonpath.append(cfg['here']) for path in pythonpath: sys.path.append(os.path.expanduser(path)) prog = cls.server and 'irc3d' or 'irc3' if cfg.get('debug'): cls.venusian_categories.append(prog + '.debug') if cfg.get('interactive'): # pragma: no cover import irc3.testing context = getattr(irc3.testing, cls.__name__)(**cfg) else: context = cls(**cfg) if cfg.get('raw'): context.include('irc3.plugins.log', venusian_categories=[prog + '.debug']) return context @classmethod def from_argv(cls, argv=None, **kwargs): prog = cls.server and 'irc3d' or 'irc3' doc = """ Run an {prog} instance from a config file Usage: {prog} [options] ... Options: -h, --help Display this help and exit --version Output version information and exit --logdir DIRECTORY Log directory to use instead of stderr --logdate Show datetimes in console output --host HOST Server name or ip --port PORT Server port -v,--verbose Increase verbosity -r,--raw Show raw irc log on the console -d,--debug Add some debug commands/utils -i,--interactive Load a ipython console with a bot instance """.format(prog=prog) if not cls.server: doc += """ --help-page Output a reST page containing commands help """.strip() import os import docopt import textwrap args = argv or sys.argv[1:] args = docopt.docopt(textwrap.dedent(doc), args, version=version) cfg = utils.parse_config( cls.server and 'server' or 'bot', *args['']) cfg.update( verbose=args['--verbose'], debug=args['--debug'], ) cfg.update(kwargs) if args['--host']: # pragma: no cover host = args['--host'] cfg['host'] = host if host in ('127.0.0.1', 'localhost'): cfg['ssl'] = False if args['--port']: # pragma: no cover cfg['port'] = args['--port'] if args['--logdir'] or 'logdir' in cfg: logdir = os.path.expanduser(args['--logdir'] or cfg.get('logdir')) cls.logging_config = config.get_file_config(logdir) if args['--logdate']: # pragma: no cover fmt = cls.logging_config['formatters']['console'] fmt['format'] = config.TIMESTAMPED_FMT if args.get('--help-page'): # pragma: no cover for v in cls.logging_config['handlers'].values(): v['level'] = 'ERROR' if args['--raw']: cfg['raw'] = True context = cls.from_config(cfg) if args.get('--help-page'): # pragma: no cover context.print_help_page() elif args['--interactive']: # pragma: no cover import IPython IPython.embed() sys.exit(0) else: context.run(forever=not bool(kwargs)) if kwargs or argv: return context ================================================ FILE: irc3/compat.py ================================================ # -*- coding: utf-8 -*- import sys import types PY37 = bool(sys.version_info[0:2] >= (3, 7)) try: # pragma: no cover from importlib import reload as reload_module except ImportError: # pragma: no cover from imp import reload as reload_module # NOQA import asyncio from asyncio.queues import Queue as BaseQueue from asyncio.queues import QueueFull # NOQA from asyncio import sleep as _original_sleep from asyncio import wait as _original_wait def __sleep(*args, **kwargs): if PY37 and "loop" in kwargs: del kwargs["loop"] return _original_sleep(*args, **kwargs) def __wait(*args, **kwargs): if PY37 and "loop" in kwargs: del kwargs["loop"] return _original_wait(*args, **kwargs) asyncio.sleep = __sleep asyncio.wait = __wait class Queue(BaseQueue): def __init__(self, *args, **kwargs): if PY37 and "loop" in kwargs: del kwargs["loop"] super().__init__(*args, **kwargs) ================================================ FILE: irc3/config.py ================================================ # -*- coding: utf-8 -*- TIMESTAMPED_FMT = '%(asctime)s %(levelname)-4s %(name)-10s %(message)s' LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'console': { 'format': '%(levelname)-4s %(name)-10s %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S', }, 'console_plus': { 'datefmt': '%Y-%m-%d %H:%M:%S', }, 'irc': { 'format': '%(levelname)-4s %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S', }, 'file': { 'format': TIMESTAMPED_FMT, 'datefmt': '%Y-%m-%d %H:%M:%S', }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stderr', 'formatter': 'console', }, 'irc': { 'level': 'INFO', 'class': 'logging.NullHandler', 'formatter': 'irc', }, 'logs': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'stream': 'ext://sys.stderr', 'formatter': 'console', } }, 'loggers': { 'asyncio': { 'handlers': ['console'], 'level': 'ERROR', 'propagate': False, }, 'trollius': { 'handlers': ['console'], 'level': 'ERROR', 'propagate': False, }, 'requests': { 'handlers': ['console'], 'level': 'ERROR', 'propagate': False, }, 'irc': { 'handlers': ['irc'], 'level': 'INFO', 'propagate': False, }, 'irc3': { 'handlers': ['console'], 'level': 'INFO', 'propagate': False, }, 'irc3d': { 'handlers': ['console'], 'level': 'INFO', 'propagate': False, }, 'raw': { 'handlers': ['logs'], 'level': 'INFO', 'propagate': False, }, } } def get_file_config(logdir='~/.irc3/logs'): import os if not os.path.isdir(logdir): os.makedirs(logdir) config = LOGGING.copy() config['handlers'] = { 'console': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(logdir, 'irc3.log'), 'backupCount': 5, 'maxBytes': 1024 * 5, 'formatter': 'file', }, 'irc': { 'level': 'INFO', 'class': 'logging.NullHandler', 'formatter': 'irc', }, 'logs': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': os.path.join(logdir, 'logs.log'), 'backupCount': 5, 'maxBytes': 1024 * 5, 'formatter': 'file', } } return config ================================================ FILE: irc3/dcc/__init__.py ================================================ # -*- coding: utf-8 -*- from .manager import DCCManager # NOQA from .manager import DCCChat # NOQA from .manager import DCCGet # NOQA from .manager import DCCSend # NOQA ================================================ FILE: irc3/dcc/client.py ================================================ # -*- coding: utf-8 -*- import os import struct from collections import deque from functools import partial from irc3.compat import asyncio class DCCBase(asyncio.Protocol): idle_handle = None idle_timeout = None fd = None def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) self.ready = self.loop.create_future() self.started = self.loop.create_future() self.closed = self.loop.create_future() self.timeout_handle = None def factory(self): return self def connection_made(self, transport): self.transport = transport self.started.set_result(self) def connection_lost(self, exc): self.close(exc) def close(self, result=None): if self.idle_handle is not None: self.idle_handle.cancel() if self.transport: self.transport.close() info = self.bot.dcc.connections[self.type] if self.port in info['masks'][self.mask]: info['total'] -= 1 del info['masks'][self.mask][self.port] if not self.closed.done(): self.closed.set_result(result) self.bot.log.debug('%s closed', self) def set_timeout(self): if self.idle_handle is not None: self.idle_handle.cancel() if self.idle_timeout: self.idle_handle = self.loop.call_later( self.idle_timeout, self.idle_timeout_reached) def idle_timeout_reached(self, *args): if self.type == 'chat': msg = "Your idle is too high. Closing connection." self.send_line(msg) self.close() def __str__(self): return '%s with %s' % (self.__class__.__name__, self.mask) def __repr__(self): return '<%s with %s>' % (self.__class__.__name__, self.mask) class DCCChat(DCCBase): """DCC CHAT implementation""" type = 'chat' ctcp = 'DCC CHAT chat {0.ip} {0.port}' def connection_made(self, transport): super(DCCChat, self).connection_made(transport) self.encoding = getattr(self.bot, 'encoding', 'ascii') self.set_timeout() self.queue = deque() def decode(self, data): """Decode data with bot's encoding""" return data.decode(self.encoding, 'ignore') def data_received(self, data): """data received""" self.set_timeout() data = self.decode(data) if self.queue: data = self.queue.popleft() + data lines = data.replace('\r', '').split('\n') self.queue.append(lines.pop(-1)) for line in lines: self.bot.dispatch(line, iotype='dcc_in', client=self) def write(self, data): if data is not None: data = data.encode(self.encoding) if not data.endswith(b'\r\n'): data = data + b'\r\n' self.transport.write(data) def send_line(self, message): self.write(message) self.bot.dispatch(message, iotype='dcc_out', client=self) def send(self, *messages): for message in messages: self.send_line(message) def action(self, message): message = '\x01ACTION ' + message + '\x01' self.send_line(message) def actions(self, *messages): for message in messages: self.action(message) class DCCGet(DCCBase): """DCC GET implementation""" type = 'get' ctcp = None def connection_made(self, transport): super(DCCGet, self).connection_made(transport) if self.resume: self.bytes_received = self.offset else: self.bytes_received = 0 self.fd = open(self.filepath, 'ab') def data_received(self, data): self.set_timeout() self.fd.write(data) self.bytes_received += len(data) self.transport.write(struct.pack('!I', self.bytes_received)) def close(self, *args, **kwargs): if self.fd: self.fd.close() self.fd = None super(DCCGet, self).close(*args, **kwargs) class DCCSend(DCCBase): """DCC SEND implementation""" type = 'send' ctcp = 'DCC SEND {0.filename_safe} {0.ip} {0.port} {0.filesize}' block_size = 1024 * 64 limit_rate = None filepath = None def connection_made(self, transport): super(DCCSend, self).connection_made(transport) self.delay = 1. / (self.limit_rate / 64.) if self.limit_rate else None socket = self.transport.get_extra_info('socket') self.socket = socket self.sendfile = getattr(self.socket, 'sendfile', None) if self.sendfile: try: self.socket.setblocking(1) except ValueError: # py38 does not allow this. use it anyway self.socket._sock.setblocking(1) self.fd = open(self.filepath, 'rb') self.fd_fileno = self.fd.fileno() # remove existing transport ref. It shouldn't read/write anything transports = getattr(self.loop, '_transports', None) if transports is not None: del self.loop._transports[transport._sock_fd] self.loop.remove_writer(socket) self.loop.add_writer(socket, self.next_chunk) def write(self, *args): # pragma: no cover raise NotImplementedError('write is not available during DCCSend') def send_chunk(self): sent = os.sendfile(self.socket.fileno(), self.fd_fileno, self.offset, self.block_size) return sent def next_chunk(self): try: sent = self.send_chunk() except Exception as e: # pragma: no cover self.bot.log.exception(e) self.fd.close() sent = 0 if sent != 0: self.offset += sent cb = partial(self.loop.add_writer, self.socket, self.next_chunk) if self.delay is not None: self.loop.call_later(self.delay, cb) else: cb() else: self.loop.remove_writer(self.socket) def data_received(self, data): self.set_timeout() bytes_received = ( struct.unpack('!I', data[i:i + 4])[0] for i in range(0, len(data), 4)) for recv in bytes_received: if recv == self.filesize: self.transport.close() def close(self, *args, **kwargs): if self.fd: self.fd.close() self.fd = None super(DCCSend, self).close(*args, **kwargs) ================================================ FILE: irc3/dcc/manager.py ================================================ # -*- coding: utf-8 -*- import os from functools import partial from collections import defaultdict from irc3.utils import slugify from irc3.utils import maybedotted from irc3.dcc.client import DCCChat from irc3.dcc.client import DCCGet from irc3.dcc.client import DCCSend DCC_TYPES = ('chat', 'get', 'send') class DCCManager: """Manage DCC connections""" def __init__(self, bot): self.bot = bot self.loop = bot.loop self.config = cfg = bot.config.get('dcc', {}) self.config.update( send_limit_rate=int(cfg.get('send_limit_rate', 0)), send_block_size=int(cfg.get('send_block_size', DCCSend.block_size)) ) self.connections = {} self.protocols = {} for klass in (DCCChat, DCCGet, DCCSend): n = klass.type self.config.update({ n + '_limit': int(cfg.get(n + '_limit', 100)), n + '_user_limit': int(cfg.get(n + '_user_limit', 1)), n + '_accept_timeout': int(cfg.get(n + '_accept_timeout', 60)), n + '_idle_timeout': int(cfg.get(n + '_idle_timeout', 60 * 5)), }) klass = maybedotted(self.config.get(n + '_protocol', klass)) self.connections[n] = {'total': 0, 'masks': defaultdict(dict)} self.protocols[n] = klass self.seeks = {} def created(self, protocol, future): if protocol.port is None: server = future.result() protocol.port = server.sockets[0].getsockname()[1] protocol.idle_handle = self.loop.call_later( self.config[protocol.type + '_accept_timeout'], server.close) ctcp_msg = protocol.ctcp.format(protocol) self.bot.ctcp(protocol.mask.nick, ctcp_msg) else: transport, protocol = future.result() protocol.idle_handle = self.loop.call_later( self.config[protocol.type + '_accept_timeout'], protocol.close) info = self.connections[protocol.type] info['total'] += 1 info['masks'][protocol.mask][protocol.port] = protocol protocol.ready.set_result(protocol) def create(self, name_or_class, mask, filepath=None, **kwargs): """Create a new DCC connection. Return an ``asyncio.Protocol``""" if isinstance(name_or_class, type): name = name_or_class.type protocol = name_or_class else: name = name_or_class protocol = self.protocols[name] assert name in DCC_TYPES if filepath: kwargs.setdefault('limit_rate', self.config['send_limit_rate']) kwargs['filepath'] = filepath if protocol.type == DCCSend.type: kwargs.setdefault('offset', 0) kwargs.update( filename_safe=slugify(os.path.basename(filepath)), filesize=os.path.getsize(filepath), ) elif protocol.type == DCCGet.type: try: offset = os.path.getsize(filepath) except OSError: offset = 0 kwargs.setdefault('offset', offset) kwargs.setdefault('resume', False) kwargs.setdefault('port', None) f = protocol( mask=mask, ip=int(self.bot.ip), bot=self.bot, loop=self.loop, **kwargs) if kwargs['port']: if self.bot.config.get('dcc_sock_factory'): sock_factory = maybedotted(self.bot.config.dcc_sock_factory) args = dict(sock=sock_factory(self.bot, f.host, f.port)) else: args = dict(host=f.host, port=f.port) task = self.bot.create_task( self.loop.create_connection(f.factory, **args)) task.add_done_callback(partial(self.created, f)) else: task = self.bot.create_task( self.loop.create_server( f.factory, '0.0.0.0', 0, backlog=1)) task.add_done_callback(partial(self.created, f)) return f def resume(self, mask, filename, port, pos): """Resume a DCC send""" self.connections['send']['masks'][mask][port].offset = pos message = 'DCC ACCEPT %s %d %d' % (filename, port, pos) self.bot.ctcp(mask, message) def is_allowed(self, name_or_class, mask): # pragma: no cover """Return True is a new connection is allowed""" if isinstance(name_or_class, type): name = name_or_class.type else: name = name_or_class info = self.connections[name] limit = self.config[name + '_limit'] if limit and info['total'] >= limit: msg = ( "Sorry, there is too much DCC %s active. Please try again " "later.") % name.upper() self.bot.notice(mask, msg) return False if mask not in info['masks']: return True limit = self.config[name + '_user_limit'] if limit and info['masks'][mask] >= limit: msg = ( "Sorry, you have too many DCC %s active. Close the other " "connection(s) or wait a few seconds and try again." ) % name.upper() self.bot.notice(mask, msg) return False return True ================================================ FILE: irc3/dec.py ================================================ # -*- coding: utf-8 -*- from irc3.utils import wraps_with_context from irc3.compat import asyncio import venusian import re def plugin(wrapped): """register a class as plugin""" setattr(wrapped, '__irc3_plugin__', True) setattr(wrapped, '__irc3d_plugin__', False) return wrapped class event: r"""register a method or function an irc event callback:: >>> @event(r'^:\S+ 353 [^&#]+(?P\S+) :(?P.*)') ... def on_names(bot, channel=None, nicknames=None): ... '''this will catch nickname when you enter a channel''' ... print(channel, nicknames.split(':')) The callback can be either a function or a plugin method If you specify the `iotype` parameter to `"out"` then the event will be triggered when the regexp match something **sent** by the bot. For example this event will repeat private messages sent by the bot to the `#irc3` channel:: >>> @event(r'PRIVMSG (?P[^#]+) :(?P.*)', iotype='out') ... def msg3(bot, target=None, data=None): ... bot.privmsg('#irc3', ... '<{0}> {1}: {2}'.format(bot.nick, target, data)) """ venusian = venusian def __init__(self, regexp, callback=None, iotype='in', venusian_category='irc3.rfc1459'): if iotype == 'out': re_out = getattr(regexp, 're_out', None) if re_out is not None: regexp = re_out try: re.compile(getattr(regexp, 're', regexp)) except Exception as e: raise e.__class__(str(e) + ' in ' + getattr(regexp, 're', regexp)) self.regexp = regexp self.iotype = iotype self.callback = callback self.venusian_category = venusian_category self.iscoroutine = False if callback is not None: self.iscoroutine = asyncio.iscoroutinefunction(callback) def async_callback(self, kwargs): # pragma: no cover return self.callback(**kwargs) def compile(self, config): regexp = getattr(self.regexp, 're', self.regexp) if config: regexp = regexp.format(**config) return re.compile(regexp).match def __call__(self, func): def callback(context, name, ob): obj = context.context if info.scope == 'class': self.callback = getattr(obj.get_plugin(ob), func.__name__) else: self.callback = wraps_with_context(func, obj) # a new instance is needed to keep this related to *one* bot # instance e = self.__class__(self.regexp, self.callback, venusian_category=self.venusian_category, iotype=self.iotype) obj.attach_events(e) info = self.venusian.attach(func, callback, category=self.venusian_category) return func def __repr__(self): s = getattr(self.regexp, 'name', self.regexp) name = self.__class__.__name__ return ''.format(name, s, self.callback) def dcc_event(regexp, callback=None, iotype='in', venusian_category='irc3.dcc'): """Work like :class:`~irc3.dec.event` but occurs during DCC CHATs""" return event(regexp, callback=callback, iotype='dcc_' + iotype, venusian_category=venusian_category) def extend(func): """Allow to extend a bot: Create a module with some useful routine: .. literalinclude:: ../examples/myextends.py .. >>> import sys >>> sys.path.append('examples') >>> from irc3 import IrcBot >>> IrcBot.defaults.update(asynchronous=False, testing=True) Now you can use those routine in your bot:: >>> bot = IrcBot() >>> bot.include('myextends') >>> print(bot.my_usefull_function(1)) my_usefull_function(*(1,)) >>> print(bot.my_usefull_method(2)) my_usefull_method(*(2,)) """ def callback(context, name, ob): obj = context.context if info.scope == 'class': instance = obj.get_plugin(ob) f = getattr(instance, func.__name__) else: instance = obj f = func setattr(obj, f.__name__, f.__get__(instance, instance.__class__)) info = venusian.attach(func, callback, category='irc3.extend') return func ================================================ FILE: irc3/plugins/__init__.py ================================================ # -*- coding: utf-8 -*- ================================================ FILE: irc3/plugins/asynchronious.py ================================================ # -*- coding: utf-8 -*- from collections import OrderedDict import re from irc3.asynchronous import AsyncEvents from irc3 import utils from irc3 import dec __doc__ = """ ====================================================== :mod:`irc3.plugins.asynchronious` Asynchronious events ====================================================== This module provide a way to catch data from various predefined events. Usage ===== You'll have to define a subclass of :class:`~irc3.asynchronous.AsyncEvents`: .. literalinclude:: ../../irc3/plugins/asynchronious.py :pyobject: Whois Notice that regexps and send_line contains some `{nick}`. This will be substitued later with the keyword arguments passed to the instance. Then you're able to use it in a plugin: .. code-block:: py class MyPlugin: def __init__(self, bot): self.bot = bot self.whois = Whois(bot) def do_whois(self): # remember {nick} in the regexp? Here it is whois = await self.whois(nick='gawel') if int(whois['idle']) / 60 > 10: self.bot.privmsg('gawel', 'Wake up dude') .. warning:: Your code should always check if the result has been set before timeout by using `result['timeout']` which is True when the bot failed to get a result before 30s (you can override the default value per call) .. warning:: Do not over use this feature. If you're making a lot of calls at the same time you should experience some weird behavior since irc do not allow to identify responses for a command. That's why the exemple use {nick} in the regexp to filter events efficiently. But two concurent call for the same nick can still fail. API === .. autoclass:: irc3.asynchronous.AsyncEvents :members: process_results, __call__ .. autoclass:: Async :members: """ class Whois(AsyncEvents): # the command will fail if we do not have a result after 30s timeout = 20 # send this line before listening to events send_line = 'WHOIS {nick} {nick}' # when those events occurs, we can add them to the result list events = ( # (?i) is for IGNORECASE. This will match either NicK or nick {'match': r"(?i)^:\S+ 301 \S+ {nick} :(?P.*)"}, {'match': r"(?i)^:\S+ 311 \S+ {nick} (?P\S+) " r"(?P\S+) . :(?P.*)"}, {'match': r"(?i)^:\S+ 312 \S+ {nick} (?P\S+) " r":(?P.*)"}, {'match': r"(?i)^:\S+ 317 \S+ {nick} (?P[0-9]+).*"}, {'match': r"(?i)^:\S+ 319 \S+ {nick} :(?P.*)", 'multi': True}, {'match': r"(?i)^:\S+ 330 \S+ {nick} (?P\S+) " r":(?P.*)"}, {'match': r"(?i)^:\S+ 671 \S+ {nick} :(?P.*)"}, # if final=True then a result is returned when the event occurs {'match': r"(?i)^:\S+ (?P(318|401)) \S+ (?P{nick}) :.*", 'final': True}, ) def process_results(self, results=None, **value): """take results list of all events and put them in a dict""" channels = [] for res in results: channels.extend(res.pop('channels', '').split()) value.update(res) value['channels'] = channels value['success'] = value.get('retcode') == '318' return value class WhoChannel(AsyncEvents): send_line = 'WHO {channel}' events = ( {"match": r"(?i)^:\S+ 352 \S+ {channel} (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) " r"(?P\S+) :(?P\S+) (?P.*)", "multi": True}, {"match": r"(?i)^:\S+ (?P(315|401)) \S+ {channel} :.*", "final": True}, ) def process_results(self, results=None, **value): users = [] for res in results: if 'retcode' in res: value.update(res) else: res['mask'] = utils.IrcString( '{nick}!{user}@{host}'.format(**res)) users.append(res) value['users'] = users value['success'] = value.get('retcode') == '315' return value class WhoChannelFlags(AsyncEvents): flags = OrderedDict([ ("u", r"(?P\S+)"), ("i", r"(?P\S+)"), ("h", r"(?P\S+)"), ("s", r"(?P\S+)"), ("n", r"(?P\S+)"), ("a", r"(?P\S+)"), ("r", r":(?P.*)"), ]) send_line = "WHO {channel} c%{flags}" events = ( {"match": r"(?i)^:\S+ (?P(315|401)) \S+ {channel} :.*", "final": True}, ) def process_results(self, results=None, **value): users = [] for res in results: if 'retcode' in res: value.update(res) else: # Works in QuakeNet, don't know about other networks if res.get('account') == '0': res['account'] = None users.append(res) value['users'] = users value['success'] = value.get('retcode') == '315' return value class WhoNick(AsyncEvents): send_line = 'WHO {nick}' events = ( {"match": r"(?i)^:\S+ 352 \S+ (?P\S+) (?P\S+) " r"(?P\S+) (?P\S+) (?P{nick}) " r"(?P\S+) :(?P\S+)\s*(?P.*)"}, {"match": r"(?i)^:\S+ (?P(315|401)) \S+ {nick} :.*", "final": True}, ) def process_results(self, results=None, **value): for res in results: if 'retcode' not in res: res['mask'] = utils.IrcString( '{nick}!{user}@{host}'.format(**res)) value.update(res) value['success'] = value.get('retcode') == '315' return value class IsOn(AsyncEvents): events = ( {"match": r"(?i)^:\S+ 303 \S+ :(?P({nicknames_re}.*|$))", "final": True}, ) def process_results(self, results=None, **value): nicknames = [] for res in results: nicknames.extend(res.pop('nicknames', '').split()) value['names'] = nicknames return value class Topic(AsyncEvents): send_line = 'TOPIC {channel}{topic}' events = ( {"match": (r"(?i)^:\S+ (?P(331|332|TOPIC))" r"(:?\s+\S+\s+|\s+){channel} :(?P.*)"), "final": True}, ) def process_results(self, results=None, **value): for res in results: status = res.get('retcode', '') if status.upper() in ('332', 'TOPIC'): value['topic'] = res.get('topic') else: value['topic'] = None return value class Names(AsyncEvents): send_line = 'NAMES {channel}' events = ( {"match": r"(?i)^:\S+ 353 .*{channel} :(?P.*)", 'multi': True}, {'match': r"(?i)^:\S+ (?P(366|401)) \S+ {channel} :.*", 'final': True}, ) def process_results(self, results=None, **value): nicknames = [] for res in results: nicknames.extend(res.pop('nicknames', '').split()) value['names'] = nicknames value['success'] = value.get('retcode') == '366' return value class ChannelBans(AsyncEvents): send_line = 'MODE {channel} +b' events = ( {"match": r"(?i)^:\S+ 367 \S+ {channel} (?P\S+) (?P\S+) " r"(?P\d+)", "multi": True}, {"match": r"(?i)^:\S+ 368 \S+ {channel} :.*", "final": True}, ) def process_results(self, results=None, **value): bans = [] for res in results: # TODO: fix event so this one isn't needed if not res: continue res['timestamp'] = int(res['timestamp']) bans.append(res) value['bans'] = bans return value class CTCP(AsyncEvents): send_line = 'PRIVMSG {nick} :\x01{ctcp}\x01' events = ( {"match": "(?i):(?P\\S+) NOTICE \\S+ :\x01(?P\\S+) " "(?P.*)\x01", "final": True}, {"match": r"(?i)^:\S+ (?P486) \S+ :(?P.*)", "final": True} ) def process_results(self, results=None, **value): """take results list of all events and return first dict""" for res in results: if 'mask' in res: res['mask'] = utils.IrcString(res['mask']) value['success'] = res.pop('retcode', None) != '486' value.update(res) return value @dec.plugin class Async: """Asynchronious plugin. Extend the bot with some common commands using :class:`~irc3.asynchronous.AsyncEvents` """ def __init__(self, context): self.context = context self.context.async_cmds = self self.async_whois = Whois(context) self.async_who_channel = WhoChannel(context) self.async_who_nick = WhoNick(context) self.async_topic = Topic(context) self.async_ison = IsOn(context) self.async_names = Names(context) self.async_channel_bans = ChannelBans(context) self.async_ctcp = CTCP(context) def async_who_channel_flags(self, channel, flags, timeout): """ Creates and calls a class from WhoChannelFlags with needed match rule for WHO command on channels with flags. """ # Lowercase flags and sort based on WhoChannelFlags.flags, otherwise # resulting dict is wrong. Also join flags if it's a sequence. flags = ''.join([f.lower() for f in WhoChannelFlags.flags if f in flags]) regex = [WhoChannelFlags.flags[f] for f in flags] channel = channel.lower() cls = type( WhoChannelFlags.__name__, (WhoChannelFlags,), {"events": WhoChannelFlags.events + ( {"match": r"(?i)^:\S+ 354 \S+ {0}".format(' '.join(regex)), "multi": True}, )} ) return cls(self.context)(channel=channel, flags=flags, timeout=timeout) @dec.extend def whois(self, nick, timeout=20): """Send a WHOIS and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.whois('gawel') """ return self.async_whois(nick=nick.lower(), timeout=timeout) @dec.extend def who(self, target, flags=None, timeout=20): """Send a WHO and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.who('gawel') result = await bot.async_cmds.who('#irc3') result = await bot.async_cmds.who('#irc3', 'an') # or result = await bot.async_cmds.who('#irc3', ['a', 'n']) """ target = target.lower() if target.startswith('#'): if flags: return self.async_who_channel_flags(channel=target, flags=flags, timeout=timeout) return self.async_who_channel(channel=target, timeout=timeout) else: return self.async_who_nick(nick=target, timeout=timeout) def topic(self, channel, topic=None, timeout=20): if not topic: topic = '' else: topic = ' ' + topic.strip() return self.async_topic(channel=channel, topic=topic, timeout=timeout) @dec.extend def ison(self, *nicknames, **kwargs): """Send a ISON and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.ison('gawel', 'irc3') """ nicknames = [n.lower() for n in nicknames] self.context.send_line('ISON :{0}'.format(' '.join(nicknames))) nicknames = [re.escape(n) for n in nicknames] return self.async_ison(nicknames_re='(%s)' % '|'.join(nicknames), **kwargs) @dec.extend def names(self, channel, timeout=20): """Send a NAMES and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.names('#irc3') """ return self.async_names(channel=channel.lower(), timeout=timeout) @dec.extend def channel_bans(self, channel, timeout=20): """Send a MODE +b and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.channel_bans('#irc3') """ return self.async_channel_bans(channel=channel.lower(), timeout=timeout) @dec.extend def ctcp_async(self, nick, ctcp, timeout=20): """Send a CTCP and return a Future which will contain recieved data: .. code-block:: py result = await bot.async_cmds.ctcp('irc3', 'version') """ return self.async_ctcp(nick=nick, ctcp=ctcp.upper(), timeout=timeout) ================================================ FILE: irc3/plugins/autocommand.py ================================================ import irc3 from irc3 import utils, asyncio import re __doc__ = ''' ================================================== :mod:`irc3.plugins.autocommand` Autocommand plugin ================================================== This plugin allows to send IRC commands to the server after connecting. This could be usable for authorization, cloaking, requesting invite to invite only channel and other use cases. It also allows to set delays between IRC commands via the ``/sleep`` command. .. >>> from irc3.testing import IrcBot >>> from irc3.testing import ini2config Usage:: This example will authorize on Freenode: >>> config = ini2config(""" ... [bot] ... includes = ... irc3.plugins.autocommand ... ... autocommands = ... PRIVMSG NickServ :IDENTIFY nick password ... """) >>> bot = IrcBot(**config) Here's another, more complicated example: >>> config = ini2config(""" ... [bot] ... includes = ... irc3.plugins.autocommand ... ... autocommands = ... AUTH user password ... MODE {nick} +x ... /sleep 2 ... PRIVMSG Q :INVITE #inviteonly ... """) >>> bot = IrcBot(**config) It will authorize on QuakeNet, cloak and request an invite to ``#inviteonly`` after a 2 second delay. ''' class SimpleCommand: def __init__(self, cmd): self.cmd = cmd async def execute(self, bot): send_cmd = self.cmd.format(nick=bot.nick) bot.send_line(send_cmd) class SleepCommand: SLEEP_RE = re.compile(r"^/sleep\s+(?P