From e3dd9614305ea46d98d422f34209e2c18548cbab Mon Sep 17 00:00:00 2001 From: Speng Reb Date: Thu, 21 May 2026 16:06:00 +0200 Subject: [PATCH] Add example python bot for making shows --- examples/python-show-bot/README.md | 32 ++++++ examples/python-show-bot/bot.py | 116 ++++++++++++++++++++++ examples/python-show-bot/requirements.txt | 23 +++++ 3 files changed, 171 insertions(+) create mode 100644 examples/python-show-bot/README.md create mode 100644 examples/python-show-bot/bot.py create mode 100644 examples/python-show-bot/requirements.txt diff --git a/examples/python-show-bot/README.md b/examples/python-show-bot/README.md new file mode 100644 index 00000000..57ca006f --- /dev/null +++ b/examples/python-show-bot/README.md @@ -0,0 +1,32 @@ +# Python Show Bot Example + +Uses `veretube-bot==0.1.4` with `AsyncBot` and the built-in Shows API helpers. + +## Setup + +```bash +cd examples/python-show-bot +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +Set environment variables: + +- `BOT_TOKEN` (required) +- `CHANNEL` (required) +- `SOCKET_URL` (default: `http://localhost:1337`) +- `API_BASE` (default: `http://localhost:8080/api/v1`) +- `SHOW_TIMEZONE` (default: `UTC`, must be valid IANA timezone) + +Run: + +```bash +python bot.py +``` + +## Chat Commands + +- `!shows` - list shows +- `!mkshow` - create a demo show +- `!runshow ` - run a show immediately diff --git a/examples/python-show-bot/bot.py b/examples/python-show-bot/bot.py new file mode 100644 index 00000000..9297ede6 --- /dev/null +++ b/examples/python-show-bot/bot.py @@ -0,0 +1,116 @@ +""" +Veretube Python Show Bot example (veretube-bot 0.1.4) + +Commands in chat: + !shows -> list existing shows + !mkshow -> create an example show scheduled ~2 minutes from now + !show -> inspect a show from API + !runshow -> trigger immediate run of a show +""" + +from datetime import datetime, timedelta, timezone +import asyncio +import os + +from veretube_bot import AsyncBot, BotAPIError + +BOT_TOKEN = os.getenv("BOT_TOKEN", "TOKEN_HERE") +CHANNEL = os.getenv("CHANNEL", "CHANNEL_NAME_HERE") +SOCKET_URL = os.getenv("SOCKET_URL", "http://localhost:1337") +API_BASE = os.getenv("API_BASE", "http://localhost:8080/api/v1") +SHOW_TIMEZONE = os.getenv("SHOW_TIMEZONE", "UTC") + +bot = AsyncBot( + token=BOT_TOKEN, + channel=CHANNEL, + socket_url=SOCKET_URL, + api_url=API_BASE, +) + + +def create_example_show_payload() -> dict: + # Schedule a couple minutes in the future to make testing easy. + scheduled_for = datetime.now(timezone.utc) + timedelta(minutes=2) + + return { + "name": f"Python Demo Show {scheduled_for.strftime('%H:%M:%S')}", + "scheduled_for": scheduled_for.isoformat(), + "timezone": SHOW_TIMEZONE, + "recurrence": "none", + "fill_mode": "append", + "conflict_mode": "force", + "start_playback": False, + "status": "scheduled", + "playlist": [ + {"type": "yt", "id": "dQw4w9WgXcQ", "pos": "end"}, + {"type": "yt", "id": "9bZkp7q19f0", "pos": "end"}, + ], + } + +@bot.on("chatMsg") +async def on_chat(data): + msg = (data.get("msg") or "").strip() + + if msg == "!shows": + try: + shows = await bot.list_shows() + except BotAPIError as err: + await bot.send_message(f"shows error: {err}") + return + + if not shows: + await bot.send_message("No shows configured") + return + + summary = ", ".join([f"#{s['id']} {s['name']} ({s['status']})" for s in shows[:4]]) + await bot.send_message(f"Shows: {summary}") + + elif msg == "!mkshow": + try: + show = await bot.create_show(create_example_show_payload()) + persisted = await bot.get_show(show["id"]) + await bot.send_message( + f"Created show #{persisted['id']} status={persisted.get('status')} " + f"scheduled_for={persisted.get('scheduled_for')} timezone={persisted.get('timezone')}" + ) + except BotAPIError as err: + await bot.send_message(f"create show error: {err}") + + elif msg.startswith("!runshow "): + parts = msg.split() + if len(parts) != 2 or not parts[1].isdigit(): + await bot.send_message("Usage: !runshow ") + return + + show_id = int(parts[1]) + try: + result = await bot.show_action(show_id, "run") + await bot.send_message(f"Show #{result['id']} run action complete, status={result['status']}") + except BotAPIError as err: + await bot.send_message(f"run show error: {err}") + + elif msg.startswith("!show "): + parts = msg.split() + if len(parts) != 2 or not parts[1].isdigit(): + await bot.send_message("Usage: !show ") + return + + show_id = int(parts[1]) + try: + show = await bot.get_show(show_id) + await bot.send_message( + f"Show #{show['id']}: status={show.get('status')} " + f"scheduled_for={show.get('scheduled_for')} timezone={show.get('timezone')}" + ) + except BotAPIError as err: + await bot.send_message(f"show lookup error: {err}") + + +@bot.on("changeMedia") +async def on_media(data): + title = data.get("title", "(unknown)") + await bot.send_message(f"Now playing: {title}") + + +if __name__ == "__main__": + asyncio.run(bot.run()) diff --git a/examples/python-show-bot/requirements.txt b/examples/python-show-bot/requirements.txt new file mode 100644 index 00000000..41e66e09 --- /dev/null +++ b/examples/python-show-bot/requirements.txt @@ -0,0 +1,23 @@ +aiohappyeyeballs==2.6.2 +aiohttp==3.13.5 +aiosignal==1.4.0 +async-timeout==5.0.1 +attrs==26.1.0 +bidict==0.23.1 +certifi==2026.5.20 +charset-normalizer==3.4.7 +frozenlist==1.8.0 +h11==0.16.0 +idna==3.15 +multidict==6.7.1 +propcache==0.5.2 +python-engineio==4.13.1 +python-socketio==5.16.1 +requests==2.33.1 +simple-websocket==1.1.0 +typing_extensions==4.15.0 +urllib3==2.7.0 +veretube-bot==0.1.4 +websocket-client==1.9.0 +wsproto==1.3.2 +yarl==1.24.2