diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c42b0..452b235 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Remove `--no-input` option and detect it automatically ([#14](https://git.autonomic.zone/decentral1se/xbotlib/issues/14)) - Refer to `jid` as `account` from now on both internally and externally ([#14](https://git.autonomic.zone/decentral1se/xbotlib/issues/14)) - `bot.conf` -> `$name.conf` ([#3](https://git.autonomic.zone/decentral1se/xbotlib/issues/3)) +- Support `!` style commands ([#12](https://git.autonomic.zone/decentral1se/xbotlib/issues/12)) # xbotlib 0.7.1 (2021-01-13) diff --git a/README.md b/README.md index 7061acf..aff8376 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,20 @@ Attributes: - **nickname**: the nickname of the sender - **type**: the type of message (`chat` or `groupchat`) +## Documenting your bot + +Add a `help = "my help"` to your `Bot` class like so. + +```python +class MyBot(Bot): + help = "My help" +``` + +The bot will then respond to: + +- `!uptime` commands in direct messages +- `:!uptime` commands in group chats (use your own nick) + ## Configure your bot All the ways you can pass configuration details to your bot. diff --git a/poetry.lock b/poetry.lock index b781e2e..d968cda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -85,6 +85,17 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" +[[package]] +name = "humanize" +version = "3.2.0" +description = "Python humanize utilities" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + [[package]] name = "importlib-metadata" version = "3.3.0" @@ -259,7 +270,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "077e5cd9d069cbe55c52ca17d9b401be8338b6d9c79126440364c69f4e40c94b" +content-hash = "1ff81969f837d57f7f28e6acdba690cde00aeedef64516953504c6f816ac73dd" [metadata.files] aiodns = [ @@ -324,6 +335,10 @@ flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, ] +humanize = [ + {file = "humanize-3.2.0-py3-none-any.whl", hash = "sha256:d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22"}, + {file = "humanize-3.2.0.tar.gz", hash = "sha256:ab69004895689951b79f2ae4fdd6b8127ff0c180aff107856d5d98119a33f026"}, +] importlib-metadata = [ {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, diff --git a/pyproject.toml b/pyproject.toml index ac0cb64..0db9061 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ keywords = [] [tool.poetry.dependencies] python = "^3.6" slixmpp = "^1.6.0" +humanize = "^3.2.0" [tool.poetry.dev-dependencies] black = "^19.10b0" diff --git a/xbotlib.py b/xbotlib.py index 8b9c6f6..04e4c1b 100644 --- a/xbotlib.py +++ b/xbotlib.py @@ -2,6 +2,7 @@ from argparse import ArgumentParser from configparser import ConfigParser +from datetime import datetime as dt from getpass import getpass from logging import DEBUG, INFO, basicConfig, getLogger from os import environ @@ -9,6 +10,7 @@ from os.path import exists from pathlib import Path from sys import exit, stdout +from humanize import naturaldelta from slixmpp import ClientXMPP @@ -69,6 +71,8 @@ class Bot(ClientXMPP): def __init__(self): self.name = type(self).__name__.lower() + self.start = dt.now() + self.CONFIG_FILE = f"{self.name}.conf" self.parse_arguments() @@ -191,8 +195,14 @@ class Bot(ClientXMPP): def direct_message(self, message): """Handle message event.""" if message["type"] in ("chat", "normal"): + _message = SimpleMessage(message) + + if _message.body.startswith("!"): + self.command(_message, to=_message.sender) + return + try: - self.direct(SimpleMessage(message)) + self.direct(_message) except AttributeError: self.log.info("Bot.direct not implemented") @@ -209,8 +219,14 @@ class Bot(ClientXMPP): """Handle groupchat_message event.""" if message["type"] in ("groupchat", "normal"): if message["mucnick"] != self.config.nick: + _message = SimpleMessage(message) + + if f"{self.nick}:!" in _message.body: + self.command(_message, room=_message.room) + return + try: - self.group(SimpleMessage(message)) + self.group(_message) except AttributeError: self.log.info("Bot.group not implemented") @@ -249,6 +265,25 @@ class Bot(ClientXMPP): self.send_message(**kwargs) + @property + def uptime(self): + """Time since the bot came up.""" + return naturaldelta(self.start - dt.now()) + + def command(self, message, **kwargs): + """Handle "!" style commands with built-in responses.""" + command = message.body.split("!")[-1] + + if command == "uptime": + self.reply(self.uptime, **kwargs) + elif command == "help": + try: + self.reply(self.help, **kwargs) + except AttributeError: + self.reply("No help found 🤔️", **kwargs) + else: + self.log.error(f"'{command}' direct command is not recognised") + class EchoBot(Bot): """Responds with whatever you send. @@ -261,6 +296,8 @@ class EchoBot(Bot): """ + help = "I echo back whatever you send to me 🖖️" + def direct(self, message): """Send back whatever we receive.""" self.reply(message.body, to=message.sender) @@ -282,6 +319,8 @@ class WhisperBot(Bot): """ + help = "I whisper your private messages into group chats 😌️" + def direct(self, message): """Receive private messages and whisper them into group chats.""" self.reply(f"*pssttt...* {message.body}", room=message.room)