feat(journal): farm-journal maubot plugin
Records what people did each day, scoped per-room/sender/timestamp. Stored in maubot's per-instance SQLite plugin DB. Commands: - !journal <text> record an entry - !journal show [@u] last 10 entries, optionally filtered by user - !journal today everything logged today (UTC) The command shape requires @command.new(arg_fallthrough=False) so the parent's pass_raw=True `text` argument doesn't swallow subcommand keywords like "show" and "today" — same pattern the upstream maubot reminder plugin uses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
153b164284
3 changed files with 101 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
*.mbp
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
88
journal/journal.py
Normal file
88
journal/journal.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from maubot import MessageEvent, Plugin
|
||||||
|
from maubot.handlers import command
|
||||||
|
from mautrix.util.async_db import Connection, UpgradeTable
|
||||||
|
|
||||||
|
upgrade_table = UpgradeTable()
|
||||||
|
|
||||||
|
|
||||||
|
@upgrade_table.register(description="Initial schema")
|
||||||
|
async def upgrade_v1(conn: Connection) -> None:
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE entries (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user TEXT NOT NULL,
|
||||||
|
room TEXT NOT NULL,
|
||||||
|
ts BIGINT NOT NULL,
|
||||||
|
text TEXT NOT NULL
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
await conn.execute("CREATE INDEX entries_user_ts ON entries (user, ts DESC)")
|
||||||
|
await conn.execute("CREATE INDEX entries_ts ON entries (ts DESC)")
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt(rows) -> str:
|
||||||
|
if not rows:
|
||||||
|
return "No entries."
|
||||||
|
out = []
|
||||||
|
for r in rows:
|
||||||
|
when = datetime.fromtimestamp(r["ts"] / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
|
||||||
|
out.append(f"- **{r['user']}** _({when} UTC)_: {r['text']}")
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
class JournalBot(Plugin):
|
||||||
|
@classmethod
|
||||||
|
def get_db_upgrade_table(cls) -> UpgradeTable:
|
||||||
|
return upgrade_table
|
||||||
|
|
||||||
|
@command.new(
|
||||||
|
"journal",
|
||||||
|
help="Farm journal — record what you did today",
|
||||||
|
require_subcommand=False,
|
||||||
|
arg_fallthrough=False,
|
||||||
|
)
|
||||||
|
@command.argument("text", pass_raw=True, required=False)
|
||||||
|
async def journal(self, evt: MessageEvent, text: str = "") -> None:
|
||||||
|
if not text:
|
||||||
|
await evt.reply(
|
||||||
|
"Usage:\n"
|
||||||
|
"- `!journal <what you did>` — record an entry\n"
|
||||||
|
"- `!journal show [@user]` — last 10 entries (optionally filtered by user)\n"
|
||||||
|
"- `!journal today` — all entries from today"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.database.execute(
|
||||||
|
"INSERT INTO entries (user, room, ts, text) VALUES ($1, $2, $3, $4)",
|
||||||
|
evt.sender, evt.room_id, evt.timestamp, text,
|
||||||
|
)
|
||||||
|
await evt.reply(f"📓 Logged for {evt.sender}.")
|
||||||
|
|
||||||
|
@journal.subcommand("show", help="Show recent entries, optionally filtered by user")
|
||||||
|
@command.argument("user", required=False)
|
||||||
|
async def show(self, evt: MessageEvent, user: Optional[str] = None) -> None:
|
||||||
|
if user:
|
||||||
|
rows = await self.database.fetch(
|
||||||
|
"SELECT user, ts, text FROM entries WHERE user = $1 ORDER BY ts DESC LIMIT 10",
|
||||||
|
user,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
rows = await self.database.fetch(
|
||||||
|
"SELECT user, ts, text FROM entries ORDER BY ts DESC LIMIT 10",
|
||||||
|
)
|
||||||
|
await evt.reply(_fmt(rows))
|
||||||
|
|
||||||
|
@journal.subcommand("today", help="All entries from today (UTC) across users")
|
||||||
|
async def today(self, evt: MessageEvent) -> None:
|
||||||
|
midnight = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
cutoff_ms = int(midnight.timestamp() * 1000)
|
||||||
|
rows = await self.database.fetch(
|
||||||
|
"SELECT user, ts, text FROM entries WHERE ts >= $1 ORDER BY ts ASC",
|
||||||
|
cutoff_ms,
|
||||||
|
)
|
||||||
|
await evt.reply(_fmt(rows))
|
||||||
9
journal/maubot.yaml
Normal file
9
journal/maubot.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
maubot: 0.1.0
|
||||||
|
id: dev.aiolabs.journal
|
||||||
|
version: 0.1.3
|
||||||
|
license: AGPL-3.0-or-later
|
||||||
|
modules:
|
||||||
|
- journal
|
||||||
|
main_class: JournalBot
|
||||||
|
database: true
|
||||||
|
database_type: asyncpg
|
||||||
Loading…
Add table
Add a link
Reference in a new issue