pyyapper 1.6.0__tar.gz → 1.7.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.
- {pyyapper-1.6.0/src/pyyapper.egg-info → pyyapper-1.7.0}/PKG-INFO +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/pyproject.toml +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/__init__.py +2 -1
- pyyapper-1.7.0/src/pyyapper/_version.py +1 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/bot.py +43 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/http.py +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/models.py +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/oauth.py +2 -2
- pyyapper-1.7.0/src/pyyapper/voice.py +44 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/webhooks.py +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0/src/pyyapper.egg-info}/PKG-INFO +1 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper.egg-info/SOURCES.txt +1 -0
- pyyapper-1.6.0/src/pyyapper/_version.py +0 -1
- {pyyapper-1.6.0 → pyyapper-1.7.0}/LICENSE +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/README.md +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/setup.cfg +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/buttons.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/cache.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/commands.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/components_v2.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/config.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/embeds.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/errors.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/__init__.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/commands/__init__.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/commands/checks.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/commands/converters.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/commands/core.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/commands/errors.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/ext/tasks.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/i18n.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/metrics.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/pagination.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/presence.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/py.typed +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper/wait_for.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper.egg-info/dependency_links.txt +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper.egg-info/requires.txt +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/src/pyyapper.egg-info/top_level.txt +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/tests/test_ext_commands.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/tests/test_oauth.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/tests/test_smoke.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/tests/test_tasks.py +0 -0
- {pyyapper-1.6.0 → pyyapper-1.7.0}/tests/test_webhooks.py +0 -0
|
@@ -19,6 +19,7 @@ from .errors import (
|
|
|
19
19
|
from .models import (
|
|
20
20
|
User, Server, Channel, Category, Member, Message, Role, VoiceChannel, picture_url,
|
|
21
21
|
)
|
|
22
|
+
from .voice import VoiceCall
|
|
22
23
|
from .presence import Activity, Presence
|
|
23
24
|
from .pagination import Paginator
|
|
24
25
|
from .webhooks import Webhook
|
|
@@ -43,7 +44,7 @@ __all__ = [
|
|
|
43
44
|
"Embed", "Button", "Row",
|
|
44
45
|
"YapperError", "ConfigurationError", "HTTPException",
|
|
45
46
|
"BadRequest", "Unauthorized", "Forbidden", "NotFound", "RateLimited", "ServerError", "ServiceUnavailable",
|
|
46
|
-
"User", "Server", "Channel", "Category", "Member", "Message", "Role", "VoiceChannel", "picture_url",
|
|
47
|
+
"User", "Server", "Channel", "Category", "Member", "Message", "Role", "VoiceChannel", "picture_url", "VoiceCall",
|
|
47
48
|
"Activity", "Presence",
|
|
48
49
|
"Paginator", "Webhook", "WaiterRegistry", "wait_for",
|
|
49
50
|
"OAuth2Client", "OAuthError", "generate_state", "generate_pkce",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.7.0"
|
|
@@ -183,7 +183,7 @@ class Bot:
|
|
|
183
183
|
self,
|
|
184
184
|
token: Optional[str] = None,
|
|
185
185
|
*,
|
|
186
|
-
base_url: str = "https://www.
|
|
186
|
+
base_url: str = "https://www.yapin.gg/api/bot/v1",
|
|
187
187
|
prefixes: Optional[list[str]] = None,
|
|
188
188
|
log: Any = True,
|
|
189
189
|
ignore_bots: bool = True,
|
|
@@ -848,6 +848,44 @@ class Bot:
|
|
|
848
848
|
"user_id": user_id,
|
|
849
849
|
})
|
|
850
850
|
|
|
851
|
+
# Voice (music): bot joins a voice channel and plays audio
|
|
852
|
+
async def join_voice(self, server_id: Any, channel_id: Any) -> Any:
|
|
853
|
+
"""Join a voice channel. Returns a LiveKit ticket dict."""
|
|
854
|
+
return await self.api("POST", f"/servers/{server_id}/voice/join", {
|
|
855
|
+
"channel_id": channel_id,
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
async def leave_voice(self, server_id: Any) -> Any:
|
|
859
|
+
"""Leave voice in a server."""
|
|
860
|
+
return await self.api("POST", f"/servers/{server_id}/voice/leave", {})
|
|
861
|
+
|
|
862
|
+
async def connect_voice(self, server_id: Any, channel_id: Any, livekit_module: Any = None) -> Any:
|
|
863
|
+
"""Join voice and return a ticket.
|
|
864
|
+
|
|
865
|
+
If ``livekit_module`` (e.g. ``livekit.rtc``) is provided and installed,
|
|
866
|
+
returns a small helper object exposing ``.source`` (an audio source) and
|
|
867
|
+
``.leave()``. Otherwise returns the raw ticket dict from the API.
|
|
868
|
+
"""
|
|
869
|
+
ticket = await self.join_voice(server_id, channel_id)
|
|
870
|
+
if livekit_module is None:
|
|
871
|
+
return ticket
|
|
872
|
+
# Best-effort LiveKit connection. E2EE key exchange is handled by the
|
|
873
|
+
# caller per the docs; here we just open the room and expose a source.
|
|
874
|
+
try:
|
|
875
|
+
from .voice import VoiceCall
|
|
876
|
+
room = livekit_module.Room()
|
|
877
|
+
source = livekit_module.AudioSource(48000, 2)
|
|
878
|
+
track = livekit_module.LocalAudioTrack.create_audio_track("bot-audio", source)
|
|
879
|
+
await room.connect(ticket["url"], ticket["token"])
|
|
880
|
+
await room.local_participant.publish_track(track)
|
|
881
|
+
obj = VoiceCall(room, source, self, server_id, ticket=ticket)
|
|
882
|
+
return obj
|
|
883
|
+
except Exception as e:
|
|
884
|
+
# Fall back to raw ticket if LiveKit isn't available/compatible.
|
|
885
|
+
if hasattr(self, "logger"):
|
|
886
|
+
self.logger.warning("connect_voice LiveKit setup failed: %s", e)
|
|
887
|
+
return ticket
|
|
888
|
+
|
|
851
889
|
# ------------------------------------------------------------------ #
|
|
852
890
|
# Channels management
|
|
853
891
|
# ------------------------------------------------------------------ #
|
|
@@ -1111,6 +1149,10 @@ class Bot:
|
|
|
1111
1149
|
if m and m.group(1) in self.prefixes and m.group(2).lower() in self.commands:
|
|
1112
1150
|
name = m.group(2).lower()
|
|
1113
1151
|
args = (m.group(3) or "").strip()
|
|
1152
|
+
# If command_for is set and points at another bot, don't handle.
|
|
1153
|
+
command_for = data.get("command_for")
|
|
1154
|
+
if command_for and self.me is not None and command_for != getattr(self.me, "id", None):
|
|
1155
|
+
name = None
|
|
1114
1156
|
if name and name in self.commands:
|
|
1115
1157
|
ctx.command = name
|
|
1116
1158
|
ctx.args = args or ""
|
|
@@ -36,7 +36,7 @@ class OAuthError(YapperError):
|
|
|
36
36
|
super().__init__(f"{error}: {description}".strip(": "))
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
DEFAULT_BASE = "https://www.
|
|
39
|
+
DEFAULT_BASE = "https://www.yapin.gg"
|
|
40
40
|
AUTHORIZE_PATH = "/oauth/authorize"
|
|
41
41
|
TOKEN_PATH = "/api/oauth/token"
|
|
42
42
|
ME_PATH = "/api/oauth/me"
|
|
@@ -72,7 +72,7 @@ class OAuth2Client:
|
|
|
72
72
|
redirect_uri:
|
|
73
73
|
Default redirect URI (must match one registered for the app).
|
|
74
74
|
base_url:
|
|
75
|
-
Yapper base URL. Defaults to ``https://www.
|
|
75
|
+
Yapper base URL. Defaults to ``https://www.yapin.gg``.
|
|
76
76
|
session:
|
|
77
77
|
Optional shared :class:`aiohttp.ClientSession`.
|
|
78
78
|
"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Voice connection helpers for PyYapper (music bots)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VoiceCall:
|
|
9
|
+
"""A handle to an active voice connection.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
room: the LiveKit room (if connected).
|
|
13
|
+
source: the audio source to capture frames into.
|
|
14
|
+
ticket: the raw ticket returned by the API.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, room: Any, source: Any, bot: Any, server_id: Any, ticket: Any = None) -> None:
|
|
18
|
+
self.room = room
|
|
19
|
+
self.source = source
|
|
20
|
+
self.bot = bot
|
|
21
|
+
self.server_id = server_id
|
|
22
|
+
self.ticket = ticket
|
|
23
|
+
|
|
24
|
+
async def leave(self) -> Any:
|
|
25
|
+
try:
|
|
26
|
+
if self.room is not None:
|
|
27
|
+
await self.room.disconnect()
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
try:
|
|
31
|
+
await self.bot.leave_voice(self.server_id)
|
|
32
|
+
except Exception:
|
|
33
|
+
pass
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
async def capture_frame(self, frame: Any) -> Any:
|
|
37
|
+
if self.source is not None and hasattr(self.source, "capture_frame"):
|
|
38
|
+
return await self.source.capture_frame(frame)
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
async def wait_for_playout(self) -> Any:
|
|
42
|
+
if self.source is not None and hasattr(self.source, "wait_for_playout"):
|
|
43
|
+
return await self.source.wait_for_playout()
|
|
44
|
+
return None
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.6.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|