From 4ca6b3c2c8ffe6fa4afdc4c4d8c69370c9f2c734 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 4 Oct 2019 14:44:19 +0100 Subject: [PATCH 1/6] Add message_responses.py, pass config parameters to callbacks --- README.md | 12 ++++++++++++ bot_commands.py | 4 +++- callbacks.py | 17 ++++++++++++----- main.py | 2 +- message_responses.py | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 message_responses.py diff --git a/README.md b/README.md index 193dcbc..90ce70d 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,18 @@ prefix (defined by the bot's config file), or through a private message directly to the bot. The `process` command is then called for the bot to act on that command. +### `message_responses.py` + +Where responses to messages that are posted in a room (but not necessarily +directed at the bot) are specified. `callbacks.py` will listen for messages in +rooms the bot is in, and upon receiving one will create a new `Message` object +(which contains the message text, amongst other things) and calls `process()` +on it, which can send a message to the room as it sees fit. + +A good example of this would be a Github bot that listens for people mentioning +issue numbers in chat (e.g. "We should fix #123"), and the bot sending messages +to the room immediately afterwards with the issue name and link. + ### `chat_functions.py` A separate file to hold helper methods related to messaging. Mostly just for diff --git a/bot_commands.py b/bot_commands.py index 2315c64..a7bc7c8 100644 --- a/bot_commands.py +++ b/bot_commands.py @@ -2,7 +2,7 @@ from chat_functions import send_text_to_room class Command(object): - def __init__(self, client, store, command, room, event): + def __init__(self, client, store, config, command, room, event): """A command made by a user Args: @@ -10,6 +10,8 @@ class Command(object): store (Storage): Bot storage + config (Config): Bot configuration parameters + command (str): The command and arguments room (nio.rooms.MatrixRoom): The room the command was sent in diff --git a/callbacks.py b/callbacks.py index d75d312..ba1f036 100644 --- a/callbacks.py +++ b/callbacks.py @@ -5,6 +5,7 @@ from bot_commands import Command from nio import ( JoinError, ) +from message_responses import Message import logging logger = logging.getLogger(__name__) @@ -12,18 +13,19 @@ logger = logging.getLogger(__name__) class Callbacks(object): - def __init__(self, client, store, command_prefix): + def __init__(self, client, store, config): """ Args: client (nio.AsyncClient): nio client used to interact with matrix store (Storage): Bot storage - command_prefix (str): The prefix for bot commands + config (Config): Bot configuration parameters """ self.client = client self.store = store - self.command_prefix = command_prefix + self.config = config + self.command_prefix = config.command_prefix async def message(self, room, event): """Callback for when a message event is received @@ -46,16 +48,21 @@ class Callbacks(object): f"{room.user_name(event.sender)}: {msg}" ) - # Ignore message if in a public room without command prefix + # Process as message if in a public room without command prefix has_command_prefix = msg.startswith(self.command_prefix) if not has_command_prefix and not room.is_group: + # General message listener + message = Message(self.client, self.store, self.config, msg, room, event) + await message.process() return + # Otherwise if this is in a 1-1 with the bot or features a command prefix, + # treat it as a command if has_command_prefix: # Remove the command prefix msg = msg[len(self.command_prefix):] - command = Command(self.client, self.store, msg, room, event) + command = Command(self.client, self.store, self.config, msg, room, event) await command.process() async def invite(self, room, event): diff --git a/main.py b/main.py index c4aef8b..531e634 100644 --- a/main.py +++ b/main.py @@ -41,7 +41,7 @@ async def main(): client.access_token = config.access_token # Set up event callbacks - callbacks = Callbacks(client, store, config.command_prefix) + callbacks = Callbacks(client, store, config) client.add_event_callback(callbacks.message, (RoomMessageText,)) client.add_event_callback(callbacks.invite, (InviteEvent,)) diff --git a/message_responses.py b/message_responses.py new file mode 100644 index 0000000..acfd99d --- /dev/null +++ b/message_responses.py @@ -0,0 +1,33 @@ +import logging + +logger = logging.getLogger(__name__) + + +class Message(object): + + def __init__(self, client, store, config, message_content, room, event): + """Initialize a new Message + + Args: + client (nio.AsyncClient): nio client used to interact with matrix + + store (Storage): Bot storage + + config (Config): Bot configuration parameters + + message_content (str): The body of the message + + room (nio.rooms.MatrixRoom): The room the event came from + + event (nio.events.room_events.RoomMessageText): The event defining the message + """ + self.client = client + self.store = store + self.config = config + self.message_content = message_content + self.room = room + self.event = event + + async def process(self): + """Process and possibly respond to the message""" + pass From 3c5602b10b7d97f0a2396faa073e55462670cd05 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 4 Oct 2019 14:46:51 +0100 Subject: [PATCH 2/6] Send m.notice by default --- chat_functions.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/chat_functions.py b/chat_functions.py index 33e0e28..7e32f14 100644 --- a/chat_functions.py +++ b/chat_functions.py @@ -7,7 +7,13 @@ from markdown import markdown logger = logging.getLogger(__name__) -async def send_text_to_room(client, room_id, message, markdown_convert=True): +async def send_text_to_room( + client, + room_id, + message, + notice=True, + markdown_convert=True +): """Send text to a matrix room Args: @@ -17,23 +23,30 @@ async def send_text_to_room(client, room_id, message, markdown_convert=True): message (str): The message content + notice (bool): Whether the message should be sent with an "m.notice" message type + (will not ping users) + markdown_convert (bool): Whether to convert the message content to markdown. Defaults to true. """ - formatted = message + # Determine whether to ping room members or not + msgtype = "m.notice" if notice else "m.text" + + content = { + "msgtype": msgtype, + "format": "org.matrix.custom.html", + "body": message, + } + if markdown_convert: - formatted = markdown(message) + content["formatted_body"] = markdown(message) try: await client.room_send( room_id, "m.room.message", - { - "msgtype": "m.text", - "format": "org.matrix.custom.html", - "body": message, - "formatted_body": formatted, - } + content, ) except SendRetryError: logger.exception(f"Unable to send message response to {room_id}") + From 39b31dd2e5050cb28ec6510d3fdd720eaae380cd Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 4 Oct 2019 14:49:52 +0100 Subject: [PATCH 3/6] Add config.yaml to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ddf6247..9211c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,8 @@ env3/ # Bot local files *.db +# Config file +config.yaml + # Python __pycache__/ From c534d52fc48fe1d5ee360870bf62eff9d462f104 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 4 Oct 2019 14:54:36 +0100 Subject: [PATCH 4/6] Add projects list --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 90ce70d..aacecb5 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ A template for creating bots with matrix-nio can be found [here](https://matrix-nio.readthedocs.io/en/latest/nio.html). +## Projects using nio-template + +* [anoadragon453/msc-chatbot](https://github.com/anoadragon453/msc-chatbot) - A matrix bot for matrix spec proposals + +Want your project listed here? [Edit this +doc!](https://github.com/anoadragon453/nio-template/edit/master/README.md) + ## Project structure ### `main.py` From bb34a4dd8730024be528119a00c488c82617733e Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 7 Oct 2019 15:55:14 +0100 Subject: [PATCH 5/6] Commit to the database after we write to it --- storage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/storage.py b/storage.py index 9f16484..7e0a7da 100644 --- a/storage.py +++ b/storage.py @@ -29,8 +29,8 @@ class Storage(object): logger.info("Performing initial database setup...") # Initialize a connection to the database - conn = sqlite3.connect(self.db_path) - self.cursor = conn.cursor() + self.conn = sqlite3.connect(self.db_path) + self.cursor = self.conn.cursor() # Sync token table self.cursor.execute("CREATE TABLE sync_token (" @@ -42,10 +42,8 @@ class Storage(object): def _run_migrations(self): """Execute database migrations""" # Initialize a connection to the database - conn = sqlite3.connect(self.db_path) - self.cursor = conn.cursor() - - pass + self.conn = sqlite3.connect(self.db_path) + self.cursor = self.conn.cursor() def get_sync_token(self): """Retrieve the next_batch token from the last sync response. @@ -73,3 +71,4 @@ class Storage(object): """ self.cursor.execute("INSERT OR REPLACE INTO sync_token" " (token) VALUES (?)", (token,)) + self.conn.commit() From bcd2cfa895b658fa9742544bab8efef0ed1af1b1 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Mon, 7 Oct 2019 17:11:33 +0100 Subject: [PATCH 6/6] INSERT OR REPLACE SQL on static value --- storage.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/storage.py b/storage.py index 7e0a7da..cc57cc9 100644 --- a/storage.py +++ b/storage.py @@ -6,6 +6,7 @@ latest_db_version = 0 logger = logging.getLogger(__name__) + class Storage(object): def __init__(self, db_path): """Setup the database @@ -34,7 +35,8 @@ class Storage(object): # Sync token table self.cursor.execute("CREATE TABLE sync_token (" - "token TEXT PRIMARY KEY" + "dedupe_id INTEGER PRIMARY KEY, " + "token TEXT NOT NULL" ")") logger.info("Database setup complete") @@ -69,6 +71,6 @@ class Storage(object): Args: token (str): A next_batch token as part of a sync response """ - self.cursor.execute("INSERT OR REPLACE INTO sync_token" - " (token) VALUES (?)", (token,)) + self.cursor.execute("INSERT OR REPLACE INTO sync_token " + "(dedupe_id, token) VALUES (1, ?)", (token,)) self.conn.commit()