vox-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vox_sdk-0.1.0/PKG-INFO +16 -0
- vox_sdk-0.1.0/README.md +106 -0
- vox_sdk-0.1.0/pyproject.toml +38 -0
- vox_sdk-0.1.0/setup.cfg +4 -0
- vox_sdk-0.1.0/setup.py +43 -0
- vox_sdk-0.1.0/src/vox_sdk/__init__.py +15 -0
- vox_sdk-0.1.0/src/vox_sdk/_media.py +6 -0
- vox_sdk-0.1.0/src/vox_sdk/api/__init__.py +1 -0
- vox_sdk-0.1.0/src/vox_sdk/api/auth.py +153 -0
- vox_sdk-0.1.0/src/vox_sdk/api/bots.py +63 -0
- vox_sdk-0.1.0/src/vox_sdk/api/channels.py +196 -0
- vox_sdk-0.1.0/src/vox_sdk/api/dms.py +102 -0
- vox_sdk-0.1.0/src/vox_sdk/api/e2ee.py +85 -0
- vox_sdk-0.1.0/src/vox_sdk/api/embeds.py +19 -0
- vox_sdk-0.1.0/src/vox_sdk/api/emoji.py +66 -0
- vox_sdk-0.1.0/src/vox_sdk/api/federation.py +84 -0
- vox_sdk-0.1.0/src/vox_sdk/api/files.py +49 -0
- vox_sdk-0.1.0/src/vox_sdk/api/invites.py +47 -0
- vox_sdk-0.1.0/src/vox_sdk/api/members.py +61 -0
- vox_sdk-0.1.0/src/vox_sdk/api/messages.py +125 -0
- vox_sdk-0.1.0/src/vox_sdk/api/moderation.py +78 -0
- vox_sdk-0.1.0/src/vox_sdk/api/roles.py +106 -0
- vox_sdk-0.1.0/src/vox_sdk/api/search.py +19 -0
- vox_sdk-0.1.0/src/vox_sdk/api/server.py +52 -0
- vox_sdk-0.1.0/src/vox_sdk/api/sync.py +33 -0
- vox_sdk-0.1.0/src/vox_sdk/api/users.py +90 -0
- vox_sdk-0.1.0/src/vox_sdk/api/voice.py +104 -0
- vox_sdk-0.1.0/src/vox_sdk/api/webhooks.py +59 -0
- vox_sdk-0.1.0/src/vox_sdk/client.py +225 -0
- vox_sdk-0.1.0/src/vox_sdk/errors.py +80 -0
- vox_sdk-0.1.0/src/vox_sdk/gateway.py +285 -0
- vox_sdk-0.1.0/src/vox_sdk/http.py +130 -0
- vox_sdk-0.1.0/src/vox_sdk/models/__init__.py +232 -0
- vox_sdk-0.1.0/src/vox_sdk/models/auth.py +64 -0
- vox_sdk-0.1.0/src/vox_sdk/models/base.py +7 -0
- vox_sdk-0.1.0/src/vox_sdk/models/bots.py +65 -0
- vox_sdk-0.1.0/src/vox_sdk/models/channels.py +52 -0
- vox_sdk-0.1.0/src/vox_sdk/models/dms.py +14 -0
- vox_sdk-0.1.0/src/vox_sdk/models/e2ee.py +35 -0
- vox_sdk-0.1.0/src/vox_sdk/models/emoji.py +25 -0
- vox_sdk-0.1.0/src/vox_sdk/models/enums.py +24 -0
- vox_sdk-0.1.0/src/vox_sdk/models/errors.py +92 -0
- vox_sdk-0.1.0/src/vox_sdk/models/events.py +670 -0
- vox_sdk-0.1.0/src/vox_sdk/models/federation.py +35 -0
- vox_sdk-0.1.0/src/vox_sdk/models/files.py +13 -0
- vox_sdk-0.1.0/src/vox_sdk/models/invites.py +22 -0
- vox_sdk-0.1.0/src/vox_sdk/models/members.py +26 -0
- vox_sdk-0.1.0/src/vox_sdk/models/messages.py +51 -0
- vox_sdk-0.1.0/src/vox_sdk/models/moderation.py +44 -0
- vox_sdk-0.1.0/src/vox_sdk/models/roles.py +14 -0
- vox_sdk-0.1.0/src/vox_sdk/models/server.py +55 -0
- vox_sdk-0.1.0/src/vox_sdk/models/sync.py +22 -0
- vox_sdk-0.1.0/src/vox_sdk/models/users.py +43 -0
- vox_sdk-0.1.0/src/vox_sdk/models/voice.py +36 -0
- vox_sdk-0.1.0/src/vox_sdk/pagination.py +70 -0
- vox_sdk-0.1.0/src/vox_sdk/permissions.py +263 -0
- vox_sdk-0.1.0/src/vox_sdk/rate_limit.py +113 -0
- vox_sdk-0.1.0/src/vox_sdk.egg-info/PKG-INFO +16 -0
- vox_sdk-0.1.0/src/vox_sdk.egg-info/SOURCES.txt +70 -0
- vox_sdk-0.1.0/src/vox_sdk.egg-info/dependency_links.txt +1 -0
- vox_sdk-0.1.0/src/vox_sdk.egg-info/requires.txt +13 -0
- vox_sdk-0.1.0/src/vox_sdk.egg-info/top_level.txt +1 -0
- vox_sdk-0.1.0/tests/test_api_groups.py +1484 -0
- vox_sdk-0.1.0/tests/test_client.py +204 -0
- vox_sdk-0.1.0/tests/test_errors.py +113 -0
- vox_sdk-0.1.0/tests/test_gateway.py +572 -0
- vox_sdk-0.1.0/tests/test_http.py +343 -0
- vox_sdk-0.1.0/tests/test_media_video.py +266 -0
- vox_sdk-0.1.0/tests/test_models.py +175 -0
- vox_sdk-0.1.0/tests/test_pagination.py +191 -0
- vox_sdk-0.1.0/tests/test_permissions.py +193 -0
- vox_sdk-0.1.0/tests/test_rate_limit.py +143 -0
vox_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vox-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client SDK for the Vox protocol
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: websockets>=13.0
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Requires-Dist: zstandard>=0.23
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
15
|
+
Provides-Extra: media
|
|
16
|
+
Requires-Dist: vox-media>=0.1; extra == "media"
|
vox_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Vox Python SDK
|
|
2
|
+
|
|
3
|
+
Python client SDK for the Vox protocol.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install vox-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import asyncio
|
|
15
|
+
from vox_sdk import Client
|
|
16
|
+
|
|
17
|
+
async def main():
|
|
18
|
+
async with Client("https://vox.example.com") as client:
|
|
19
|
+
# Login
|
|
20
|
+
await client.login("alice", "password123")
|
|
21
|
+
|
|
22
|
+
# Send a message
|
|
23
|
+
msg = await client.messages.send(feed_id=1, body="Hello from the SDK!")
|
|
24
|
+
|
|
25
|
+
# List members
|
|
26
|
+
members = await client.members.list()
|
|
27
|
+
print(f"{len(members.items)} members online")
|
|
28
|
+
|
|
29
|
+
asyncio.run(main())
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Gateway Events
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import asyncio
|
|
36
|
+
from vox_sdk import Client, GatewayClient
|
|
37
|
+
|
|
38
|
+
async def main():
|
|
39
|
+
async with Client("https://vox.example.com") as client:
|
|
40
|
+
await client.login("alice", "password123")
|
|
41
|
+
|
|
42
|
+
info = await client.server.gateway_info()
|
|
43
|
+
gw = GatewayClient(info.url, client.http.token)
|
|
44
|
+
|
|
45
|
+
@gw.on("message_create")
|
|
46
|
+
async def on_message(event):
|
|
47
|
+
print(f"[{event.feed_id}] {event.author_id}: {event.body}")
|
|
48
|
+
|
|
49
|
+
@gw.on("presence_update")
|
|
50
|
+
async def on_presence(event):
|
|
51
|
+
print(f"User {event.user_id} is now {event.status}")
|
|
52
|
+
|
|
53
|
+
# run() auto-reconnects on recoverable errors
|
|
54
|
+
await gw.run()
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Error Handling
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from vox_sdk import VoxHTTPError, VoxNetworkError
|
|
63
|
+
from vox_sdk.errors import VoxGatewayError
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
await client.messages.send(feed_id=1, body="hello")
|
|
67
|
+
except VoxHTTPError as e:
|
|
68
|
+
print(f"API error: {e.status} {e.code}")
|
|
69
|
+
except VoxNetworkError as e:
|
|
70
|
+
print(f"Network error: {e}")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Groups
|
|
74
|
+
|
|
75
|
+
The client exposes these API groups as properties:
|
|
76
|
+
|
|
77
|
+
| Property | Description |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `client.auth` | Login, register, MFA, sessions |
|
|
80
|
+
| `client.messages` | Send, edit, delete, reactions, pins |
|
|
81
|
+
| `client.channels` | Feeds, rooms, categories, threads |
|
|
82
|
+
| `client.members` | List, get, ban, kick, update |
|
|
83
|
+
| `client.roles` | Create, assign, permissions |
|
|
84
|
+
| `client.server` | Server info, layout, limits |
|
|
85
|
+
| `client.users` | Profiles, friends, blocks |
|
|
86
|
+
| `client.invites` | Create, resolve, delete |
|
|
87
|
+
| `client.dms` | Open, send, close DMs |
|
|
88
|
+
| `client.voice` | Join, leave, mute, stage |
|
|
89
|
+
| `client.webhooks` | Create, execute webhooks |
|
|
90
|
+
| `client.bots` | Commands, interactions |
|
|
91
|
+
| `client.files` | Upload, download, delete |
|
|
92
|
+
| `client.emoji` | Emoji and stickers |
|
|
93
|
+
| `client.e2ee` | Prekeys, devices, MLS |
|
|
94
|
+
| `client.moderation` | Reports, audit log |
|
|
95
|
+
| `client.federation` | Cross-server federation |
|
|
96
|
+
| `client.search` | Message search |
|
|
97
|
+
| `client.sync` | Offline sync |
|
|
98
|
+
| `client.embeds` | URL embed resolution |
|
|
99
|
+
|
|
100
|
+
## Models
|
|
101
|
+
|
|
102
|
+
All response models are re-exported from `vox_sdk.models`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from vox_sdk.models import MessageResponse, UserResponse, FeedResponse
|
|
106
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vox-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client SDK for the Vox protocol"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.27",
|
|
12
|
+
"websockets>=13.0",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"zstandard>=0.23",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0",
|
|
20
|
+
"pytest-asyncio>=0.24",
|
|
21
|
+
"pytest-cov>=5.0",
|
|
22
|
+
"ruff>=0.6",
|
|
23
|
+
]
|
|
24
|
+
media = [
|
|
25
|
+
"vox-media>=0.1",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
target-version = "py311"
|
|
33
|
+
line-length = 100
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
addopts = "--cov=vox_sdk --cov-report=term-missing"
|
vox_sdk-0.1.0/setup.cfg
ADDED
vox_sdk-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Custom build hooks — clean stale native extensions before editable installs.
|
|
2
|
+
|
|
3
|
+
maturin develop can leave .so/.pyd files in src/ that shadow pure-Python
|
|
4
|
+
wrappers and cause hard-to-debug import failures. This hooks into both
|
|
5
|
+
``pip install -e .`` and ``python setup.py develop`` to wipe them first.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import glob
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from setuptools import setup
|
|
12
|
+
from setuptools.command.build_py import build_py
|
|
13
|
+
from setuptools.command.develop import develop
|
|
14
|
+
|
|
15
|
+
_STALE_PATTERNS = ("src/**/*.so", "src/**/*.dylib", "src/**/*.pyd")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _clean_stale_extensions() -> None:
|
|
19
|
+
root = os.path.dirname(os.path.abspath(__file__))
|
|
20
|
+
for pattern in _STALE_PATTERNS:
|
|
21
|
+
for path in glob.glob(os.path.join(root, pattern), recursive=True):
|
|
22
|
+
print(f"removing stale native extension: {path}")
|
|
23
|
+
os.remove(path)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CleanDevelop(develop):
|
|
27
|
+
def run(self) -> None:
|
|
28
|
+
_clean_stale_extensions()
|
|
29
|
+
super().run()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CleanBuildPy(build_py):
|
|
33
|
+
def run(self) -> None:
|
|
34
|
+
_clean_stale_extensions()
|
|
35
|
+
super().run()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
setup(
|
|
39
|
+
cmdclass={
|
|
40
|
+
"develop": CleanDevelop,
|
|
41
|
+
"build_py": CleanBuildPy,
|
|
42
|
+
},
|
|
43
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Vox Client SDK — Python client for the Vox protocol."""
|
|
2
|
+
|
|
3
|
+
from vox_sdk.client import Client
|
|
4
|
+
from vox_sdk.gateway import GatewayClient
|
|
5
|
+
from vox_sdk.errors import VoxHTTPError, VoxGatewayError, VoxNetworkError
|
|
6
|
+
from vox_sdk.permissions import Permissions
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Client",
|
|
10
|
+
"GatewayClient",
|
|
11
|
+
"Permissions",
|
|
12
|
+
"VoxHTTPError",
|
|
13
|
+
"VoxGatewayError",
|
|
14
|
+
"VoxNetworkError",
|
|
15
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SDK API method groups."""
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Auth API methods."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from vox_sdk.models.auth import (
|
|
8
|
+
LoginResponse,
|
|
9
|
+
MFARequiredResponse,
|
|
10
|
+
MFASetupConfirmResponse,
|
|
11
|
+
MFASetupResponse,
|
|
12
|
+
MFAStatusResponse,
|
|
13
|
+
RegisterResponse,
|
|
14
|
+
SessionListResponse,
|
|
15
|
+
SuccessResponse,
|
|
16
|
+
WebAuthnChallengeResponse,
|
|
17
|
+
WebAuthnCredentialResponse,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from vox_sdk.http import HTTPClient
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AuthAPI:
|
|
25
|
+
"""Methods for /api/v1/auth endpoints."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
28
|
+
self._http = http
|
|
29
|
+
|
|
30
|
+
async def register(
|
|
31
|
+
self, username: str, password: str, display_name: str | None = None
|
|
32
|
+
) -> RegisterResponse:
|
|
33
|
+
payload: dict[str, Any] = {"username": username, "password": password}
|
|
34
|
+
if display_name is not None:
|
|
35
|
+
payload["display_name"] = display_name
|
|
36
|
+
r = await self._http.post("/api/v1/auth/register", json=payload)
|
|
37
|
+
return RegisterResponse.model_validate(r.json())
|
|
38
|
+
|
|
39
|
+
async def login(self, username: str, password: str) -> LoginResponse | MFARequiredResponse:
|
|
40
|
+
r = await self._http.post(
|
|
41
|
+
"/api/v1/auth/login", json={"username": username, "password": password}
|
|
42
|
+
)
|
|
43
|
+
data = r.json()
|
|
44
|
+
if data.get("mfa_required"):
|
|
45
|
+
return MFARequiredResponse.model_validate(data)
|
|
46
|
+
return LoginResponse.model_validate(data)
|
|
47
|
+
|
|
48
|
+
async def login_2fa(
|
|
49
|
+
self,
|
|
50
|
+
mfa_ticket: str,
|
|
51
|
+
method: str,
|
|
52
|
+
code: str | None = None,
|
|
53
|
+
assertion: dict | None = None,
|
|
54
|
+
) -> LoginResponse:
|
|
55
|
+
payload: dict[str, Any] = {"mfa_ticket": mfa_ticket, "method": method}
|
|
56
|
+
if code is not None:
|
|
57
|
+
payload["code"] = code
|
|
58
|
+
if assertion is not None:
|
|
59
|
+
payload["assertion"] = assertion
|
|
60
|
+
r = await self._http.post("/api/v1/auth/login/2fa", json=payload)
|
|
61
|
+
return LoginResponse.model_validate(r.json())
|
|
62
|
+
|
|
63
|
+
async def login_webauthn_challenge(self, username: str) -> WebAuthnChallengeResponse:
|
|
64
|
+
r = await self._http.post(
|
|
65
|
+
"/api/v1/auth/login/webauthn/challenge", json={"username": username}
|
|
66
|
+
)
|
|
67
|
+
return WebAuthnChallengeResponse.model_validate(r.json())
|
|
68
|
+
|
|
69
|
+
async def login_webauthn(
|
|
70
|
+
self,
|
|
71
|
+
username: str,
|
|
72
|
+
challenge_id: str,
|
|
73
|
+
client_data_json: str,
|
|
74
|
+
authenticator_data: str,
|
|
75
|
+
signature: str,
|
|
76
|
+
credential_id: str,
|
|
77
|
+
user_handle: str | None = None,
|
|
78
|
+
) -> LoginResponse:
|
|
79
|
+
payload: dict[str, Any] = {
|
|
80
|
+
"username": username,
|
|
81
|
+
"challenge_id": challenge_id,
|
|
82
|
+
"client_data_json": client_data_json,
|
|
83
|
+
"authenticator_data": authenticator_data,
|
|
84
|
+
"signature": signature,
|
|
85
|
+
"credential_id": credential_id,
|
|
86
|
+
}
|
|
87
|
+
if user_handle is not None:
|
|
88
|
+
payload["user_handle"] = user_handle
|
|
89
|
+
r = await self._http.post("/api/v1/auth/login/webauthn", json=payload)
|
|
90
|
+
return LoginResponse.model_validate(r.json())
|
|
91
|
+
|
|
92
|
+
async def login_federation(self, federation_token: str) -> LoginResponse:
|
|
93
|
+
r = await self._http.post(
|
|
94
|
+
"/api/v1/auth/login/federation", json={"federation_token": federation_token}
|
|
95
|
+
)
|
|
96
|
+
return LoginResponse.model_validate(r.json())
|
|
97
|
+
|
|
98
|
+
async def logout(self) -> None:
|
|
99
|
+
await self._http.post("/api/v1/auth/logout")
|
|
100
|
+
|
|
101
|
+
async def mfa_status(self) -> MFAStatusResponse:
|
|
102
|
+
r = await self._http.get("/api/v1/auth/2fa")
|
|
103
|
+
return MFAStatusResponse.model_validate(r.json())
|
|
104
|
+
|
|
105
|
+
async def mfa_setup(self, method: str) -> MFASetupResponse:
|
|
106
|
+
r = await self._http.post("/api/v1/auth/2fa/setup", json={"method": method})
|
|
107
|
+
return MFASetupResponse.model_validate(r.json())
|
|
108
|
+
|
|
109
|
+
async def mfa_setup_confirm(
|
|
110
|
+
self,
|
|
111
|
+
setup_id: str,
|
|
112
|
+
code: str | None = None,
|
|
113
|
+
attestation: dict | None = None,
|
|
114
|
+
credential_name: str | None = None,
|
|
115
|
+
) -> MFASetupConfirmResponse:
|
|
116
|
+
payload: dict[str, Any] = {"setup_id": setup_id}
|
|
117
|
+
if code is not None:
|
|
118
|
+
payload["code"] = code
|
|
119
|
+
if attestation is not None:
|
|
120
|
+
payload["attestation"] = attestation
|
|
121
|
+
if credential_name is not None:
|
|
122
|
+
payload["credential_name"] = credential_name
|
|
123
|
+
r = await self._http.post("/api/v1/auth/2fa/setup/confirm", json=payload)
|
|
124
|
+
return MFASetupConfirmResponse.model_validate(r.json())
|
|
125
|
+
|
|
126
|
+
async def mfa_remove(
|
|
127
|
+
self,
|
|
128
|
+
method: str,
|
|
129
|
+
code: str | None = None,
|
|
130
|
+
assertion: dict | None = None,
|
|
131
|
+
) -> SuccessResponse:
|
|
132
|
+
payload: dict[str, Any] = {"method": method}
|
|
133
|
+
if code is not None:
|
|
134
|
+
payload["code"] = code
|
|
135
|
+
if assertion is not None:
|
|
136
|
+
payload["assertion"] = assertion
|
|
137
|
+
r = await self._http.delete("/api/v1/auth/2fa", json=payload)
|
|
138
|
+
return SuccessResponse.model_validate(r.json())
|
|
139
|
+
|
|
140
|
+
async def list_webauthn_credentials(self) -> list[WebAuthnCredentialResponse]:
|
|
141
|
+
r = await self._http.get("/api/v1/auth/webauthn/credentials")
|
|
142
|
+
return [WebAuthnCredentialResponse.model_validate(c) for c in r.json()]
|
|
143
|
+
|
|
144
|
+
async def delete_webauthn_credential(self, credential_id: str) -> SuccessResponse:
|
|
145
|
+
r = await self._http.delete(f"/api/v1/auth/webauthn/credentials/{credential_id}")
|
|
146
|
+
return SuccessResponse.model_validate(r.json())
|
|
147
|
+
|
|
148
|
+
async def list_sessions(self) -> SessionListResponse:
|
|
149
|
+
r = await self._http.get("/api/v1/auth/sessions")
|
|
150
|
+
return SessionListResponse.model_validate(r.json())
|
|
151
|
+
|
|
152
|
+
async def revoke_session(self, session_id: int) -> None:
|
|
153
|
+
await self._http.delete(f"/api/v1/auth/sessions/{session_id}")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Bots API methods."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from vox_sdk.models.bots import CommandListResponse, OkResponse
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from vox_sdk.http import HTTPClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BotsAPI:
|
|
14
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
|
|
17
|
+
async def register_commands(
|
|
18
|
+
self, user_id: int, commands: list[dict[str, Any]]
|
|
19
|
+
) -> OkResponse:
|
|
20
|
+
r = await self._http.put(
|
|
21
|
+
f"/api/v1/bots/{user_id}/commands", json={"commands": commands}
|
|
22
|
+
)
|
|
23
|
+
return OkResponse.model_validate(r.json())
|
|
24
|
+
|
|
25
|
+
async def list_bot_commands(self, user_id: int) -> CommandListResponse:
|
|
26
|
+
r = await self._http.get(f"/api/v1/bots/{user_id}/commands")
|
|
27
|
+
return CommandListResponse.model_validate(r.json())
|
|
28
|
+
|
|
29
|
+
async def deregister_commands(
|
|
30
|
+
self, user_id: int, command_names: list[str]
|
|
31
|
+
) -> OkResponse:
|
|
32
|
+
r = await self._http.delete(
|
|
33
|
+
f"/api/v1/bots/{user_id}/commands", json={"command_names": command_names}
|
|
34
|
+
)
|
|
35
|
+
return OkResponse.model_validate(r.json())
|
|
36
|
+
|
|
37
|
+
async def list_commands(self) -> CommandListResponse:
|
|
38
|
+
r = await self._http.get("/api/v1/commands")
|
|
39
|
+
return CommandListResponse.model_validate(r.json())
|
|
40
|
+
|
|
41
|
+
async def respond_to_interaction(
|
|
42
|
+
self,
|
|
43
|
+
interaction_id: str,
|
|
44
|
+
*,
|
|
45
|
+
body: str | None = None,
|
|
46
|
+
embeds: list[dict] | None = None,
|
|
47
|
+
components: list[dict] | None = None,
|
|
48
|
+
ephemeral: bool = False,
|
|
49
|
+
) -> None:
|
|
50
|
+
payload: dict[str, Any] = {"ephemeral": ephemeral}
|
|
51
|
+
if body is not None:
|
|
52
|
+
payload["body"] = body
|
|
53
|
+
if embeds is not None:
|
|
54
|
+
payload["embeds"] = embeds
|
|
55
|
+
if components is not None:
|
|
56
|
+
payload["components"] = components
|
|
57
|
+
await self._http.post(f"/api/v1/interactions/{interaction_id}/response", json=payload)
|
|
58
|
+
|
|
59
|
+
async def component_interaction(self, msg_id: int, component_id: str) -> None:
|
|
60
|
+
await self._http.post(
|
|
61
|
+
"/api/v1/interactions/component",
|
|
62
|
+
json={"msg_id": msg_id, "component_id": component_id},
|
|
63
|
+
)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Channels API methods (feeds, rooms, categories, threads)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from vox_sdk.models.channels import (
|
|
8
|
+
CategoryListResponse,
|
|
9
|
+
CategoryResponse,
|
|
10
|
+
FeedResponse,
|
|
11
|
+
RoomResponse,
|
|
12
|
+
ThreadListResponse,
|
|
13
|
+
ThreadResponse,
|
|
14
|
+
)
|
|
15
|
+
from vox_sdk.models.enums import FeedType, RoomType
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from vox_sdk.http import HTTPClient
|
|
19
|
+
|
|
20
|
+
_UNSET: Any = object()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ChannelsAPI:
|
|
24
|
+
def __init__(self, http: HTTPClient) -> None:
|
|
25
|
+
self._http = http
|
|
26
|
+
|
|
27
|
+
# --- Feeds ---
|
|
28
|
+
|
|
29
|
+
async def get_feed(self, feed_id: int) -> FeedResponse:
|
|
30
|
+
r = await self._http.get(f"/api/v1/feeds/{feed_id}")
|
|
31
|
+
return FeedResponse.model_validate(r.json())
|
|
32
|
+
|
|
33
|
+
async def create_feed(
|
|
34
|
+
self,
|
|
35
|
+
name: str,
|
|
36
|
+
type: FeedType = FeedType.text,
|
|
37
|
+
*,
|
|
38
|
+
category_id: int | None = None,
|
|
39
|
+
permission_overrides: list[dict] | None = None,
|
|
40
|
+
) -> FeedResponse:
|
|
41
|
+
payload: dict[str, Any] = {"name": name, "type": type}
|
|
42
|
+
if category_id is not None:
|
|
43
|
+
payload["category_id"] = category_id
|
|
44
|
+
if permission_overrides is not None:
|
|
45
|
+
payload["permission_overrides"] = permission_overrides
|
|
46
|
+
r = await self._http.post("/api/v1/feeds", json=payload)
|
|
47
|
+
return FeedResponse.model_validate(r.json())
|
|
48
|
+
|
|
49
|
+
async def update_feed(
|
|
50
|
+
self,
|
|
51
|
+
feed_id: int,
|
|
52
|
+
*,
|
|
53
|
+
name: str | None = None,
|
|
54
|
+
topic: str | None = None,
|
|
55
|
+
category_id: int | None = _UNSET,
|
|
56
|
+
position: int | None = None,
|
|
57
|
+
) -> FeedResponse:
|
|
58
|
+
payload: dict[str, Any] = {}
|
|
59
|
+
if name is not None:
|
|
60
|
+
payload["name"] = name
|
|
61
|
+
if topic is not None:
|
|
62
|
+
payload["topic"] = topic
|
|
63
|
+
if category_id is not _UNSET:
|
|
64
|
+
payload["category_id"] = category_id
|
|
65
|
+
if position is not None:
|
|
66
|
+
payload["position"] = position
|
|
67
|
+
r = await self._http.patch(f"/api/v1/feeds/{feed_id}", json=payload)
|
|
68
|
+
return FeedResponse.model_validate(r.json())
|
|
69
|
+
|
|
70
|
+
async def delete_feed(self, feed_id: int) -> None:
|
|
71
|
+
await self._http.delete(f"/api/v1/feeds/{feed_id}")
|
|
72
|
+
|
|
73
|
+
async def subscribe_feed(self, feed_id: int) -> None:
|
|
74
|
+
await self._http.put(f"/api/v1/feeds/{feed_id}/subscribers")
|
|
75
|
+
|
|
76
|
+
async def unsubscribe_feed(self, feed_id: int) -> None:
|
|
77
|
+
await self._http.delete(f"/api/v1/feeds/{feed_id}/subscribers")
|
|
78
|
+
|
|
79
|
+
# --- Rooms ---
|
|
80
|
+
|
|
81
|
+
async def get_room(self, room_id: int) -> RoomResponse:
|
|
82
|
+
r = await self._http.get(f"/api/v1/rooms/{room_id}")
|
|
83
|
+
return RoomResponse.model_validate(r.json())
|
|
84
|
+
|
|
85
|
+
async def create_room(
|
|
86
|
+
self,
|
|
87
|
+
name: str,
|
|
88
|
+
type: RoomType = RoomType.voice,
|
|
89
|
+
*,
|
|
90
|
+
category_id: int | None = None,
|
|
91
|
+
permission_overrides: list[dict] | None = None,
|
|
92
|
+
) -> RoomResponse:
|
|
93
|
+
payload: dict[str, Any] = {"name": name, "type": type}
|
|
94
|
+
if category_id is not None:
|
|
95
|
+
payload["category_id"] = category_id
|
|
96
|
+
if permission_overrides is not None:
|
|
97
|
+
payload["permission_overrides"] = permission_overrides
|
|
98
|
+
r = await self._http.post("/api/v1/rooms", json=payload)
|
|
99
|
+
return RoomResponse.model_validate(r.json())
|
|
100
|
+
|
|
101
|
+
async def update_room(
|
|
102
|
+
self,
|
|
103
|
+
room_id: int,
|
|
104
|
+
*,
|
|
105
|
+
name: str | None = None,
|
|
106
|
+
category_id: int | None = _UNSET,
|
|
107
|
+
position: int | None = None,
|
|
108
|
+
) -> RoomResponse:
|
|
109
|
+
payload: dict[str, Any] = {}
|
|
110
|
+
if name is not None:
|
|
111
|
+
payload["name"] = name
|
|
112
|
+
if category_id is not _UNSET:
|
|
113
|
+
payload["category_id"] = category_id
|
|
114
|
+
if position is not None:
|
|
115
|
+
payload["position"] = position
|
|
116
|
+
r = await self._http.patch(f"/api/v1/rooms/{room_id}", json=payload)
|
|
117
|
+
return RoomResponse.model_validate(r.json())
|
|
118
|
+
|
|
119
|
+
async def delete_room(self, room_id: int) -> None:
|
|
120
|
+
await self._http.delete(f"/api/v1/rooms/{room_id}")
|
|
121
|
+
|
|
122
|
+
# --- Categories ---
|
|
123
|
+
|
|
124
|
+
async def list_categories(self) -> CategoryListResponse:
|
|
125
|
+
r = await self._http.get("/api/v1/categories")
|
|
126
|
+
return CategoryListResponse.model_validate(r.json())
|
|
127
|
+
|
|
128
|
+
async def get_category(self, category_id: int) -> CategoryResponse:
|
|
129
|
+
r = await self._http.get(f"/api/v1/categories/{category_id}")
|
|
130
|
+
return CategoryResponse.model_validate(r.json())
|
|
131
|
+
|
|
132
|
+
async def create_category(self, name: str, position: int = 0) -> CategoryResponse:
|
|
133
|
+
r = await self._http.post(
|
|
134
|
+
"/api/v1/categories", json={"name": name, "position": position}
|
|
135
|
+
)
|
|
136
|
+
return CategoryResponse.model_validate(r.json())
|
|
137
|
+
|
|
138
|
+
async def update_category(
|
|
139
|
+
self, category_id: int, *, name: str | None = None, position: int | None = None
|
|
140
|
+
) -> CategoryResponse:
|
|
141
|
+
payload: dict[str, Any] = {}
|
|
142
|
+
if name is not None:
|
|
143
|
+
payload["name"] = name
|
|
144
|
+
if position is not None:
|
|
145
|
+
payload["position"] = position
|
|
146
|
+
r = await self._http.patch(f"/api/v1/categories/{category_id}", json=payload)
|
|
147
|
+
return CategoryResponse.model_validate(r.json())
|
|
148
|
+
|
|
149
|
+
async def delete_category(self, category_id: int) -> None:
|
|
150
|
+
await self._http.delete(f"/api/v1/categories/{category_id}")
|
|
151
|
+
|
|
152
|
+
# --- Threads ---
|
|
153
|
+
|
|
154
|
+
async def get_thread(self, thread_id: int) -> ThreadResponse:
|
|
155
|
+
r = await self._http.get(f"/api/v1/threads/{thread_id}")
|
|
156
|
+
return ThreadResponse.model_validate(r.json())
|
|
157
|
+
|
|
158
|
+
async def list_threads(self, feed_id: int) -> ThreadListResponse:
|
|
159
|
+
r = await self._http.get(f"/api/v1/feeds/{feed_id}/threads")
|
|
160
|
+
return ThreadListResponse.model_validate(r.json())
|
|
161
|
+
|
|
162
|
+
async def create_thread(
|
|
163
|
+
self, feed_id: int, parent_msg_id: int, name: str
|
|
164
|
+
) -> ThreadResponse:
|
|
165
|
+
r = await self._http.post(
|
|
166
|
+
f"/api/v1/feeds/{feed_id}/threads",
|
|
167
|
+
json={"parent_msg_id": parent_msg_id, "name": name},
|
|
168
|
+
)
|
|
169
|
+
return ThreadResponse.model_validate(r.json())
|
|
170
|
+
|
|
171
|
+
async def update_thread(
|
|
172
|
+
self,
|
|
173
|
+
thread_id: int,
|
|
174
|
+
*,
|
|
175
|
+
name: str | None = None,
|
|
176
|
+
archived: bool | None = None,
|
|
177
|
+
locked: bool | None = None,
|
|
178
|
+
) -> ThreadResponse:
|
|
179
|
+
payload: dict[str, Any] = {}
|
|
180
|
+
if name is not None:
|
|
181
|
+
payload["name"] = name
|
|
182
|
+
if archived is not None:
|
|
183
|
+
payload["archived"] = archived
|
|
184
|
+
if locked is not None:
|
|
185
|
+
payload["locked"] = locked
|
|
186
|
+
r = await self._http.patch(f"/api/v1/threads/{thread_id}", json=payload)
|
|
187
|
+
return ThreadResponse.model_validate(r.json())
|
|
188
|
+
|
|
189
|
+
async def delete_thread(self, thread_id: int) -> None:
|
|
190
|
+
await self._http.delete(f"/api/v1/threads/{thread_id}")
|
|
191
|
+
|
|
192
|
+
async def subscribe_thread(self, feed_id: int, thread_id: int) -> None:
|
|
193
|
+
await self._http.put(f"/api/v1/feeds/{feed_id}/threads/{thread_id}/subscribers")
|
|
194
|
+
|
|
195
|
+
async def unsubscribe_thread(self, feed_id: int, thread_id: int) -> None:
|
|
196
|
+
await self._http.delete(f"/api/v1/feeds/{feed_id}/threads/{thread_id}/subscribers")
|