diff --git a/.gitignore b/.gitignore index 1ad7dd3..8609b81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.conf *.egg-info/ *.pyc .coverage @@ -6,8 +7,8 @@ .mypy_cache/ .tox/ __pycache__ -*.conf -echobot.py +avatar.png build/ dist/ +echobot.py pip-wheel-metadata/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 452b235..8c7500f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # xbotlib x.x.x (UNRELEASED) +# xbotlib 0.8.1 (2021-01-15) + +- Support avatars ([#17](https://git.autonomic.zone/decentral1se/xbotlib/issues/17)) + # xbotlib 0.8.0 (2021-01-14) - Support not providing response implementation ([#18](https://git.autonomic.zone/decentral1se/xbotlib/issues/18)) diff --git a/README.md b/README.md index aff8376..5740f23 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,13 @@ The bot will then respond to: - `!uptime` commands in direct messages - `:!uptime` commands in group chats (use your own nick) +## Avatars + +By default, `xbotlib` will look for an `avatar.png` file alongside your Python +script which contains your bot implementation. You can also specify another +path using the `--avatar` option on the command-line interface. The images +should ideally have a height of `64` and a width of `64` pixels each. + ## Configure your bot All the ways you can pass configuration details to your bot. diff --git a/xbotlib.py b/xbotlib.py index 04e4c1b..3613e7b 100644 --- a/xbotlib.py +++ b/xbotlib.py @@ -4,6 +4,7 @@ from argparse import ArgumentParser from configparser import ConfigParser from datetime import datetime as dt from getpass import getpass +from imghdr import what from logging import DEBUG, INFO, basicConfig, getLogger from os import environ from os.path import exists @@ -12,6 +13,7 @@ from sys import exit, stdout from humanize import naturaldelta from slixmpp import ClientXMPP +from slixmpp.exceptions import XMPPError class SimpleMessage: @@ -114,6 +116,13 @@ class Bot(ClientXMPP): dest="nick", help="Nickname for the bot account", ) + self.parser.add_argument( + "-av", + "--avatar", + dest="avatar", + help="Avatar for the bot account", + default="avatar.png", + ) self.args = self.parser.parse_args() @@ -185,6 +194,8 @@ class Bot(ClientXMPP): self.password = password self.nick = nick + self.avatar = self.args.avatar + def register_xmpp_event_handlers(self): """Register functions against specific XMPP event handlers.""" self.add_event_handler("session_start", self.session_start) @@ -210,6 +221,27 @@ class Bot(ClientXMPP): """Handle session_start event.""" self.send_presence() self.get_roster() + self.publish_avatar() + + def publish_avatar(self): + """Publish bot avatar.""" + try: + abspath = Path(self.avatar).absolute() + with open(abspath, "rb") as handle: + contents = handle.read() + except IOError: + self.log.info(f"No avatar discovered (tried '{abspath}')") + return + + id = self.plugin["xep_0084"].generate_id(contents) + info = { + "id": id, + "type": f"image/{what('', contents)}", + "bytes": len(contents), + } + + self.plugin["xep_0084"].publish_avatar(contents) + self.plugin["xep_0084"].publish_avatar_metadata(items=[info]) def group_invite(self, message): """Accept invites to group chats.""" @@ -236,6 +268,8 @@ class Bot(ClientXMPP): self.register_plugin("xep_0045") # Multi-User Chat self.register_plugin("xep_0199") # XMPP Ping + self.register_plugin("xep_0084") # User Avatar + def run(self): """Run the bot.""" self.connect()