modifyself 0.2.5__tar.gz → 0.2.7__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.
- {modifyself-0.2.5 → modifyself-0.2.7}/PKG-INFO +1 -1
- modifyself-0.2.7/modifyself/__init__.py +100 -0
- modifyself-0.2.7/modifyself/voice.py +277 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself.egg-info/PKG-INFO +1 -1
- {modifyself-0.2.5 → modifyself-0.2.7}/pyproject.toml +1 -1
- modifyself-0.2.5/modifyself/__init__.py +0 -12
- modifyself-0.2.5/modifyself/voice.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/LICENSE +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/README.md +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/activity.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/client.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/__init__.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/checks.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/cog.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/context.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/converters.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/commands/core.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/components.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/core/bitfield.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/core/enums.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/core/mixins.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/core/snowflake.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/errors.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/gateway/__init__.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/gateway/dispatcher.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/gateway/heartbeat.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/gateway/websocket.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/headers.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/http/__init__.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/http/client.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/http/ratelimit.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/http/route.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/interactions.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/__init__.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/base.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/billing.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/channel.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/guild.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/member.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/message.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/relationship.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/settings.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/user.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/models/webhook.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/state.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/types.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself/utils.py +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself.egg-info/SOURCES.txt +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself.egg-info/dependency_links.txt +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself.egg-info/requires.txt +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/modifyself.egg-info/top_level.txt +0 -0
- {modifyself-0.2.5 → modifyself-0.2.7}/setup.cfg +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
\"\"\"
|
|
2
|
+
modifyself — a clean, pythonic Discord self-bot library.
|
|
3
|
+
\"\"\"
|
|
4
|
+
|
|
5
|
+
__version__ = \"0.2.7\"
|
|
6
|
+
|
|
7
|
+
from .client import Client
|
|
8
|
+
from .commands.core import command
|
|
9
|
+
from .commands.cog import Cog
|
|
10
|
+
from .commands.context import Context
|
|
11
|
+
|
|
12
|
+
from .models.relationship import Relationship
|
|
13
|
+
from .models.billing import PaymentSource, Subscription
|
|
14
|
+
from .models.settings import GuildSettings, UserSettings
|
|
15
|
+
from .models.webhook import Webhook, WebhookMessage
|
|
16
|
+
|
|
17
|
+
from .activity import (
|
|
18
|
+
Activity,
|
|
19
|
+
ActivityType,
|
|
20
|
+
ActivityFlags,
|
|
21
|
+
spotify_activity,
|
|
22
|
+
youtube_activity,
|
|
23
|
+
xbox_activity,
|
|
24
|
+
playstation_activity,
|
|
25
|
+
crunchyroll_activity,
|
|
26
|
+
custom_activity,
|
|
27
|
+
listening_activity,
|
|
28
|
+
streaming_activity,
|
|
29
|
+
competing_activity,
|
|
30
|
+
LOGO_MAP,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from .components import (
|
|
34
|
+
ComponentType,
|
|
35
|
+
ButtonStyle,
|
|
36
|
+
TextInputStyle,
|
|
37
|
+
Button,
|
|
38
|
+
SelectOption,
|
|
39
|
+
SelectMenu,
|
|
40
|
+
ChannelSelect,
|
|
41
|
+
RoleSelect,
|
|
42
|
+
MentionableSelect,
|
|
43
|
+
UserSelect,
|
|
44
|
+
TextInput,
|
|
45
|
+
ActionRow,
|
|
46
|
+
Modal,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from .interactions import (
|
|
50
|
+
Interaction,
|
|
51
|
+
InteractionType,
|
|
52
|
+
InteractionCallbackType,
|
|
53
|
+
InteractionHandler,
|
|
54
|
+
interaction_handler,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
\"Client\",
|
|
59
|
+
\"command\",
|
|
60
|
+
\"Cog\",
|
|
61
|
+
\"Context\",
|
|
62
|
+
\"Relationship\",
|
|
63
|
+
\"PaymentSource\",
|
|
64
|
+
\"Subscription\",
|
|
65
|
+
\"GuildSettings\",
|
|
66
|
+
\"UserSettings\",
|
|
67
|
+
\"Webhook\",
|
|
68
|
+
\"WebhookMessage\",
|
|
69
|
+
\"Activity\",
|
|
70
|
+
\"ActivityType\",
|
|
71
|
+
\"ActivityFlags\",
|
|
72
|
+
\"spotify_activity\",
|
|
73
|
+
\"youtube_activity\",
|
|
74
|
+
\"xbox_activity\",
|
|
75
|
+
\"playstation_activity\",
|
|
76
|
+
\"crunchyroll_activity\",
|
|
77
|
+
\"custom_activity\",
|
|
78
|
+
\"listening_activity\",
|
|
79
|
+
\"streaming_activity\",
|
|
80
|
+
\"competing_activity\",
|
|
81
|
+
\"LOGO_MAP\",
|
|
82
|
+
\"ComponentType\",
|
|
83
|
+
\"ButtonStyle\",
|
|
84
|
+
\"TextInputStyle\",
|
|
85
|
+
\"Button\",
|
|
86
|
+
\"SelectOption\",
|
|
87
|
+
\"SelectMenu\",
|
|
88
|
+
\"ChannelSelect\",
|
|
89
|
+
\"RoleSelect\",
|
|
90
|
+
\"MentionableSelect\",
|
|
91
|
+
\"UserSelect\",
|
|
92
|
+
\"TextInput\",
|
|
93
|
+
\"ActionRow\",
|
|
94
|
+
\"Modal\",
|
|
95
|
+
\"Interaction\",
|
|
96
|
+
\"InteractionType\",
|
|
97
|
+
\"InteractionCallbackType\",
|
|
98
|
+
\"InteractionHandler\",
|
|
99
|
+
\"interaction_handler\",
|
|
100
|
+
]
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Discord voice support.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import socket
|
|
9
|
+
import struct
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
from typing import Optional, List, Dict, Any, Callable, Union
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import nacl.secret
|
|
18
|
+
import nacl.utils
|
|
19
|
+
HAS_NACL = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
HAS_NACL = False
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import opuslib
|
|
25
|
+
HAS_OPUS = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
HAS_OPUS = False
|
|
28
|
+
|
|
29
|
+
from .http.route import Route
|
|
30
|
+
from .errors import VoiceError
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
class VoiceState:
|
|
35
|
+
"""Voice connection state."""
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.session_id = None
|
|
38
|
+
self.token = None
|
|
39
|
+
self.endpoint = None
|
|
40
|
+
self.guild_id = None
|
|
41
|
+
self.user_id = None
|
|
42
|
+
self.channel_id = None
|
|
43
|
+
self.mute = False
|
|
44
|
+
self.deaf = False
|
|
45
|
+
self.self_mute = False
|
|
46
|
+
self.self_deaf = False
|
|
47
|
+
self.self_stream = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class VoiceClient:
|
|
51
|
+
"""Represents a connection to a Discord voice channel."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, client, channel_id: int, guild_id: int, endpoint: str, token: str, session_id: str):
|
|
54
|
+
self.client = client
|
|
55
|
+
self.channel_id = channel_id
|
|
56
|
+
self.guild_id = guild_id
|
|
57
|
+
self.endpoint = endpoint
|
|
58
|
+
self.token = token
|
|
59
|
+
self.session_id = session_id
|
|
60
|
+
self._state = VoiceState()
|
|
61
|
+
self._state.channel_id = channel_id
|
|
62
|
+
self._state.guild_id = guild_id
|
|
63
|
+
self._state.session_id = session_id
|
|
64
|
+
self._state.token = token
|
|
65
|
+
self._state.endpoint = endpoint
|
|
66
|
+
|
|
67
|
+
self._ws = None
|
|
68
|
+
self._connected = False
|
|
69
|
+
self._closed = False
|
|
70
|
+
self._sequence = 0
|
|
71
|
+
self._timestamp = 0
|
|
72
|
+
self._ssrc = None
|
|
73
|
+
self._encryption = None
|
|
74
|
+
self._secret_key = None
|
|
75
|
+
self._voice_ip = None
|
|
76
|
+
self._voice_port = None
|
|
77
|
+
self._sock = None
|
|
78
|
+
|
|
79
|
+
self._heartbeat_task = None
|
|
80
|
+
self._receive_task = None
|
|
81
|
+
self._audio_task = None
|
|
82
|
+
self._audio_queue = asyncio.Queue()
|
|
83
|
+
self._ready = asyncio.Event()
|
|
84
|
+
|
|
85
|
+
self._handlers = {
|
|
86
|
+
"on_ready": [],
|
|
87
|
+
"on_speaking": [],
|
|
88
|
+
"on_disconnect": [],
|
|
89
|
+
"on_error": [],
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.info(f"Voice client created for guild {guild_id}, channel {channel_id}")
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def is_connected(self) -> bool:
|
|
96
|
+
return self._connected and not self._closed
|
|
97
|
+
|
|
98
|
+
async def connect(self) -> bool:
|
|
99
|
+
"""Connect to the voice channel."""
|
|
100
|
+
if self._connected:
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
import websockets
|
|
105
|
+
ws_url = f"wss://{self.endpoint}?v=4"
|
|
106
|
+
self._ws = await websockets.connect(ws_url)
|
|
107
|
+
|
|
108
|
+
await self._send({
|
|
109
|
+
"op": 0,
|
|
110
|
+
"d": {
|
|
111
|
+
"server_id": str(self.guild_id),
|
|
112
|
+
"user_id": str(self.client.user.id),
|
|
113
|
+
"session_id": self.session_id,
|
|
114
|
+
"token": self.token,
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
ready = await self._wait_for_event("ready", timeout=10)
|
|
119
|
+
if ready:
|
|
120
|
+
self._connected = True
|
|
121
|
+
self._ready.set()
|
|
122
|
+
logger.info("Voice connection ready")
|
|
123
|
+
await self._on_ready()
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Voice connection failed: {e}")
|
|
130
|
+
await self._on_error(e)
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
async def disconnect(self) -> None:
|
|
134
|
+
"""Disconnect from voice channel."""
|
|
135
|
+
if self._closed:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
self._closed = True
|
|
139
|
+
self._connected = False
|
|
140
|
+
self._ready.clear()
|
|
141
|
+
|
|
142
|
+
if self._heartbeat_task:
|
|
143
|
+
self._heartbeat_task.cancel()
|
|
144
|
+
|
|
145
|
+
if self._receive_task:
|
|
146
|
+
self._receive_task.cancel()
|
|
147
|
+
|
|
148
|
+
if self._audio_task:
|
|
149
|
+
self._audio_task.cancel()
|
|
150
|
+
|
|
151
|
+
if self._sock:
|
|
152
|
+
self._sock.close()
|
|
153
|
+
self._sock = None
|
|
154
|
+
|
|
155
|
+
if self._ws:
|
|
156
|
+
await self._ws.close()
|
|
157
|
+
self._ws = None
|
|
158
|
+
|
|
159
|
+
logger.info("Voice connection closed")
|
|
160
|
+
|
|
161
|
+
async def send_audio(self, data: bytes) -> None:
|
|
162
|
+
"""Send audio data to the voice channel."""
|
|
163
|
+
if not self._connected:
|
|
164
|
+
raise VoiceError("Not connected to voice")
|
|
165
|
+
await self._audio_queue.put(data)
|
|
166
|
+
|
|
167
|
+
async def _send(self, data: dict) -> None:
|
|
168
|
+
"""Send a WebSocket message."""
|
|
169
|
+
if self._ws:
|
|
170
|
+
await self._ws.send(json.dumps(data))
|
|
171
|
+
|
|
172
|
+
async def _wait_for_event(self, event_type: str, timeout: float = 10) -> Optional[dict]:
|
|
173
|
+
"""Wait for a specific voice event."""
|
|
174
|
+
start = time.time()
|
|
175
|
+
while time.time() - start < timeout:
|
|
176
|
+
if self._ws:
|
|
177
|
+
try:
|
|
178
|
+
msg = await asyncio.wait_for(self._ws.recv(), timeout=1)
|
|
179
|
+
data = json.loads(msg)
|
|
180
|
+
if data.get("op") == 2 and data.get("t") == event_type.upper():
|
|
181
|
+
return data
|
|
182
|
+
except asyncio.TimeoutError:
|
|
183
|
+
continue
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"Error waiting for event: {e}")
|
|
186
|
+
break
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def on(self, event: str, callback: Callable):
|
|
190
|
+
"""Register an event handler."""
|
|
191
|
+
if event not in self._handlers:
|
|
192
|
+
self._handlers[event] = []
|
|
193
|
+
self._handlers[event].append(callback)
|
|
194
|
+
|
|
195
|
+
async def _on_ready(self):
|
|
196
|
+
for callback in self._handlers.get("on_ready", []):
|
|
197
|
+
try:
|
|
198
|
+
if asyncio.iscoroutinefunction(callback):
|
|
199
|
+
await callback(self)
|
|
200
|
+
else:
|
|
201
|
+
callback(self)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Error in on_ready handler: {e}")
|
|
204
|
+
|
|
205
|
+
async def _on_error(self, error: Exception):
|
|
206
|
+
for callback in self._handlers.get("on_error", []):
|
|
207
|
+
try:
|
|
208
|
+
if asyncio.iscoroutinefunction(callback):
|
|
209
|
+
await callback(error)
|
|
210
|
+
else:
|
|
211
|
+
callback(error)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(f"Error in on_error handler: {e}")
|
|
214
|
+
|
|
215
|
+
def __repr__(self) -> str:
|
|
216
|
+
return f"<VoiceClient guild={self.guild_id} channel={self.channel_id} connected={self._connected}>"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class VoiceManager:
|
|
220
|
+
"""Manages voice connections."""
|
|
221
|
+
|
|
222
|
+
def __init__(self, client):
|
|
223
|
+
self.client = client
|
|
224
|
+
self._connections: Dict[int, VoiceClient] = {}
|
|
225
|
+
self._state: Dict[int, VoiceState] = {}
|
|
226
|
+
|
|
227
|
+
async def connect(
|
|
228
|
+
self,
|
|
229
|
+
channel_id: int,
|
|
230
|
+
guild_id: int,
|
|
231
|
+
endpoint: str,
|
|
232
|
+
token: str,
|
|
233
|
+
session_id: str,
|
|
234
|
+
) -> VoiceClient:
|
|
235
|
+
"""Create and connect a voice client."""
|
|
236
|
+
if guild_id in self._connections:
|
|
237
|
+
await self._connections[guild_id].disconnect()
|
|
238
|
+
|
|
239
|
+
client = VoiceClient(
|
|
240
|
+
self.client,
|
|
241
|
+
channel_id,
|
|
242
|
+
guild_id,
|
|
243
|
+
endpoint,
|
|
244
|
+
token,
|
|
245
|
+
session_id,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
await client.connect()
|
|
249
|
+
self._connections[guild_id] = client
|
|
250
|
+
return client
|
|
251
|
+
|
|
252
|
+
def get_client(self, guild_id: int) -> Optional[VoiceClient]:
|
|
253
|
+
"""Get the voice client for a guild."""
|
|
254
|
+
return self._connections.get(guild_id)
|
|
255
|
+
|
|
256
|
+
async def disconnect(self, guild_id: int) -> None:
|
|
257
|
+
"""Disconnect a voice client."""
|
|
258
|
+
if guild_id in self._connections:
|
|
259
|
+
await self._connections[guild_id].disconnect()
|
|
260
|
+
del self._connections[guild_id]
|
|
261
|
+
|
|
262
|
+
async def disconnect_all(self) -> None:
|
|
263
|
+
"""Disconnect all voice clients."""
|
|
264
|
+
for guild_id in list(self._connections.keys()):
|
|
265
|
+
await self.disconnect(guild_id)
|
|
266
|
+
|
|
267
|
+
def update_state(self, guild_id: int, state_data: dict) -> None:
|
|
268
|
+
"""Update voice state."""
|
|
269
|
+
state = self._state.get(guild_id)
|
|
270
|
+
if not state:
|
|
271
|
+
state = VoiceState()
|
|
272
|
+
self._state[guild_id] = state
|
|
273
|
+
|
|
274
|
+
state.session_id = state_data.get("session_id")
|
|
275
|
+
state.token = state_data.get("token")
|
|
276
|
+
state.endpoint = state_data.get("endpoint")
|
|
277
|
+
state.channel_id = state_data.get("channel_id")
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
modifyself ? a clean, pythonic Discord self-bot library.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
__version__ = "0.1.4"
|
|
6
|
-
|
|
7
|
-
from .client import Client
|
|
8
|
-
from .commands.core import command
|
|
9
|
-
from .commands.cog import Cog
|
|
10
|
-
from .commands.context import Context
|
|
11
|
-
|
|
12
|
-
__all__ = ["Client", "command", "Cog", "Context"]
|
|
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
|
|
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
|