summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-03-09 01:21:59 -0700
committerPinapelz <yukais@pinapelz.com>2026-03-09 01:22:15 -0700
commit3c34982281cb6f3a8fc9bfef313e8149cad0cb50 (patch)
treef3df06a90f2d8ca873612b41bff11ab6b6f34024
parent6a31ee36a0660ff17b88b3e3707564ffbd2bdfd4 (diff)
add manual random games func
-rw-r--r--.gitignore2
-rw-r--r--bot.py8
-rw-r--r--db.py167
-rw-r--r--games.py80
-rw-r--r--restaurants.py124
5 files changed, 279 insertions, 102 deletions
diff --git a/.gitignore b/.gitignore
index 20ccad4..65f6b43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@ wheels/
.env
session.txt
*.db
-seed.txt
+seeds
diff --git a/bot.py b/bot.py
index 244ba7e..bc44cb1 100644
--- a/bot.py
+++ b/bot.py
@@ -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()
diff --git a/db.py b/db.py
new file mode 100644
index 0000000..5116708
--- /dev/null
+++ b/db.py
@@ -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
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage