Add show API to bot lib
This commit is contained in:
parent
12d01f9d6d
commit
952440bb05
5 changed files with 163 additions and 1 deletions
49
README.md
49
README.md
|
|
@ -175,6 +175,53 @@ bot.clear_playlist() # clear all (rank >= ADMIN)
|
||||||
|
|
||||||
**Media types:** `yt` YouTube, `sc` SoundCloud, `tw` Twitch stream, `tc` Twitch clip, `vm` Vimeo, `dm` Dailymotion, `fi` direct file URL, `cu` custom embed.
|
**Media types:** `yt` YouTube, `sc` SoundCloud, `tw` Twitch stream, `tc` Twitch clip, `vm` Vimeo, `dm` Dailymotion, `fi` direct file URL, `cu` custom embed.
|
||||||
|
|
||||||
|
## Shows
|
||||||
|
|
||||||
|
Show endpoints manage scheduled playlist runs. They support bot Bearer auth and session auth.
|
||||||
|
|
||||||
|
```python
|
||||||
|
shows = bot.list_shows() # rank >= MOD
|
||||||
|
show = bot.get_show(show_id) # rank >= MOD
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"name": "Friday Prime",
|
||||||
|
"scheduled_for": "2026-05-22T19:00:00.000Z",
|
||||||
|
"timezone": "America/New_York",
|
||||||
|
"recurrence": "weekly",
|
||||||
|
"fill_mode": "replace",
|
||||||
|
"conflict_mode": "force",
|
||||||
|
"start_playback": True,
|
||||||
|
"playlist": [
|
||||||
|
{"type": "yt", "id": "dQw4w9WgXcQ", "pos": "end"}
|
||||||
|
],
|
||||||
|
"status": "scheduled",
|
||||||
|
}
|
||||||
|
|
||||||
|
created = bot.create_show(payload) # rank >= MOD
|
||||||
|
updated = bot.update_show(show_id, payload) # rank >= MOD
|
||||||
|
bot.delete_show(show_id) # rank >= ADMIN
|
||||||
|
|
||||||
|
bot.show_action(show_id, "pause") # rank >= MOD
|
||||||
|
bot.show_action(show_id, "resume") # rank >= MOD
|
||||||
|
bot.show_action(show_id, "schedule") # rank >= MOD
|
||||||
|
bot.show_action(show_id, "run") # rank >= ADMIN
|
||||||
|
bot.show_action(show_id, "cancel") # rank >= ADMIN
|
||||||
|
```
|
||||||
|
|
||||||
|
Create/update payload constraints:
|
||||||
|
- `timezone`: required non-empty IANA time zone region string from the IANA Time Zone Database (for example `Europe/Berlin`, `America/New_York`)
|
||||||
|
- `recurrence`: `none` | `daily` | `weekly`
|
||||||
|
- `fill_mode`: `append` | `replace`
|
||||||
|
- `conflict_mode`: `force` | `skip`
|
||||||
|
- `playlist`: required non-empty array of media entries with `type`, `id`, and optional `pos` (`next` | `end`)
|
||||||
|
- `status`: `draft` | `scheduled` | `paused` | `completed` | `failed` | `canceled` (`running` is internal)
|
||||||
|
|
||||||
|
Action payload schema:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "action": "run" }
|
||||||
|
```
|
||||||
|
|
||||||
## Moderation
|
## Moderation
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -216,6 +263,8 @@ Every REST endpoint is also accessible on `bot.api` if you need something not co
|
||||||
playlist = bot.api.get_playlist() # { items, currentIndex, locked }
|
playlist = bot.api.get_playlist() # { items, currentIndex, locked }
|
||||||
bot.api.skip_to(uid)
|
bot.api.skip_to(uid)
|
||||||
bot.api.set_user_rank("Alice", 2)
|
bot.api.set_user_rank("Alice", 2)
|
||||||
|
bot.api.list_shows()
|
||||||
|
bot.api.show_action(show_id, "run")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Error handling
|
## Error handling
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "veretube-bot"
|
name = "veretube-bot"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
description = "Python bot library for veretube sync channels"
|
description = "Python bot library for veretube sync channels"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,69 @@ class BotAPI:
|
||||||
def update_settings(self, **kwargs) -> None:
|
def update_settings(self, **kwargs) -> None:
|
||||||
"""Update one or more channel settings. Requires rank >= OWNER and channel active."""
|
"""Update one or more channel settings. Requires rank >= OWNER and channel active."""
|
||||||
self._request("PUT", "/settings", json=kwargs)
|
self._request("PUT", "/settings", json=kwargs)
|
||||||
|
|
||||||
|
# ── Shows ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _validate_show_payload(self, payload: dict) -> None:
|
||||||
|
timezone = payload.get("timezone")
|
||||||
|
if not isinstance(timezone, str) or not timezone.strip():
|
||||||
|
raise ValueError("timezone is required and must be a non-empty IANA timezone string")
|
||||||
|
|
||||||
|
recurrence = payload.get("recurrence")
|
||||||
|
if recurrence is not None and recurrence not in {"none", "daily", "weekly"}:
|
||||||
|
raise ValueError("recurrence must be one of: none, daily, weekly")
|
||||||
|
|
||||||
|
fill_mode = payload.get("fill_mode")
|
||||||
|
if fill_mode is not None and fill_mode not in {"append", "replace"}:
|
||||||
|
raise ValueError("fill_mode must be one of: append, replace")
|
||||||
|
|
||||||
|
conflict_mode = payload.get("conflict_mode")
|
||||||
|
if conflict_mode is not None and conflict_mode not in {"force", "skip"}:
|
||||||
|
raise ValueError("conflict_mode must be one of: force, skip")
|
||||||
|
|
||||||
|
status = payload.get("status")
|
||||||
|
if status is not None and status not in {"draft", "scheduled", "paused", "completed", "failed", "canceled"}:
|
||||||
|
raise ValueError("status must be one of: draft, scheduled, paused, completed, failed, canceled")
|
||||||
|
|
||||||
|
playlist = payload.get("playlist")
|
||||||
|
if not isinstance(playlist, list) or not playlist:
|
||||||
|
raise ValueError("playlist is required and must be a non-empty list")
|
||||||
|
for index, item in enumerate(playlist):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
raise ValueError(f"playlist[{index}] must be an object")
|
||||||
|
if not item.get("type") or not item.get("id"):
|
||||||
|
raise ValueError(f"playlist[{index}] must include non-empty type and id")
|
||||||
|
pos = item.get("pos")
|
||||||
|
if pos is not None and pos not in {"next", "end"}:
|
||||||
|
raise ValueError(f"playlist[{index}].pos must be one of: next, end")
|
||||||
|
|
||||||
|
def list_shows(self) -> list:
|
||||||
|
"""List channel shows. Requires rank >= MOD."""
|
||||||
|
return self._request("GET", "/shows")
|
||||||
|
|
||||||
|
def get_show(self, show_id: int | str) -> dict:
|
||||||
|
"""Get a single show by id. Requires rank >= MOD."""
|
||||||
|
return self._request("GET", f"/shows/{show_id}")
|
||||||
|
|
||||||
|
def create_show(self, payload: dict) -> dict:
|
||||||
|
"""Create a show. Requires rank >= MOD."""
|
||||||
|
self._validate_show_payload(payload)
|
||||||
|
return self._request("POST", "/shows", json=payload)
|
||||||
|
|
||||||
|
def update_show(self, show_id: int | str, payload: dict) -> dict:
|
||||||
|
"""Update a show by id. Requires rank >= MOD."""
|
||||||
|
self._validate_show_payload(payload)
|
||||||
|
return self._request("PUT", f"/shows/{show_id}", json=payload)
|
||||||
|
|
||||||
|
def delete_show(self, show_id: int | str) -> None:
|
||||||
|
"""Delete a show by id. Requires rank >= ADMIN."""
|
||||||
|
self._request("DELETE", f"/shows/{show_id}")
|
||||||
|
|
||||||
|
def show_action(self, show_id: int | str, action: str) -> dict:
|
||||||
|
"""
|
||||||
|
Run a show action.
|
||||||
|
pause/resume/schedule require rank >= MOD. run/cancel require rank >= ADMIN.
|
||||||
|
"""
|
||||||
|
if action not in {"pause", "resume", "schedule", "run", "cancel"}:
|
||||||
|
raise ValueError("action must be one of: pause, resume, schedule, run, cancel")
|
||||||
|
return self._request("POST", f"/shows/{show_id}/action", json={"action": action})
|
||||||
|
|
|
||||||
|
|
@ -246,3 +246,21 @@ class AsyncBot:
|
||||||
|
|
||||||
async def clear_playlist(self):
|
async def clear_playlist(self):
|
||||||
await asyncio.to_thread(self.api.clear_playlist)
|
await asyncio.to_thread(self.api.clear_playlist)
|
||||||
|
|
||||||
|
async def list_shows(self) -> list:
|
||||||
|
return await asyncio.to_thread(self.api.list_shows)
|
||||||
|
|
||||||
|
async def get_show(self, show_id: int | str) -> dict:
|
||||||
|
return await asyncio.to_thread(self.api.get_show, show_id)
|
||||||
|
|
||||||
|
async def create_show(self, payload: dict) -> dict:
|
||||||
|
return await asyncio.to_thread(self.api.create_show, payload)
|
||||||
|
|
||||||
|
async def update_show(self, show_id: int | str, payload: dict) -> dict:
|
||||||
|
return await asyncio.to_thread(self.api.update_show, show_id, payload)
|
||||||
|
|
||||||
|
async def delete_show(self, show_id: int | str):
|
||||||
|
await asyncio.to_thread(self.api.delete_show, show_id)
|
||||||
|
|
||||||
|
async def show_action(self, show_id: int | str, action: str) -> dict:
|
||||||
|
return await asyncio.to_thread(self.api.show_action, show_id, action)
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,35 @@ class Bot:
|
||||||
"""Clear the entire playlist. Requires rank >= ADMIN."""
|
"""Clear the entire playlist. Requires rank >= ADMIN."""
|
||||||
self.api.clear_playlist()
|
self.api.clear_playlist()
|
||||||
|
|
||||||
|
# ── Shows ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def list_shows(self) -> list:
|
||||||
|
"""List scheduled shows. Requires rank >= MOD."""
|
||||||
|
return self.api.list_shows()
|
||||||
|
|
||||||
|
def get_show(self, show_id: int | str) -> dict:
|
||||||
|
"""Get a single show by id. Requires rank >= MOD."""
|
||||||
|
return self.api.get_show(show_id)
|
||||||
|
|
||||||
|
def create_show(self, payload: dict) -> dict:
|
||||||
|
"""Create a show. Requires rank >= MOD."""
|
||||||
|
return self.api.create_show(payload)
|
||||||
|
|
||||||
|
def update_show(self, show_id: int | str, payload: dict) -> dict:
|
||||||
|
"""Update a show by id. Requires rank >= MOD."""
|
||||||
|
return self.api.update_show(show_id, payload)
|
||||||
|
|
||||||
|
def delete_show(self, show_id: int | str):
|
||||||
|
"""Delete a show by id. Requires rank >= ADMIN."""
|
||||||
|
self.api.delete_show(show_id)
|
||||||
|
|
||||||
|
def show_action(self, show_id: int | str, action: str) -> dict:
|
||||||
|
"""
|
||||||
|
Run show action.
|
||||||
|
pause/resume/schedule require rank >= MOD. run/cancel require rank >= ADMIN.
|
||||||
|
"""
|
||||||
|
return self.api.show_action(show_id, action)
|
||||||
|
|
||||||
# ── Emotes ────────────────────────────────────────────────────────────────
|
# ── Emotes ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def get_emotes(self) -> list:
|
def get_emotes(self) -> list:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue