From 0aef57b5d667f9973853476250eb6aaec1f0a4e2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 27 Mar 2022 20:03:21 +0300 Subject: [PATCH] Add simple database example --- examples/LICENSE | 2 +- examples/README.md | 1 + examples/config/maubot.yaml | 2 +- examples/database/storagebot.py | 72 +++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 examples/database/storagebot.py diff --git a/examples/LICENSE b/examples/LICENSE index bfdfe68..a4b60f3 100644 --- a/examples/LICENSE +++ b/examples/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Tulir Asokan +Copyright (c) 2022 Tulir Asokan 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 diff --git a/examples/README.md b/examples/README.md index 1837fec..2efabca 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,3 +4,4 @@ All examples are published under the [MIT license](LICENSE). * [Hello World](helloworld/) - Very basic event handling bot that responds "Hello, World!" to all messages. * [Echo bot](https://github.com/maubot/echo) - Basic command handling bot with !echo and !ping commands * [Config example](config/) - Simple example of using a config file +* [Database example](database/) - Simple example of using a database diff --git a/examples/config/maubot.yaml b/examples/config/maubot.yaml index 89ae153..8ab36a9 100644 --- a/examples/config/maubot.yaml +++ b/examples/config/maubot.yaml @@ -1,5 +1,5 @@ maubot: 0.1.0 -id: xyz.maubot.databasebot +id: xyz.maubot.configurablebot version: 2.0.0 license: MIT modules: diff --git a/examples/database/storagebot.py b/examples/database/storagebot.py new file mode 100644 index 0000000..786bba5 --- /dev/null +++ b/examples/database/storagebot.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from mautrix.util.async_db import UpgradeTable, Connection +from maubot import Plugin, MessageEvent +from maubot.handlers import command + +upgrade_table = UpgradeTable() + + +@upgrade_table.register(description="Initial revision") +async def upgrade_v1(conn: Connection) -> None: + await conn.execute( + """CREATE TABLE stored_data ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )""" + ) + + +@upgrade_table.register(description="Remember user who added value") +async def upgrade_v2(conn: Connection) -> None: + await conn.execute("ALTER TABLE stored_data ADD COLUMN creator TEXT") + + +class StorageBot(Plugin): + @command.new() + async def storage(self, evt: MessageEvent) -> None: + pass + + @storage.subcommand(help="Store a value") + @command.argument("key") + @command.argument("value", pass_raw=True) + async def put(self, evt: MessageEvent, key: str, value: str) -> None: + q = """ + INSERT INTO stored_data (key, value, creator) VALUES ($1, $2, $3) + ON CONFLICT (key) DO UPDATE SET value=excluded.value, creator=excluded.creator + """ + await self.database.execute(q, key, value, evt.sender) + await evt.reply(f"Inserted {key} into the database") + + @storage.subcommand(help="Get a value from the storage") + @command.argument("key") + async def get(self, evt: MessageEvent, key: str) -> None: + q = "SELECT key, value, creator FROM stored_data WHERE LOWER(key)=LOWER($1)" + row = await self.database.fetchrow(q, key) + if row: + key = row["key"] + value = row["value"] + creator = row["creator"] + await evt.reply(f"`{key}` stored by {creator}:\n\n```\n{value}\n```") + else: + await evt.reply(f"No data stored under `{key}` :(") + + @storage.subcommand(help="List keys in the storage") + @command.argument("prefix", required=False) + async def list(self, evt: MessageEvent, prefix: str | None) -> None: + q = "SELECT key, creator FROM stored_data WHERE key LIKE $1" + rows = await self.database.fetch(q, prefix + "%") + prefix_reply = f" starting with `{prefix}`" if prefix else "" + if len(rows) == 0: + await evt.reply(f"Nothing{prefix_reply} stored in database :(") + else: + formatted_data = "\n".join( + f"* `{row['key']}` stored by {row['creator']}" for row in rows + ) + await evt.reply( + f"Found {len(rows)} keys{prefix_reply} in database:\n\n{formatted_data}" + ) + + @classmethod + def get_db_upgrade_table(cls) -> UpgradeTable | None: + return upgrade_table