diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-03-09 01:21:59 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-03-09 01:22:15 -0700 |
| commit | 3c34982281cb6f3a8fc9bfef313e8149cad0cb50 (patch) | |
| tree | f3df06a90f2d8ca873612b41bff11ab6b6f34024 | |
| parent | 6a31ee36a0660ff17b88b3e3707564ffbd2bdfd4 (diff) | |
add manual random games func
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | bot.py | 8 | ||||
| -rw-r--r-- | db.py | 167 | ||||
| -rw-r--r-- | games.py | 80 | ||||
| -rw-r--r-- | restaurants.py | 124 |
5 files changed, 279 insertions, 102 deletions
@@ -11,4 +11,4 @@ wheels/ .env session.txt *.db -seed.txt +seeds @@ -3,6 +3,7 @@ import os from dotenv import load_dotenv import constants import restaurants +import games load_dotenv() @@ -10,11 +11,13 @@ HOMESERVER = os.environ.get("HOMESERVER", "") USERNAME = os.environ.get("USERNAME", "") ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN", "") TARGET_ROOM_ID = os.environ.get("TARGET_ROOM_ID", "") +SELF = os.environ.get("SELF") creds = botlib.Creds(homeserver=HOMESERVER, username=USERNAME, access_token=ACCESS_TOKEN) bot = botlib.Bot(creds) restaurants.init_db() +games.init_db() @bot.listener.on_message_event @@ -24,7 +27,10 @@ async def trigger_responses(room, message): if room.room_id != TARGET_ROOM_ID: return - if await restaurants.handle_restaurant_command(bot.api, room.room_id, message.sender, message.body): + if await restaurants.handle_restaurant_command(bot.api, room.room_id, message.sender, message.body, SELF): + return + + if await games.handle_game_command(bot.api, room.room_id, message.sender, message.body, SELF): return msg_text = message.body.strip().lower() @@ -0,0 +1,167 @@ +import sqlite3 +import random +import os +from typing import Optional + +DB_PATH = os.path.join(os.path.dirname(__file__), "bot.db") +SEEDS_DIR = os.path.join(os.path.dirname(__file__), "seeds") + + +def _get_conn() -> sqlite3.Connection: + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def init_db(): + with _get_conn() as conn: + conn.execute(""" + CREATE TABLE IF NOT EXISTS items ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL COLLATE NOCASE, + name TEXT NOT NULL COLLATE NOCASE, + note TEXT NOT NULL DEFAULT '', + added_by TEXT NOT NULL DEFAULT 'unknown', + UNIQUE(category, name) + ) + """) + conn.commit() + + +def load_seed_file(category: str) -> list[tuple[str, str, str]]: + path = os.path.join(SEEDS_DIR, f"{category}.txt") + if not os.path.exists(path): + return [] + + entries: list[tuple[str, str, str]] = [] + with open(path, encoding="utf-8") as f: + lines = [line.rstrip("\n") for line in f.readlines()] + + # Split on blank lines to get per-entry blocks + blocks: list[list[str]] = [] + current: list[str] = [] + for line in lines: + if line.strip() == "": + if current: + blocks.append(current) + current = [] + else: + current.append(line) + if current: + blocks.append(current) + + for block in blocks: + if len(block) < 2: + continue + name = block[0].strip() + if len(block) == 2: + note = "" + added_by = block[1].strip() + else: + note = block[1].strip() + added_by = block[2].strip() + if name: + entries.append((name, note, added_by)) + + return entries + + +def seed(category: str, entries: Optional[list[tuple[str, str, str]]] = None): + if entries is None: + entries = load_seed_file(category) + if not entries: + return + with _get_conn() as conn: + row = conn.execute( + "SELECT COUNT(*) AS cnt FROM items WHERE category = ?", (category,) + ).fetchone() + if row["cnt"] == 0: + conn.executemany( + "INSERT OR IGNORE INTO items (category, name, note, added_by) VALUES (?, ?, ?, ?)", + [(category, name, note, added_by) for name, note, added_by in entries], + ) + conn.commit() + + +def add_item(category: str, name: str, added_by: str, note: str = "") -> str | None: + name = name.strip() + note = note.strip() + category = category.strip() + if not name: + return "❌ Name cannot be empty." + with _get_conn() as conn: + existing = conn.execute( + "SELECT id FROM items WHERE category = ? AND name = ?", (category, name) + ).fetchone() + if existing: + return f"❌ **{name}** is already in the list." + conn.execute( + "INSERT INTO items (category, name, note, added_by) VALUES (?, ?, ?, ?)", + (category, name, note, added_by), + ) + conn.commit() + return None + + +def remove_item(category: str, name: str) -> str | None: + name = name.strip() + if not name: + return "❌ Please provide a name to remove." + with _get_conn() as conn: + existing = conn.execute( + "SELECT id, name FROM items WHERE category = ? AND name = ?", (category, name) + ).fetchone() + if not existing: + return None + actual_name = existing["name"] + conn.execute("DELETE FROM items WHERE id = ?", (existing["id"],)) + conn.commit() + return actual_name + + +def find_item(category: str, keyword: str = "") -> sqlite3.Row | None: + """Return a random item from the category, optionally filtered by keyword in note.""" + keyword = keyword.strip().lower() + with _get_conn() as conn: + if keyword: + rows = conn.execute( + "SELECT name, note, added_by FROM items WHERE category = ? AND LOWER(note) LIKE ?", + (category, f"%{keyword}%"), + ).fetchall() + else: + rows = conn.execute( + "SELECT name, note, added_by FROM items WHERE category = ?", (category,) + ).fetchall() + if not rows: + return None + return random.choice(rows) + + +def migrate_from(old_db_path: str, category: str): + if not os.path.exists(old_db_path): + return + + old_conn = sqlite3.connect(old_db_path) + old_conn.row_factory = sqlite3.Row + try: + tables = old_conn.execute( + "SELECT name FROM sqlite_master WHERE type='table'" + ).fetchall() + if not tables: + return + table_name = tables[0]["name"] + rows = old_conn.execute( + f"SELECT name, note, added_by FROM {table_name}" # noqa: S608 + ).fetchall() + finally: + old_conn.close() + + if rows: + with _get_conn() as conn: + conn.executemany( + "INSERT OR IGNORE INTO items (category, name, note, added_by) VALUES (?, ?, ?, ?)", + [(category, r["name"], r["note"], r["added_by"]) for r in rows], + ) + conn.commit() + + os.remove(old_db_path) diff --git a/games.py b/games.py new file mode 100644 index 0000000..1008516 --- /dev/null +++ b/games.py @@ -0,0 +1,80 @@ +import db + +CATEGORY = "game" + + +def init_db(): + db.init_db() + db.seed(CATEGORY) + + +def _format(row) -> str: + note_line = f"\n- {row['note']}" if row['note'].strip() else "" + return f"{row['name']}{note_line}\nAdded by {row['added_by']}" + + +HELP_TEXT = ( + "🎮 Game bot commands:\n" + " !addgame <name> [| <note>] — add a new game (names must be unique, note is optional)\n" + " !removegame <name> — remove a game by name\n" + " !findgame [keyword] — get a random game (optionally filtered by keyword in note)" +) + + +async def handle_game_command(bot_api, room_id: str, sender: str, body: str, self_name: str): + stripped = body.strip() + lower = stripped.lower() + + # Passive trigger: any message containing "what to play" + if "what to play" in lower and not lower.startswith("!") and self_name in lower: + pick = db.find_item(CATEGORY) + reply = f"Fresh from the list of considerations:\n\n{_format(pick)}" if pick else "No games in the list yet. Use !addgame to add one!" + await bot_api.send_text_message(room_id, reply) + return True + + if not lower.startswith("!"): + return False + + # !addgame <name> | <note> + if lower.startswith("!addgame"): + rest = stripped[len("!addgame"):].strip() + if not rest: + await bot_api.send_text_message( + room_id, + "Usage: !addgame <name> [| <note>]\nExample: !addgame Minecraft | sandbox survival game\nExample: !addgame Minecraft", + ) + return True + if "|" in rest: + name, _, note = rest.partition("|") + else: + name = rest + note = "" + added_by = sender.lstrip("@").split(":")[0] + error = db.add_item(CATEGORY, name, added_by, note=note) + reply = error if error else f"✅ I will add **{name.strip()}** to the list of considerations" + await bot_api.send_text_message(room_id, reply) + return True + + # !removegame <name> + if lower.startswith("!removegame"): + name = stripped[len("!removegame"):].strip() + if not name: + await bot_api.send_text_message( + room_id, + "Usage: !removegame <name>\nExample: !removegame Minecraft", + ) + return True + result = db.remove_item(CATEGORY, name) + if result is None: + reply = f"❌ No game named **{name}** found. Names are case-insensitive." + else: + reply = f"🗑️ Removed **{result}** from the game list." + await bot_api.send_text_message(room_id, reply) + return True + + # !games (help alias) + if lower.startswith("!games"): + await bot_api.send_text_message(room_id, HELP_TEXT) + return True + + return False diff --git a/restaurants.py b/restaurants.py index d501b9b..7c28960 100644 --- a/restaurants.py +++ b/restaurants.py @@ -1,105 +1,20 @@ -import sqlite3 -import random import os +import db -DB_PATH = os.path.join(os.path.dirname(__file__), "restaurants.db") - -SEED_DATA = [ -] - - -def _get_conn() -> sqlite3.Connection: - conn = sqlite3.connect(DB_PATH) - conn.row_factory = sqlite3.Row - return conn +CATEGORY = "restaurant" +OLD_DB_PATH = os.path.join(os.path.dirname(__file__), "restaurants.db") def init_db(): - with _get_conn() as conn: - conn.execute(""" - CREATE TABLE IF NOT EXISTS restaurants ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE COLLATE NOCASE, - note TEXT NOT NULL DEFAULT '', - added_by TEXT NOT NULL DEFAULT 'unknown' - ) - """) - conn.commit() - - row = conn.execute("SELECT COUNT(*) AS cnt FROM restaurants").fetchone() - if row["cnt"] == 0: - conn.executemany( - "INSERT OR IGNORE INTO restaurants (name, note, added_by) VALUES (?, ?, ?)", - SEED_DATA, - ) - conn.commit() + db.init_db() + db.migrate_from(OLD_DB_PATH, CATEGORY) + db.seed(CATEGORY) -def _format_restaurant(row: sqlite3.Row) -> str: +def _format(row) -> str: return f"{row['name']}\n- {row['note']}\nAdded by {row['added_by']}" -def add_restaurant(name: str, note: str, added_by: str) -> str: - name = name.strip() - note = note.strip() - if not name: - return "❌ Restaurant name cannot be empty." - with _get_conn() as conn: - existing = conn.execute( - "SELECT id FROM restaurants WHERE name = ?", (name,) - ).fetchone() - if existing: - return f"❌ **{name}** is already in the list." - conn.execute( - "INSERT INTO restaurants (name, note, added_by) VALUES (?, ?, ?)", - (name, note, added_by), - ) - conn.commit() - return f"✅ Added **{name}** to the restaurant list!" - - -def remove_restaurant(name: str) -> str: - name = name.strip() - if not name: - return "❌ Please provide a restaurant name to remove." - with _get_conn() as conn: - existing = conn.execute( - "SELECT id, name FROM restaurants WHERE name = ?", (name,) - ).fetchone() - if not existing: - return f"❌ No restaurant named **{name}** found. Names are case-insensitive." - actual_name = existing["name"] - conn.execute("DELETE FROM restaurants WHERE id = ?", (existing["id"],)) - conn.commit() - return f"🗑️ Removed **{actual_name}** from the restaurant list." - - - - -def find_restaurant(keyword: str = "") -> str: - keyword = keyword.strip().lower() - with _get_conn() as conn: - if keyword: - rows = conn.execute( - "SELECT name, note, added_by FROM restaurants WHERE LOWER(note) LIKE ?", - (f"%{keyword}%",), - ).fetchall() - else: - rows = conn.execute( - "SELECT name, note, added_by FROM restaurants" - ).fetchall() - - if not rows: - if keyword: - return f"🔍 No restaurants found with **{keyword}** in their notes." - return "No restaurants in the list yet. Use !addrestaurant to add one!" - - pick = random.choice(rows) - label = f'🎲 Random pick (keyword: "{keyword}"):' if keyword else "🎲 Random pick:" - return f"{label}\n\n{_format_restaurant(pick)}" - - - HELP_TEXT = ( "🍽️ Restaurant bot commands:\n" " !addrestaurant <name> | <note> — add a new restaurant (names must be unique)\n" @@ -108,7 +23,7 @@ HELP_TEXT = ( ) -async def handle_restaurant_command(bot_api, room_id: str, sender: str, body: str): +async def handle_restaurant_command(bot_api, room_id: str, sender: str, body: str, self_name: str): """ Returns True if the message was handled as a restaurant command, False otherwise. Call this from your on_message_event listener. @@ -116,9 +31,9 @@ async def handle_restaurant_command(bot_api, room_id: str, sender: str, body: st stripped = body.strip() lower = stripped.lower() - # Passive trigger: any message containing "where to eat" - if "where to eat" in lower and not lower.startswith("!"): - reply = find_restaurant() + if ("where to eat" in lower or "what to eat" in lower) and not lower.startswith("!") and self_name in lower: + pick = db.find_item(CATEGORY) + reply = f"🎲 Random pick:\n\n{_format(pick)}" if pick else "No restaurants in the list yet. Use !addrestaurant to add one!" await bot_api.send_text_message(room_id, reply) return True @@ -139,9 +54,9 @@ async def handle_restaurant_command(bot_api, room_id: str, sender: str, body: st else: name = rest note = "" - # Derive a friendly display name from the Matrix sender ID (@user:server -> user) added_by = sender.lstrip("@").split(":")[0] - reply = add_restaurant(name, note, added_by) + error = db.add_item(CATEGORY, name, added_by, note=note) + reply = error if error else f"✅ Added **{name.strip()}** to the restaurant list!" await bot_api.send_text_message(room_id, reply) return True @@ -154,14 +69,23 @@ async def handle_restaurant_command(bot_api, room_id: str, sender: str, body: st "Usage: !removerestaurant <name>\nExample: !removerestaurant MCDONALDS", ) return True - reply = remove_restaurant(name) + result = db.remove_item(CATEGORY, name) + if result is None: + reply = f"❌ No restaurant named **{name}** found. Names are case-insensitive." + else: + reply = f"🗑️ Removed **{result}** from the restaurant list." await bot_api.send_text_message(room_id, reply) return True # !findrestaurant [keyword] if lower.startswith("!findrestaurant"): keyword = stripped[len("!findrestaurant"):].strip() - reply = find_restaurant(keyword) + pick = db.find_item(CATEGORY, keyword) + if pick is None: + reply = f"🔍 No restaurants found with **{keyword}** in their notes." if keyword else "No restaurants in the list yet. Use !addrestaurant to add one!" + else: + label = f'🎲 Random pick (keyword: "{keyword}"):' if keyword else "🎲 Random pick:" + reply = f"{label}\n\n{_format(pick)}" await bot_api.send_text_message(room_id, reply) return True |
