Upload to pypi

This commit is contained in:
Speng Reb 2026-05-05 01:17:24 +02:00
parent 175448d390
commit f6fc9ee7a2
4 changed files with 289 additions and 1 deletions

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# build output — generated by `python -m build`, uploaded via twine, not committed
dist/
build/
# packaging metadata — generated automatically, not edited by hand
*.egg-info/
# virtual environments
.venv/
venv/
env/
# Python cache
__pycache__/
*.pyc
*.pyo
# test / coverage output
.pytest_cache/
.coverage
htmlcov/
# editor
.vscode/
.idea/
*.swp

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 veretube contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

221
README.md Normal file
View file

@ -0,0 +1,221 @@
# veretube-bot
Python library for writing bots on sync 4.0 aka veretube channels.
Bots connect over **socket.io** for real-time events (chat, user list, playlist changes) and use a **REST API** for commands (queue media, kick/ban, manage emotes, read/write settings). Both surfaces are covered by this library.
## Installation
```bash
pip install veretube-bot
```
## Getting a token
Tokens are issued in the channel settings modal on the **Bots** tab. You need at least moderator rank to see it. Fill in a name and rank, click **Issue Token**, and copy the token immediately — it starts with `cbt_` and is only shown once.
## Quick start
```python
from veretube_bot import Bot
bot = Bot(
token="cbt_...",
channel="mychannel",
socket_url="http://your-server:1337", # socket.io port (default 1337)
api_url="http://your-server:8080/api/v1", # HTTP port (default 8080)
)
@bot.on("chatMsg")
def on_chat(data):
if data["msg"] == "!hello":
bot.send_message("Hello!", to=data["username"])
if data["msg"] == "!np":
if bot.now_playing:
bot.send_message(f"Now playing: {bot.now_playing['title']}")
@bot.on("changeMedia")
def on_media(data):
print(f"Now playing: {data['title']}")
bot.run() # connect and block until disconnected
```
## Connection
```python
Bot(
token, # str — bot token starting with 'cbt_'
channel, # str — channel name (lowercase, no '#')
socket_url, # str — socket.io server URL (uses io.port, not the HTTP port)
api_url, # str — REST API base URL, e.g. http://host:8080/api/v1
reconnection=False, # set True to reconnect automatically on drop
reconnection_delay=3, # seconds between reconnect attempts
)
```
| Method | Description |
|--------|-------------|
| `bot.run()` | Connect and block until disconnected |
| `bot.connect()` | Open the connection (returns immediately) |
| `bot.wait()` | Block until disconnected (call after `connect()`) |
| `bot.disconnect()` | Close the connection |
By default `reconnection=False` — the expectation is that an external process supervisor (systemd, supervisord, etc.) handles restarts. Pass `reconnection=True` for bots that should self-recover.
## Event handling
Register handlers with `@bot.on(event_name)`. Multiple handlers for the same event are all called. Exceptions in a handler are logged and skipped so one bad handler doesn't affect the others.
```python
@bot.on("chatMsg")
def on_chat(data):
print(data["username"], data["msg"])
@bot.on("userLeave")
def on_leave(data):
print(f"{data['name']} left")
```
### Socket events
| Event | Payload | Description |
|-------|---------|-------------|
| `connect` | `None` | Socket connected |
| `disconnect` | reason string or `None` | Socket disconnected |
| `connect_error` | error object | Connection failed |
| `login` | `{ success, name, guest }` | Server accepted the token |
| `chatMsg` | `{ username, msg, meta, time }` | Chat message sent |
| `pm` | `{ username, msg, meta }` | Private message received |
| `userlist` | `[{ name, rank, meta }, ...]` | Full user list on join |
| `addUser` | `{ name, rank, meta }` | User joined |
| `userLeave` | `{ name }` | User left |
| `setUserMeta` | `{ name, meta }` | User AFK/muted state changed |
| `setUserRank` | `{ name, rank }` | User rank changed |
| `changeMedia` | `{ id, type, title, seconds, ... }` | New video started |
| `playlist` | `[{ uid, media, queueby, temp }, ...]` | Full playlist on join |
| `queue` | `{ item, after }` | Item added to playlist |
| `delete` | `{ uid }` | Playlist item removed |
| `channelOpts` | settings dict | Channel options updated |
| `clearchat` | `None` | Chat cleared by a moderator |
| `errorMsg` | `{ msg }` | Error from the server |
| `kick` | `{ reason }` | Bot was kicked |
| `announcement` | `{ title, text }` | Server-wide announcement |
| `updateEmote` | `{ name, image }` | Emote added or changed |
| `removeEmote` | `{ name }` | Emote removed |
`meta.is_bot` is `True` in `chatMsg` and userlist payloads for bots.
## In-memory state
These are updated automatically from socket events before your handlers are called:
```python
bot.users # list of { name, rank, meta } — current user list
bot.now_playing # { id, type, title, seconds, ... } or None
bot.playlist # list of playlist items
bot.channel_opts # channel options dict
```
Look up a specific user:
```python
user = bot.get_user("Alice") # returns dict or None
```
## Chat
```python
bot.send_message("Hello!")
bot.send_message("Hey!", to="Alice") # sends "Alice: Hey!"
bot.send_action("waves") # sends "/me waves"
bot.send_pm("Alice", "Hello privately")
```
## Playlist
```python
bot.queue("dQw4w9WgXcQ", "yt") # add to end (rank >= MOD)
bot.queue("dQw4w9WgXcQ", "yt", "next") # add as next up
bot.skip() # skip to next item (rank >= MOD)
bot.skip_to(uid) # jump to specific uid (rank >= MOD)
bot.delete_item(uid) # remove by uid (rank >= ADMIN)
bot.shuffle_playlist() # shuffle (rank >= ADMIN)
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.
## Moderation
```python
bot.kick("BadUser") # rank >= MOD
bot.kick("BadUser", reason="Spamming")
bot.ban("BadUser") # rank >= ADMIN
bot.ban("BadUser", reason="Evading")
bot.unban("BadUser") # rank >= ADMIN
bot.set_rank("Alice", Rank.MOD) # rank >= OWNER
```
## Emotes
Emote endpoints read/write the database directly and work even when the channel is offline.
```python
emotes = bot.get_emotes() # list of { name, image, source }
bot.add_emote("KEKW", "https://...") # rank >= OWNER
bot.update_emote("KEKW", image="https://...")
bot.update_emote("KEKW", new_name="KEKWait")
bot.delete_emote("KEKW") # rank >= OWNER
```
## Settings
```python
settings = bot.get_settings() # rank >= OWNER, channel must be active
bot.update_settings(pagetitle="Now Playing: Chill Beats")
bot.update_settings(allow_voteskip=False, afk_timeout=300)
```
Available setting keys: `allow_voteskip`, `allow_dupes`, `voteskip_ratio`, `maxlength`, `playlist_max_duration_per_user`, `afk_timeout`, `enable_link_regex`, `chat_antiflood`, `chat_antiflood_burst`, `chat_antiflood_sustained`, `new_user_chat_delay`, `new_user_chat_link_delay`, `pagetitle`, `password`, `externalcss`, `externaljs`, `show_public`, `torbanned`, `block_anonymous_users`, `allow_ascii_control`, `playlist_max_per_user`.
## Direct REST access
Every REST endpoint is also accessible on `bot.api` if you need something not covered by the shortcuts:
```python
playlist = bot.api.get_playlist() # { items, currentIndex, locked }
bot.api.skip_to(uid)
bot.api.set_user_rank("Alice", 2)
```
## Error handling
REST calls raise `BotAPIError` on failure:
```python
from veretube_bot import Bot, BotAPIError
@bot.on("chatMsg")
def on_chat(data):
if data["msg"].startswith("!add "):
_, type, id = data["msg"].split(None, 2)
try:
bot.queue(id, type)
except BotAPIError as e:
bot.send_message(f"Error: {e}") # e.status_code has the HTTP code
```
## Rank constants
```python
from veretube_bot import Rank
Rank.MOD # 2
Rank.ADMIN # 3
Rank.OWNER # 4
Rank.CREATOR # 5
```
A bot's effective rank is capped at the rank of the user who issued its token.

View file

@ -6,14 +6,34 @@ build-backend = "setuptools.build_meta"
name = "veretube-bot" name = "veretube-bot"
version = "0.1.0" version = "0.1.0"
description = "Python bot library for veretube sync channels" description = "Python bot library for veretube sync channels"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10" requires-python = ">=3.10"
authors = [
{ name = "veretube", email = "faceeatingtumor@gmail.com" },
]
keywords = ["veretube", "cytube", "bot", "chat", "socketio"]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
"Topic :: Communications :: Chat",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [ dependencies = [
"python-socketio[client]>=5.0", "python-socketio[client]>=5.0",
"requests>=2.28", "requests>=2.28",
] ]
[project.optional-dependencies] [project.optional-dependencies]
dev = ["pytest"] dev = ["pytest", "build", "twine"]
[project.urls]
Homepage = "https://veretium.com"
Source = "https://veretium.com/w0zard/veretube_bot_lib"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["."] where = ["."]