modifyself 0.1.4__tar.gz → 0.2.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.
Files changed (53) hide show
  1. {modifyself-0.1.4 → modifyself-0.2.0}/PKG-INFO +1 -1
  2. modifyself-0.2.0/modifyself/__init__.py +12 -0
  3. modifyself-0.2.0/modifyself/activity.py +0 -0
  4. modifyself-0.2.0/modifyself/client.py +638 -0
  5. modifyself-0.2.0/modifyself/components.py +0 -0
  6. modifyself-0.2.0/modifyself/interactions.py +0 -0
  7. modifyself-0.2.0/modifyself/models/billing.py +0 -0
  8. modifyself-0.2.0/modifyself/models/relationship.py +0 -0
  9. modifyself-0.2.0/modifyself/models/settings.py +0 -0
  10. modifyself-0.2.0/modifyself/models/webhook.py +0 -0
  11. modifyself-0.2.0/modifyself/types.py +162 -0
  12. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/utils.py +33 -7
  13. modifyself-0.2.0/modifyself/voice.py +0 -0
  14. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself.egg-info/PKG-INFO +1 -1
  15. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself.egg-info/SOURCES.txt +9 -1
  16. {modifyself-0.1.4 → modifyself-0.2.0}/pyproject.toml +29 -29
  17. modifyself-0.1.4/modifyself/__init__.py +0 -12
  18. modifyself-0.1.4/modifyself/client.py +0 -432
  19. modifyself-0.1.4/modifyself/types.py +0 -106
  20. {modifyself-0.1.4 → modifyself-0.2.0}/LICENSE +0 -0
  21. {modifyself-0.1.4 → modifyself-0.2.0}/README.md +0 -0
  22. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/__init__.py +0 -0
  23. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/checks.py +0 -0
  24. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/cog.py +0 -0
  25. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/context.py +0 -0
  26. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/converters.py +0 -0
  27. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/commands/core.py +0 -0
  28. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/core/bitfield.py +0 -0
  29. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/core/enums.py +0 -0
  30. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/core/mixins.py +0 -0
  31. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/core/snowflake.py +0 -0
  32. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/errors.py +0 -0
  33. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/gateway/__init__.py +0 -0
  34. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/gateway/dispatcher.py +0 -0
  35. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/gateway/heartbeat.py +0 -0
  36. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/gateway/websocket.py +0 -0
  37. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/headers.py +0 -0
  38. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/http/__init__.py +0 -0
  39. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/http/client.py +0 -0
  40. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/http/ratelimit.py +0 -0
  41. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/http/route.py +0 -0
  42. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/__init__.py +0 -0
  43. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/base.py +0 -0
  44. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/channel.py +0 -0
  45. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/guild.py +0 -0
  46. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/member.py +0 -0
  47. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/message.py +0 -0
  48. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/models/user.py +0 -0
  49. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself/state.py +0 -0
  50. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself.egg-info/dependency_links.txt +0 -0
  51. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself.egg-info/requires.txt +0 -0
  52. {modifyself-0.1.4 → modifyself-0.2.0}/modifyself.egg-info/top_level.txt +0 -0
  53. {modifyself-0.1.4 → modifyself-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: A clean, pythonic Discord self-bot library
5
5
  Author-email: modifyself <modifyself@example.com>
6
6
  License-Expression: MIT
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,638 @@
1
+ """
2
+ Main client class for modifyself.
3
+ """
4
+
5
+ import asyncio
6
+ import inspect
7
+ import logging
8
+ import signal
9
+ import sys
10
+ from typing import Callable, Any, Optional, List, Dict, Union
11
+
12
+ from .http.client import HTTPClient
13
+ from .http.route import Route
14
+ from .gateway.websocket import GatewayWebSocket
15
+ from .gateway.dispatcher import EventDispatcher
16
+ from .state import ConnectionState
17
+ from .commands.core import Command, command
18
+ from .commands.context import Context
19
+ from .commands.cog import Cog
20
+ from .errors import CommandError, ConversionError, CommandNotFound
21
+ from .models.message import Message
22
+ from .models.relationship import Relationship
23
+ from .models.billing import PaymentSource, Subscription
24
+ from .models.settings import GuildSettings, UserSettings
25
+ from .models.webhook import Webhook, WebhookMessage
26
+ from .headers import HeaderSpoofer, EMULATION
27
+ from .utils import send_notification, START_IMAGE, ERROR_IMAGE
28
+ from .interactions import Interaction, InteractionHandler, interaction_handler
29
+ from .voice import VoiceManager, VoiceClient, VoiceState
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ class Client:
35
+ """
36
+ The main client for interacting with Discord.
37
+
38
+ Usage:
39
+ bot = Client(token="your_token")
40
+
41
+ @bot.event
42
+ async def on_ready():
43
+ print(f"Logged in as {bot.user}")
44
+
45
+ @bot.command()
46
+ async def ping(ctx):
47
+ await ctx.reply("Pong!")
48
+
49
+ bot.run()
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ *,
55
+ token: str,
56
+ command_prefix: Union[str, Callable[["Client", Message], str]] = "!",
57
+ owner_ids: Optional[List[int]] = None,
58
+ proxy: Optional[str] = None,
59
+ notifications: bool = True,
60
+ ):
61
+ self.token = token
62
+ self.command_prefix = command_prefix
63
+ self.owner_ids = set(owner_ids) if owner_ids else set()
64
+ self._notifications = notifications
65
+
66
+ self._headers = HeaderSpoofer(token, EMULATION)
67
+ self._http = HTTPClient(
68
+ token,
69
+ headers=self._headers,
70
+ proxy=proxy,
71
+ )
72
+ self._state = ConnectionState(self._http)
73
+ self._dispatcher = EventDispatcher(self._state)
74
+ self._gateway = GatewayWebSocket(
75
+ self._dispatcher,
76
+ token,
77
+ headers=self._headers,
78
+ )
79
+
80
+ self._event_handlers: Dict[str, List[Callable]] = {}
81
+ self._once_handlers: Dict[str, List[Callable]] = {}
82
+
83
+ self._commands: Dict[str, Command] = {}
84
+ self._cogs: Dict[str, Cog] = {}
85
+
86
+ self._ready = asyncio.Event()
87
+ self._closed = False
88
+ self._task: Optional[asyncio.Task] = None
89
+ self._close_task: Optional[asyncio.Task] = None
90
+ self._error_handled = False
91
+
92
+ self._interaction_handlers: Dict[str, Callable] = {}
93
+
94
+ self._voice = VoiceManager(self)
95
+
96
+ self._dispatcher.on("READY", self._on_ready_internal)
97
+ self._dispatcher.on("MESSAGE_CREATE", self._on_message_create_internal)
98
+ self._dispatcher.on("INTERACTION_CREATE", self._on_interaction_create)
99
+ self._dispatcher.on("VOICE_STATE_UPDATE", self._on_voice_state_update)
100
+
101
+ if self._notifications:
102
+ send_notification(
103
+ title="🚀 modifyself Initialized",
104
+ message="Client created successfully. Starting connection...",
105
+ image_url=START_IMAGE,
106
+ timeout=3
107
+ )
108
+
109
+ @property
110
+ def user(self):
111
+ return self._state.user
112
+
113
+ @property
114
+ def guilds(self):
115
+ return list(self._state._guilds.values())
116
+
117
+ @property
118
+ def users(self):
119
+ return list(self._state._users.values())
120
+
121
+ @property
122
+ def latency(self) -> float:
123
+ return self._gateway.latency
124
+
125
+ @property
126
+ def listeners(self) -> Dict[str, List]:
127
+ return dict(self._dispatcher._handlers)
128
+
129
+ @property
130
+ def commands(self) -> List[Command]:
131
+ return list(self._commands.values())
132
+
133
+ @property
134
+ def cogs(self) -> List[Cog]:
135
+ return list(self._cogs.values())
136
+
137
+ @property
138
+ def voice(self) -> VoiceManager:
139
+ """Access voice manager."""
140
+ return self._voice
141
+
142
+ def event(self, coro: Callable):
143
+ if not asyncio.iscoroutinefunction(coro):
144
+ raise TypeError("Event handlers must be coroutines")
145
+ name = coro.__name__.replace("on_", "", 1).upper()
146
+ self._dispatcher.on(name, coro)
147
+ self._event_handlers.setdefault(name, []).append(coro)
148
+ logger.debug("Registered event handler for %s", name)
149
+ return coro
150
+
151
+ def listen(self, name: Optional[str] = None):
152
+ def decorator(coro: Callable):
153
+ if not asyncio.iscoroutinefunction(coro):
154
+ raise TypeError("Listeners must be coroutines")
155
+ event_name = (name or coro.__name__).upper()
156
+ self._dispatcher.on(event_name, coro)
157
+ return coro
158
+ return decorator
159
+
160
+ def add_listener(self, event: str, handler: Callable):
161
+ event = event.upper()
162
+ self._dispatcher.on(event, handler)
163
+ logger.info("[client] listener registered: %s -> %s", event, handler)
164
+
165
+ def remove_listener(self, event: str, handler: Callable):
166
+ self._dispatcher.off(event.upper(), handler)
167
+
168
+ def command(self, *, name: Optional[str] = None, aliases: Optional[List[str]] = None, **kwargs):
169
+ def decorator(func: Callable):
170
+ cmd = Command(func, name=name, aliases=aliases, **kwargs)
171
+ self.add_command(cmd)
172
+ func._command = cmd
173
+ return func
174
+ return decorator
175
+
176
+ def add_command(self, cmd: Command):
177
+ if cmd.name in self._commands:
178
+ raise ValueError(f"Command {cmd.name} is already registered")
179
+
180
+ for alias in cmd.aliases:
181
+ if alias in self._commands:
182
+ raise ValueError(f"Alias {alias} is already registered")
183
+
184
+ self._commands[cmd.name] = cmd
185
+ for alias in cmd.aliases:
186
+ self._commands[alias] = cmd
187
+ logger.debug("Registered command: %s", cmd.name)
188
+
189
+ def remove_command(self, name: str):
190
+ cmd = self._commands.pop(name, None)
191
+ if cmd and cmd.name == name:
192
+ for alias in cmd.aliases:
193
+ self._commands.pop(alias, None)
194
+
195
+ def get_command(self, name: str) -> Optional[Command]:
196
+ return self._commands.get(name)
197
+
198
+ def add_cog(self, cog: Cog):
199
+ if cog.name in self._cogs:
200
+ raise ValueError(f"Cog {cog.name} is already loaded")
201
+ cog._inject(self)
202
+ self._cogs[cog.name] = cog
203
+ cog.cog_load()
204
+ logger.info("Loaded cog: %s", cog.name)
205
+
206
+ def remove_cog(self, name: str):
207
+ cog = self._cogs.pop(name, None)
208
+ if cog:
209
+ cog.cog_unload()
210
+ cog._eject(self)
211
+ logger.info("Unloaded cog: %s", name)
212
+
213
+ def get_cog(self, name: str) -> Optional[Cog]:
214
+ return self._cogs.get(name)
215
+
216
+ def interaction_handler(self, custom_id: str):
217
+ """Decorator to register an interaction handler."""
218
+ def decorator(func: Callable):
219
+ self._interaction_handlers[custom_id] = func
220
+ return func
221
+ return decorator
222
+
223
+ async def _on_ready_internal(self, user):
224
+ self._ready.set()
225
+ logger.debug("Ready event received internally")
226
+
227
+ if self._notifications:
228
+ username = user.name if user else "Unknown"
229
+ send_notification(
230
+ title="✅ Bot is Running",
231
+ message=f"Logged in as {username}",
232
+ image_url=START_IMAGE,
233
+ timeout=5
234
+ )
235
+
236
+ async def _on_message_create_internal(self, message: Message):
237
+ logger.debug(f"📩 MESSAGE_CREATE: {message.content[:50] if message.content else '(empty)'}")
238
+ await self._process_commands(message)
239
+
240
+ async def _on_interaction_create(self, data: dict):
241
+ """Handle interaction_create events."""
242
+ try:
243
+ interaction = Interaction(self._state, data)
244
+
245
+ if interaction.custom_id and interaction.custom_id in self._interaction_handlers:
246
+ await self._interaction_handlers[interaction.custom_id](interaction)
247
+ else:
248
+ await interaction_handler.handle(interaction)
249
+ except Exception as e:
250
+ logger.exception(f"Error handling interaction: {e}")
251
+
252
+ async def _on_voice_state_update(self, data: dict):
253
+ """Handle voice state updates."""
254
+ guild_id = int(data.get("guild_id", 0))
255
+ if not guild_id:
256
+ return
257
+
258
+ voice_state = data.get("voice_state", {})
259
+ self._voice.update_state(guild_id, voice_state)
260
+
261
+ async def _process_commands(self, message: Message):
262
+ if not self.user or message.author.id != self.user.id:
263
+ return
264
+
265
+ if message.author.bot:
266
+ logger.debug("Message from bot, ignoring")
267
+ return
268
+
269
+ if callable(self.command_prefix):
270
+ prefix = self.command_prefix(self, message)
271
+ else:
272
+ prefix = self.command_prefix
273
+
274
+ if not prefix:
275
+ return
276
+
277
+ content = message.content
278
+ if not content:
279
+ return
280
+
281
+ logger.debug(f"Checking command: content='{content}', prefix='{prefix}'")
282
+
283
+ if not content.startswith(prefix):
284
+ if self.user:
285
+ mention = f"<@{self.user.id}>"
286
+ mention_nick = f"<@!{self.user.id}>"
287
+ if content.startswith(mention):
288
+ content = content[len(mention):].lstrip()
289
+ logger.debug(f"Stripped mention: '{content}'")
290
+ elif content.startswith(mention_nick):
291
+ content = content[len(mention_nick):].lstrip()
292
+ logger.debug(f"Stripped mention_nick: '{content}'")
293
+ else:
294
+ return
295
+ else:
296
+ return
297
+ else:
298
+ content = content[len(prefix):].strip()
299
+ logger.debug(f"Stripped prefix: '{content}'")
300
+
301
+ if not content:
302
+ return
303
+
304
+ parts = content.split()
305
+ name = parts[0]
306
+ args = parts[1:]
307
+
308
+ logger.info(f"Command detected: {name} with args: {args}")
309
+
310
+ command = self._commands.get(name)
311
+ if not command:
312
+ logger.debug(f"Command not found: {name}")
313
+ for handler in self._event_handlers.get("COMMAND_NOT_FOUND", []):
314
+ try:
315
+ await handler(message, name)
316
+ except Exception:
317
+ logger.exception("Error in command not found handler")
318
+ return
319
+
320
+ ctx = Context(
321
+ message=message,
322
+ command=command,
323
+ args=args,
324
+ kwargs={},
325
+ bot=self,
326
+ )
327
+
328
+ try:
329
+ await command.invoke(ctx)
330
+ logger.info(f"Command {name} executed successfully")
331
+ except CommandError as exc:
332
+ logger.warning("Command error in %s: %s", command.name, exc)
333
+ self._handle_error(f"Command error in {command.name}: {exc}")
334
+ for handler in self._event_handlers.get("COMMAND_ERROR", []):
335
+ try:
336
+ await handler(ctx, exc)
337
+ except Exception:
338
+ logger.exception("Error in command error handler")
339
+ except Exception as exc:
340
+ logger.exception("Unexpected error in command %s", command.name)
341
+ self._handle_error(f"Unexpected error in {command.name}: {exc}")
342
+ for handler in self._event_handlers.get("COMMAND_ERROR", []):
343
+ try:
344
+ await handler(ctx, exc)
345
+ except Exception:
346
+ pass
347
+
348
+ def _handle_error(self, error_message: str):
349
+ if self._notifications and not self._error_handled:
350
+ self._error_handled = True
351
+ send_notification(
352
+ title="❌ Error Occurred",
353
+ message=error_message[:100] + ("..." if len(error_message) > 100 else ""),
354
+ image_url=ERROR_IMAGE,
355
+ timeout=10
356
+ )
357
+ asyncio.get_event_loop().call_later(2, lambda: setattr(self, '_error_handled', False))
358
+
359
+ async def start(self):
360
+ self._closed = False
361
+ self._error_handled = False
362
+ try:
363
+ await self._gateway.connect()
364
+ except Exception as exc:
365
+ logger.exception("Gateway connection error: %s", exc)
366
+ self._handle_error(f"Failed to connect: {exc}")
367
+ raise
368
+
369
+ async def close(self):
370
+ if self._closed:
371
+ return
372
+
373
+ self._closed = True
374
+ self._ready.clear()
375
+
376
+ await self._voice.disconnect_all()
377
+ await self._gateway.close()
378
+ await self._http.close()
379
+
380
+ logger.info("Client closed")
381
+
382
+ def run(self):
383
+ loop = asyncio.new_event_loop()
384
+ asyncio.set_event_loop(loop)
385
+
386
+ self._close_task = None
387
+
388
+ def signal_handler(sig):
389
+ logger.info("Received signal %s, shutting down...", sig)
390
+ if loop.is_running():
391
+ asyncio.run_coroutine_threadsafe(self.close(), loop)
392
+
393
+ try:
394
+ for sig in (signal.SIGINT, signal.SIGTERM):
395
+ loop.add_signal_handler(sig, lambda s=sig: signal_handler(s))
396
+ except NotImplementedError:
397
+ pass
398
+
399
+ async def runner():
400
+ try:
401
+ await self.start()
402
+ except Exception as exc:
403
+ self._handle_error(f"Bot crashed: {exc}")
404
+ raise
405
+ finally:
406
+ await self.close()
407
+
408
+ try:
409
+ loop.run_until_complete(runner())
410
+ except KeyboardInterrupt:
411
+ logger.info("Keyboard interrupt received, shutting down...")
412
+ except Exception as exc:
413
+ self._handle_error(f"Bot crashed: {exc}")
414
+ raise
415
+ finally:
416
+ try:
417
+ loop.run_until_complete(loop.shutdown_asyncgens())
418
+ finally:
419
+ loop.close()
420
+
421
+ async def wait_until_ready(self):
422
+ await self._ready.wait()
423
+
424
+ # ============================================================
425
+ # FETCH METHODS
426
+ # ============================================================
427
+ async def fetch_user(self, user_id: int):
428
+ data = await self._http.request(Route.user(user_id))
429
+ return self._state._add_user(data)
430
+
431
+ async def fetch_guild(self, guild_id: int):
432
+ data = await self._http.request(Route.guild(guild_id))
433
+ return self._state._add_guild(data)
434
+
435
+ async def fetch_channel(self, channel_id: int):
436
+ data = await self._http.request(Route.channel(channel_id))
437
+ return self._state._add_channel(data)
438
+
439
+ async def fetch_message(self, channel_id: int, message_id: int):
440
+ data = await self._http.request(Route.channel_message(channel_id, message_id))
441
+ return self._state._store_message(data)
442
+
443
+ def get_guild(self, guild_id: int):
444
+ return self._state._guilds.get(guild_id)
445
+
446
+ def get_channel(self, channel_id: int):
447
+ return self._state._channels.get(channel_id)
448
+
449
+ def get_user(self, user_id: int):
450
+ return self._state._users.get(user_id)
451
+
452
+ # ============================================================
453
+ # RELATIONSHIPS
454
+ # ============================================================
455
+ async def get_relationships(self) -> List[Relationship]:
456
+ data = await self._http.request(Route("GET", "/users/@me/relationships"))
457
+ return [Relationship(state=self._state, data=r) for r in data]
458
+
459
+ async def add_friend(self, user_id: int, username: str = None) -> Dict[str, Any]:
460
+ payload = {"username": username} if username else {}
461
+ return await self._http.request(
462
+ Route("POST", f"/users/@me/relationships/{user_id}"),
463
+ json=payload
464
+ )
465
+
466
+ async def remove_friend(self, user_id: int) -> None:
467
+ await self._http.request(
468
+ Route("DELETE", f"/users/@me/relationships/{user_id}")
469
+ )
470
+
471
+ async def block_user(self, user_id: int) -> None:
472
+ await self._http.request(
473
+ Route("PUT", f"/users/@me/relationships/{user_id}"),
474
+ json={"type": 2}
475
+ )
476
+
477
+ async def unblock_user(self, user_id: int) -> None:
478
+ await self._http.request(
479
+ Route("DELETE", f"/users/@me/relationships/{user_id}")
480
+ )
481
+
482
+ # ============================================================
483
+ # BILLING
484
+ # ============================================================
485
+ async def get_payment_sources(self) -> List[PaymentSource]:
486
+ data = await self._http.request(Route("GET", "/users/@me/billing/payment-sources"))
487
+ return [PaymentSource(state=self._state, data=s) for s in data]
488
+
489
+ async def get_subscriptions(self) -> List[Subscription]:
490
+ data = await self._http.request(Route("GET", "/users/@me/billing/subscriptions"))
491
+ return [Subscription(state=self._state, data=s) for s in data]
492
+
493
+ async def get_entitlements(self) -> List[Dict[str, Any]]:
494
+ return await self._http.request(Route("GET", "/users/@me/entitlements"))
495
+
496
+ async def get_skus(self) -> List[Dict[str, Any]]:
497
+ return await self._http.request(Route("GET", "/users/@me/entitlements/skus"))
498
+
499
+ # ============================================================
500
+ # SETTINGS
501
+ # ============================================================
502
+ async def get_user_settings(self) -> UserSettings:
503
+ data = await self._http.request(Route("GET", "/users/@me/settings"))
504
+ return UserSettings(data)
505
+
506
+ async def update_user_settings(self, **kwargs) -> Dict[str, Any]:
507
+ return await self._http.request(
508
+ Route("PATCH", "/users/@me/settings"),
509
+ json=kwargs
510
+ )
511
+
512
+ async def set_status(self, status: str) -> Dict[str, Any]:
513
+ return await self.update_user_settings(status=status)
514
+
515
+ async def set_theme(self, theme: str) -> Dict[str, Any]:
516
+ return await self.update_user_settings(theme=theme)
517
+
518
+ async def set_language(self, locale: str) -> Dict[str, Any]:
519
+ return await self.update_user_settings(locale=locale)
520
+
521
+ async def set_dev_mode(self, enabled: bool) -> Dict[str, Any]:
522
+ return await self.update_user_settings(developer_mode=enabled)
523
+
524
+ async def get_guild_settings(self, guild_id: int) -> GuildSettings:
525
+ data = await self._http.request(
526
+ Route("GET", f"/users/@me/guilds/{guild_id}/settings")
527
+ )
528
+ return GuildSettings(state=self._state, data=data)
529
+
530
+ async def update_guild_settings(self, guild_id: int, **kwargs) -> Dict[str, Any]:
531
+ return await self._http.request(
532
+ Route("PATCH", f"/users/@me/guilds/{guild_id}/settings"),
533
+ json=kwargs
534
+ )
535
+
536
+ # ============================================================
537
+ # PROFILE
538
+ # ============================================================
539
+ async def get_profile(self, user_id: int = None) -> Dict[str, Any]:
540
+ endpoint = f"/users/{user_id}/profile" if user_id else "/users/@me/profile"
541
+ return await self._http.request(Route("GET", endpoint))
542
+
543
+ async def update_avatar(self, avatar_data: str) -> Dict[str, Any]:
544
+ return await self._http.request(
545
+ Route("PATCH", "/users/@me"),
546
+ json={"avatar": avatar_data}
547
+ )
548
+
549
+ async def update_banner(self, banner_data: str) -> Dict[str, Any]:
550
+ return await self._http.request(
551
+ Route("PATCH", "/users/@me"),
552
+ json={"banner": banner_data}
553
+ )
554
+
555
+ async def update_bio(self, bio: str) -> Dict[str, Any]:
556
+ return await self._http.request(
557
+ Route("PATCH", "/users/@me"),
558
+ json={"bio": bio}
559
+ )
560
+
561
+ async def update_display_name(self, name: str) -> Dict[str, Any]:
562
+ return await self._http.request(
563
+ Route("PATCH", "/users/@me"),
564
+ json={"global_name": name}
565
+ )
566
+
567
+ async def get_connections(self) -> List[Dict[str, Any]]:
568
+ return await self._http.request(Route("GET", "/users/@me/connections"))
569
+
570
+ # ============================================================
571
+ # WEBHOOKS
572
+ # ============================================================
573
+ async def get_webhook(self, webhook_id: int) -> Webhook:
574
+ """Get a webhook by ID."""
575
+ data = await self._http.request(Route("GET", f"/webhooks/{webhook_id}"))
576
+ return Webhook(state=self._state, data=data)
577
+
578
+ async def get_webhook_with_token(self, webhook_id: int, token: str) -> Webhook:
579
+ """Get a webhook by ID and token."""
580
+ data = await self._http.request(
581
+ method="GET",
582
+ url=f"/webhooks/{webhook_id}/{token}",
583
+ )
584
+ return Webhook(state=self._state, data=data)
585
+
586
+ async def get_channel_webhooks(self, channel_id: int) -> List[Webhook]:
587
+ """Get all webhooks in a channel."""
588
+ data = await self._http.request(Route("GET", f"/channels/{channel_id}/webhooks"))
589
+ return [Webhook(state=self._state, data=w) for w in data]
590
+
591
+ async def get_guild_webhooks(self, guild_id: int) -> List[Webhook]:
592
+ """Get all webhooks in a guild."""
593
+ data = await self._http.request(Route("GET", f"/guilds/{guild_id}/webhooks"))
594
+ return [Webhook(state=self._state, data=w) for w in data]
595
+
596
+ async def create_webhook(
597
+ self,
598
+ channel_id: int,
599
+ name: str,
600
+ avatar: Optional[str] = None,
601
+ ) -> Webhook:
602
+ """Create a webhook in a channel."""
603
+ payload = {"name": name}
604
+ if avatar:
605
+ payload["avatar"] = avatar
606
+ data = await self._http.request(
607
+ Route("POST", f"/channels/{channel_id}/webhooks"),
608
+ json=payload,
609
+ )
610
+ return Webhook(state=self._state, data=data)
611
+
612
+ # ============================================================
613
+ # VOICE
614
+ # ============================================================
615
+ async def create_voice_client(
616
+ self,
617
+ channel_id: int,
618
+ guild_id: int,
619
+ endpoint: str,
620
+ token: str,
621
+ session_id: str,
622
+ ) -> VoiceClient:
623
+ """Create and connect a voice client."""
624
+ return await self._voice.connect(
625
+ channel_id,
626
+ guild_id,
627
+ endpoint,
628
+ token,
629
+ session_id,
630
+ )
631
+
632
+ def get_voice_client(self, guild_id: int) -> Optional[VoiceClient]:
633
+ """Get the voice client for a guild."""
634
+ return self._voice.get_client(guild_id)
635
+
636
+ async def disconnect_voice(self, guild_id: int) -> None:
637
+ """Disconnect voice client for a guild."""
638
+ await self._voice.disconnect(guild_id)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes