modifyself 0.1.9__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 (51) hide show
  1. {modifyself-0.1.9 → modifyself-0.2.0}/PKG-INFO +1 -1
  2. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/__init__.py +12 -12
  3. modifyself-0.2.0/modifyself/activity.py +0 -0
  4. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/client.py +240 -85
  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.2.0/modifyself/voice.py +0 -0
  13. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself.egg-info/PKG-INFO +1 -1
  14. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself.egg-info/SOURCES.txt +9 -1
  15. {modifyself-0.1.9 → modifyself-0.2.0}/pyproject.toml +29 -29
  16. modifyself-0.1.9/modifyself/types.py +0 -106
  17. {modifyself-0.1.9 → modifyself-0.2.0}/LICENSE +0 -0
  18. {modifyself-0.1.9 → modifyself-0.2.0}/README.md +0 -0
  19. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/__init__.py +0 -0
  20. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/checks.py +0 -0
  21. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/cog.py +0 -0
  22. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/context.py +0 -0
  23. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/converters.py +0 -0
  24. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/commands/core.py +0 -0
  25. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/core/bitfield.py +0 -0
  26. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/core/enums.py +0 -0
  27. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/core/mixins.py +0 -0
  28. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/core/snowflake.py +0 -0
  29. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/errors.py +0 -0
  30. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/gateway/__init__.py +0 -0
  31. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/gateway/dispatcher.py +0 -0
  32. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/gateway/heartbeat.py +0 -0
  33. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/gateway/websocket.py +0 -0
  34. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/headers.py +0 -0
  35. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/http/__init__.py +0 -0
  36. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/http/client.py +0 -0
  37. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/http/ratelimit.py +0 -0
  38. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/http/route.py +0 -0
  39. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/__init__.py +0 -0
  40. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/base.py +0 -0
  41. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/channel.py +0 -0
  42. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/guild.py +0 -0
  43. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/member.py +0 -0
  44. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/message.py +0 -0
  45. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/models/user.py +0 -0
  46. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/state.py +0 -0
  47. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself/utils.py +0 -0
  48. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself.egg-info/dependency_links.txt +0 -0
  49. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself.egg-info/requires.txt +0 -0
  50. {modifyself-0.1.9 → modifyself-0.2.0}/modifyself.egg-info/top_level.txt +0 -0
  51. {modifyself-0.1.9 → 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.9
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
@@ -1,12 +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"]
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
@@ -19,8 +19,14 @@ from .commands.context import Context
19
19
  from .commands.cog import Cog
20
20
  from .errors import CommandError, ConversionError, CommandNotFound
21
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
22
26
  from .headers import HeaderSpoofer, EMULATION
23
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
24
30
 
25
31
  logger = logging.getLogger(__name__)
26
32
 
@@ -50,14 +56,13 @@ class Client:
50
56
  command_prefix: Union[str, Callable[["Client", Message], str]] = "!",
51
57
  owner_ids: Optional[List[int]] = None,
52
58
  proxy: Optional[str] = None,
53
- notifications: bool = True, # NEW: enable/disable notifications
59
+ notifications: bool = True,
54
60
  ):
55
61
  self.token = token
56
62
  self.command_prefix = command_prefix
57
63
  self.owner_ids = set(owner_ids) if owner_ids else set()
58
- self._notifications = notifications # NEW
64
+ self._notifications = notifications
59
65
 
60
- # Internal components
61
66
  self._headers = HeaderSpoofer(token, EMULATION)
62
67
  self._http = HTTPClient(
63
68
  token,
@@ -72,26 +77,27 @@ class Client:
72
77
  headers=self._headers,
73
78
  )
74
79
 
75
- # Event handlers
76
80
  self._event_handlers: Dict[str, List[Callable]] = {}
77
81
  self._once_handlers: Dict[str, List[Callable]] = {}
78
82
 
79
- # Command registry
80
83
  self._commands: Dict[str, Command] = {}
81
84
  self._cogs: Dict[str, Cog] = {}
82
85
 
83
- # Lifecycle
84
86
  self._ready = asyncio.Event()
85
87
  self._closed = False
86
88
  self._task: Optional[asyncio.Task] = None
87
89
  self._close_task: Optional[asyncio.Task] = None
88
- self._error_handled = False # NEW: prevent duplicate error notifications
90
+ self._error_handled = False
91
+
92
+ self._interaction_handlers: Dict[str, Callable] = {}
93
+
94
+ self._voice = VoiceManager(self)
89
95
 
90
- # Auto-register built-in event parsers
91
96
  self._dispatcher.on("READY", self._on_ready_internal)
92
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)
93
100
 
94
- # Send startup notification
95
101
  if self._notifications:
96
102
  send_notification(
97
103
  title="🚀 modifyself Initialized",
@@ -100,49 +106,40 @@ class Client:
100
106
  timeout=3
101
107
  )
102
108
 
103
- # ------------------------------------------------------------------
104
- # Properties
105
- # ------------------------------------------------------------------
106
109
  @property
107
110
  def user(self):
108
- """The client's user object."""
109
111
  return self._state.user
110
112
 
111
113
  @property
112
114
  def guilds(self):
113
- """A view of the guilds the client is in."""
114
115
  return list(self._state._guilds.values())
115
116
 
116
117
  @property
117
118
  def users(self):
118
- """A view of the users the client has cached."""
119
119
  return list(self._state._users.values())
120
120
 
121
121
  @property
122
122
  def latency(self) -> float:
123
- """The current gateway latency in seconds."""
124
123
  return self._gateway.latency
125
124
 
126
125
  @property
127
126
  def listeners(self) -> Dict[str, List]:
128
- """A view of registered event listeners by event name."""
129
127
  return dict(self._dispatcher._handlers)
130
128
 
131
129
  @property
132
130
  def commands(self) -> List[Command]:
133
- """Return a list of registered commands."""
134
131
  return list(self._commands.values())
135
132
 
136
133
  @property
137
134
  def cogs(self) -> List[Cog]:
138
- """Return a list of loaded cogs."""
139
135
  return list(self._cogs.values())
140
136
 
141
- # ------------------------------------------------------------------
142
- # Event decorators
143
- # ------------------------------------------------------------------
137
+ @property
138
+ def voice(self) -> VoiceManager:
139
+ """Access voice manager."""
140
+ return self._voice
141
+
144
142
  def event(self, coro: Callable):
145
- """Register an event handler."""
146
143
  if not asyncio.iscoroutinefunction(coro):
147
144
  raise TypeError("Event handlers must be coroutines")
148
145
  name = coro.__name__.replace("on_", "", 1).upper()
@@ -152,7 +149,6 @@ class Client:
152
149
  return coro
153
150
 
154
151
  def listen(self, name: Optional[str] = None):
155
- """Decorator to register an event listener by explicit name."""
156
152
  def decorator(coro: Callable):
157
153
  if not asyncio.iscoroutinefunction(coro):
158
154
  raise TypeError("Listeners must be coroutines")
@@ -162,20 +158,14 @@ class Client:
162
158
  return decorator
163
159
 
164
160
  def add_listener(self, event: str, handler: Callable):
165
- """Add a listener for an event."""
166
161
  event = event.upper()
167
162
  self._dispatcher.on(event, handler)
168
163
  logger.info("[client] listener registered: %s -> %s", event, handler)
169
164
 
170
165
  def remove_listener(self, event: str, handler: Callable):
171
- """Remove a listener for an event."""
172
166
  self._dispatcher.off(event.upper(), handler)
173
167
 
174
- # ------------------------------------------------------------------
175
- # Command registration
176
- # ------------------------------------------------------------------
177
168
  def command(self, *, name: Optional[str] = None, aliases: Optional[List[str]] = None, **kwargs):
178
- """Decorator to register a command."""
179
169
  def decorator(func: Callable):
180
170
  cmd = Command(func, name=name, aliases=aliases, **kwargs)
181
171
  self.add_command(cmd)
@@ -184,37 +174,28 @@ class Client:
184
174
  return decorator
185
175
 
186
176
  def add_command(self, cmd: Command):
187
- """Register a Command instance."""
188
177
  if cmd.name in self._commands:
189
178
  raise ValueError(f"Command {cmd.name} is already registered")
190
179
 
191
- # Check aliases first before adding anything
192
180
  for alias in cmd.aliases:
193
181
  if alias in self._commands:
194
182
  raise ValueError(f"Alias {alias} is already registered")
195
183
 
196
- # Now safe to add
197
184
  self._commands[cmd.name] = cmd
198
185
  for alias in cmd.aliases:
199
186
  self._commands[alias] = cmd
200
187
  logger.debug("Registered command: %s", cmd.name)
201
188
 
202
189
  def remove_command(self, name: str):
203
- """Remove a command by name or alias."""
204
190
  cmd = self._commands.pop(name, None)
205
191
  if cmd and cmd.name == name:
206
192
  for alias in cmd.aliases:
207
193
  self._commands.pop(alias, None)
208
194
 
209
195
  def get_command(self, name: str) -> Optional[Command]:
210
- """Get a command by name or alias."""
211
196
  return self._commands.get(name)
212
197
 
213
- # ------------------------------------------------------------------
214
- # Cog management
215
- # ------------------------------------------------------------------
216
198
  def add_cog(self, cog: Cog):
217
- """Add a cog to the bot."""
218
199
  if cog.name in self._cogs:
219
200
  raise ValueError(f"Cog {cog.name} is already loaded")
220
201
  cog._inject(self)
@@ -223,7 +204,6 @@ class Client:
223
204
  logger.info("Loaded cog: %s", cog.name)
224
205
 
225
206
  def remove_cog(self, name: str):
226
- """Remove a cog from the bot."""
227
207
  cog = self._cogs.pop(name, None)
228
208
  if cog:
229
209
  cog.cog_unload()
@@ -231,18 +211,19 @@ class Client:
231
211
  logger.info("Unloaded cog: %s", name)
232
212
 
233
213
  def get_cog(self, name: str) -> Optional[Cog]:
234
- """Get a loaded cog by name."""
235
214
  return self._cogs.get(name)
236
215
 
237
- # ------------------------------------------------------------------
238
- # Internal event handlers
239
- # ------------------------------------------------------------------
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
+
240
223
  async def _on_ready_internal(self, user):
241
- """Internal READY handler."""
242
224
  self._ready.set()
243
225
  logger.debug("Ready event received internally")
244
226
 
245
- # Send notification when bot is ready
246
227
  if self._notifications:
247
228
  username = user.name if user else "Unknown"
248
229
  send_notification(
@@ -253,22 +234,38 @@ class Client:
253
234
  )
254
235
 
255
236
  async def _on_message_create_internal(self, message: Message):
256
- """Internal MESSAGE_CREATE handler for command processing."""
257
237
  logger.debug(f"📩 MESSAGE_CREATE: {message.content[:50] if message.content else '(empty)'}")
258
238
  await self._process_commands(message)
259
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
+
260
261
  async def _process_commands(self, message: Message):
261
- """Check if a message triggers a command and invoke it."""
262
- # SELF-BOT: Only respond to our own messages
263
262
  if not self.user or message.author.id != self.user.id:
264
263
  return
265
264
 
266
- # Don't process messages from bots
267
265
  if message.author.bot:
268
266
  logger.debug("Message from bot, ignoring")
269
267
  return
270
268
 
271
- # Resolve prefix
272
269
  if callable(self.command_prefix):
273
270
  prefix = self.command_prefix(self, message)
274
271
  else:
@@ -283,9 +280,7 @@ class Client:
283
280
 
284
281
  logger.debug(f"Checking command: content='{content}', prefix='{prefix}'")
285
282
 
286
- # Check for prefix
287
283
  if not content.startswith(prefix):
288
- # Check for mention prefix (if user account)
289
284
  if self.user:
290
285
  mention = f"<@{self.user.id}>"
291
286
  mention_nick = f"<@!{self.user.id}>"
@@ -300,7 +295,6 @@ class Client:
300
295
  else:
301
296
  return
302
297
  else:
303
- # Strip prefix
304
298
  content = content[len(prefix):].strip()
305
299
  logger.debug(f"Stripped prefix: '{content}'")
306
300
 
@@ -316,7 +310,6 @@ class Client:
316
310
  command = self._commands.get(name)
317
311
  if not command:
318
312
  logger.debug(f"Command not found: {name}")
319
- # Fire command not found event if registered
320
313
  for handler in self._event_handlers.get("COMMAND_NOT_FOUND", []):
321
314
  try:
322
315
  await handler(message, name)
@@ -352,11 +345,7 @@ class Client:
352
345
  except Exception:
353
346
  pass
354
347
 
355
- # ------------------------------------------------------------------
356
- # Error handling
357
- # ------------------------------------------------------------------
358
348
  def _handle_error(self, error_message: str):
359
- """Send a notification when an error occurs."""
360
349
  if self._notifications and not self._error_handled:
361
350
  self._error_handled = True
362
351
  send_notification(
@@ -365,14 +354,9 @@ class Client:
365
354
  image_url=ERROR_IMAGE,
366
355
  timeout=10
367
356
  )
368
- # Reset after a moment so new errors can trigger notifications
369
357
  asyncio.get_event_loop().call_later(2, lambda: setattr(self, '_error_handled', False))
370
358
 
371
- # ------------------------------------------------------------------
372
- # Lifecycle
373
- # ------------------------------------------------------------------
374
359
  async def start(self):
375
- """Start the client (connects to gateway)."""
376
360
  self._closed = False
377
361
  self._error_handled = False
378
362
  try:
@@ -383,47 +367,39 @@ class Client:
383
367
  raise
384
368
 
385
369
  async def close(self):
386
- """Close the client and cleanup."""
387
370
  if self._closed:
388
371
  return
389
372
 
390
373
  self._closed = True
391
374
  self._ready.clear()
392
375
 
393
- # Close gateway first
376
+ await self._voice.disconnect_all()
394
377
  await self._gateway.close()
395
-
396
- # Close HTTP client
397
378
  await self._http.close()
398
379
 
399
380
  logger.info("Client closed")
400
381
 
401
382
  def run(self):
402
- """Run the client with a new event loop."""
403
383
  loop = asyncio.new_event_loop()
404
384
  asyncio.set_event_loop(loop)
405
385
 
406
- # Store close task reference
407
386
  self._close_task = None
408
387
 
409
388
  def signal_handler(sig):
410
389
  logger.info("Received signal %s, shutting down...", sig)
411
390
  if loop.is_running():
412
- # Schedule close in the event loop
413
391
  asyncio.run_coroutine_threadsafe(self.close(), loop)
414
392
 
415
393
  try:
416
394
  for sig in (signal.SIGINT, signal.SIGTERM):
417
395
  loop.add_signal_handler(sig, lambda s=sig: signal_handler(s))
418
396
  except NotImplementedError:
419
- # Windows doesn't support add_signal_handler
420
397
  pass
421
398
 
422
399
  async def runner():
423
400
  try:
424
401
  await self.start()
425
402
  except Exception as exc:
426
- # Catch any unhandled errors in the main loop
427
403
  self._handle_error(f"Bot crashed: {exc}")
428
404
  raise
429
405
  finally:
@@ -434,7 +410,6 @@ class Client:
434
410
  except KeyboardInterrupt:
435
411
  logger.info("Keyboard interrupt received, shutting down...")
436
412
  except Exception as exc:
437
- # Any unhandled exception at the top level
438
413
  self._handle_error(f"Bot crashed: {exc}")
439
414
  raise
440
415
  finally:
@@ -444,40 +419,220 @@ class Client:
444
419
  loop.close()
445
420
 
446
421
  async def wait_until_ready(self):
447
- """Wait until the client receives the READY event."""
448
422
  await self._ready.wait()
449
423
 
450
- # ------------------------------------------------------------------
451
- # Convenience methods
452
- # ------------------------------------------------------------------
424
+ # ============================================================
425
+ # FETCH METHODS
426
+ # ============================================================
453
427
  async def fetch_user(self, user_id: int):
454
- """Fetch a user from the HTTP API."""
455
428
  data = await self._http.request(Route.user(user_id))
456
429
  return self._state._add_user(data)
457
430
 
458
431
  async def fetch_guild(self, guild_id: int):
459
- """Fetch a guild from the HTTP API."""
460
432
  data = await self._http.request(Route.guild(guild_id))
461
433
  return self._state._add_guild(data)
462
434
 
463
435
  async def fetch_channel(self, channel_id: int):
464
- """Fetch a channel from the HTTP API."""
465
436
  data = await self._http.request(Route.channel(channel_id))
466
437
  return self._state._add_channel(data)
467
438
 
468
439
  async def fetch_message(self, channel_id: int, message_id: int):
469
- """Fetch a message from the HTTP API."""
470
440
  data = await self._http.request(Route.channel_message(channel_id, message_id))
471
441
  return self._state._store_message(data)
472
442
 
473
443
  def get_guild(self, guild_id: int):
474
- """Get a cached guild by ID."""
475
444
  return self._state._guilds.get(guild_id)
476
445
 
477
446
  def get_channel(self, channel_id: int):
478
- """Get a cached channel by ID."""
479
447
  return self._state._channels.get(channel_id)
480
448
 
481
449
  def get_user(self, user_id: int):
482
- """Get a cached user by ID."""
483
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
@@ -0,0 +1,162 @@
1
+ """
2
+ Type definitions for modifyself.
3
+ """
4
+
5
+ from typing import Any, Dict, List, Optional, Union, Callable, Coroutine, TypeVar, Generic
6
+ from typing_extensions import Self
7
+
8
+ # ============================================
9
+ # Basic Types
10
+ # ============================================
11
+
12
+ Snowflake = int
13
+ Timestamp = str
14
+ Color = int
15
+
16
+ # ============================================
17
+ # Command Types
18
+ # ============================================
19
+
20
+ CommandCallback = Callable[..., Coroutine[Any, Any, Any]]
21
+ CommandName = str
22
+ CommandAliases = List[str]
23
+ CommandCooldown = int
24
+
25
+ # ============================================
26
+ # Event Types
27
+ # ============================================
28
+
29
+ EventHandler = Callable[..., Coroutine[Any, Any, Any]]
30
+ EventName = str
31
+
32
+ # ============================================
33
+ # HTTP Types
34
+ # ============================================
35
+
36
+ HTTPMethod = str
37
+ HTTPHeaders = Dict[str, str]
38
+ HTTPParams = Dict[str, Any]
39
+ HTTPData = Union[Dict[str, Any], str, None]
40
+
41
+ # ============================================
42
+ # Gateway Types
43
+ # ============================================
44
+
45
+ GatewayPayload = Dict[str, Any]
46
+ GatewayOPCode = int
47
+ GatewaySequence = int
48
+
49
+ # ============================================
50
+ # Model Types
51
+ # ============================================
52
+
53
+ T = TypeVar('T')
54
+
55
+ class CachedObject(Generic[T]):
56
+ """Base class for cached objects."""
57
+ def __init__(self, data: T, state: Any) -> None:
58
+ self._data = data
59
+ self._state = state
60
+
61
+ def _update(self, data: T) -> None:
62
+ """Update the object with new data."""
63
+ self._data = data
64
+
65
+ # ============================================
66
+ # Permission Types
67
+ # ============================================
68
+
69
+ PermissionValue = int
70
+ PermissionOverwrite = Dict[str, Any]
71
+
72
+ # ============================================
73
+ # Channel Types (using Python 3.12 union syntax)
74
+ # ============================================
75
+
76
+ ChannelID = int
77
+ GuildID = int
78
+ UserID = int
79
+ MessageID = int
80
+ RoleID = int
81
+ EmojiID = int
82
+
83
+ # ============================================
84
+ # Context Types
85
+ # ============================================
86
+
87
+ class ContextBase:
88
+ """Base context class."""
89
+ def __init__(self, **kwargs: Any) -> None:
90
+ for key, value in kwargs.items():
91
+ setattr(self, key, value)
92
+
93
+ # ============================================
94
+ # Option Types (Python 3.12 union syntax)
95
+ # ============================================
96
+
97
+ OptionalStr = str | None
98
+ OptionalInt = int | None
99
+ OptionalList = list | None
100
+ OptionalDict = dict | None
101
+
102
+ # ============================================
103
+ # Function Types
104
+ # ============================================
105
+
106
+ AsyncFunc = Callable[..., Coroutine[Any, Any, Any]]
107
+ SyncFunc = Callable[..., Any]
108
+ Predicate = Callable[[Any], bool]
109
+
110
+ # ============================================
111
+ # State Types
112
+ # ============================================
113
+
114
+ StateCache = Dict[Snowflake, Any]
115
+ StateUpdate = Dict[str, Any]
116
+
117
+ # ============================================
118
+ # Error Types
119
+ # ============================================
120
+
121
+ ErrorCode = int
122
+ ErrorMessage = str
123
+
124
+ # ============================================
125
+ # Rate Limit Types
126
+ # ============================================
127
+
128
+ RateLimitBucket = str
129
+ RateLimitRemaining = int
130
+ RateLimitReset = float
131
+
132
+ # ============================================
133
+ # WebSocket Types
134
+ # ============================================
135
+
136
+ WebSocketURL = str
137
+ WebSocketEvent = Dict[str, Any]
138
+
139
+ # ============================================
140
+ # Notification Types (Python 3.12 union syntax)
141
+ # ============================================
142
+
143
+ NotificationTitle = str
144
+ NotificationMessage = str
145
+ NotificationImage = str | None # Python 3.12 union syntax
146
+ NotificationTimeout = int
147
+
148
+ # ============================================
149
+ # Client Types
150
+ # ============================================
151
+
152
+ ClientToken = str
153
+ ClientPrefix = str | list[str] | Callable # Python 3.12 union syntax
154
+ ClientID = int
155
+
156
+ # ============================================
157
+ # JSON Types
158
+ # ============================================
159
+
160
+ JSONValue = str | int | float | bool | None | dict | list
161
+ JSONObject = dict[str, JSONValue]
162
+ JSONArray = list[JSONValue]
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.1.9
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
@@ -2,12 +2,16 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  modifyself/__init__.py
5
+ modifyself/activity.py
5
6
  modifyself/client.py
7
+ modifyself/components.py
6
8
  modifyself/errors.py
7
9
  modifyself/headers.py
10
+ modifyself/interactions.py
8
11
  modifyself/state.py
9
12
  modifyself/types.py
10
13
  modifyself/utils.py
14
+ modifyself/voice.py
11
15
  modifyself.egg-info/PKG-INFO
12
16
  modifyself.egg-info/SOURCES.txt
13
17
  modifyself.egg-info/dependency_links.txt
@@ -33,8 +37,12 @@ modifyself/http/ratelimit.py
33
37
  modifyself/http/route.py
34
38
  modifyself/models/__init__.py
35
39
  modifyself/models/base.py
40
+ modifyself/models/billing.py
36
41
  modifyself/models/channel.py
37
42
  modifyself/models/guild.py
38
43
  modifyself/models/member.py
39
44
  modifyself/models/message.py
40
- modifyself/models/user.py
45
+ modifyself/models/relationship.py
46
+ modifyself/models/settings.py
47
+ modifyself/models/user.py
48
+ modifyself/models/webhook.py
@@ -1,30 +1,30 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "modifyself"
7
- version = "0.1.9"
8
- description = "A clean, pythonic Discord self-bot library"
9
- readme = "README.md"
10
- license = "MIT"
11
- authors = [
12
- {name = "modifyself", email = "modifyself@example.com"}
13
- ]
14
- requires-python = ">=3.9"
15
- dependencies = [
16
- "websockets>=10.0",
17
- "wreq>=0.1.0",
18
- "aiohttp>=3.8.0",
19
- "plyer>=0.9",
20
- "pillow>=10.0",
21
- "requests>=2.28",
22
- ]
23
-
24
- [project.urls]
25
- Homepage = "https://github.com/clashings/modifyself"
26
- Bug-Reports = "https://github.com/clashings/modifyself/issues"
27
- Source = "https://github.com/clashings/modifyself"
28
-
29
- [tool.setuptools.packages.find]
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "modifyself"
7
+ version = "0.2.0"
8
+ description = "A clean, pythonic Discord self-bot library"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ {name = "modifyself", email = "modifyself@example.com"}
13
+ ]
14
+ requires-python = ">=3.9"
15
+ dependencies = [
16
+ "websockets>=10.0",
17
+ "wreq>=0.1.0",
18
+ "aiohttp>=3.8.0",
19
+ "plyer>=0.9",
20
+ "pillow>=10.0",
21
+ "requests>=2.28",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/clashings/modifyself"
26
+ Bug-Reports = "https://github.com/clashings/modifyself/issues"
27
+ Source = "https://github.com/clashings/modifyself"
28
+
29
+ [tool.setuptools.packages.find]
30
30
  include = ["modifyself*"]
@@ -1,106 +0,0 @@
1
- """
2
- TypedDict mirrors of Discord's JSON payloads.
3
- Update this when Discord changes their API.
4
- """
5
-
6
- from typing import TypedDict, NotRequired
7
-
8
-
9
- class UserPayload(TypedDict):
10
- id: str
11
- username: str
12
- discriminator: str
13
- avatar: str | None
14
- bot: NotRequired[bool]
15
- system: NotRequired[bool]
16
- public_flags: NotRequired[int]
17
-
18
-
19
- class MemberPayload(TypedDict):
20
- user: NotRequired[UserPayload]
21
- nick: NotRequired[str | None]
22
- roles: list[str]
23
- joined_at: str
24
- premium_since: NotRequired[str | None]
25
- deaf: bool
26
- mute: bool
27
- pending: NotRequired[bool]
28
-
29
-
30
- class ChannelPayload(TypedDict):
31
- id: str
32
- type: int
33
- guild_id: NotRequired[str]
34
- name: NotRequired[str]
35
- topic: NotRequired[str | None]
36
- nsfw: NotRequired[bool]
37
- last_message_id: NotRequired[str | None]
38
- parent_id: NotRequired[str | None]
39
- permission_overwrites: NotRequired[list[dict]]
40
- position: NotRequired[int]
41
- recipients: NotRequired[list[UserPayload]]
42
-
43
-
44
- class MessagePayload(TypedDict):
45
- id: str
46
- channel_id: str
47
- author: UserPayload
48
- content: str
49
- timestamp: str
50
- edited_timestamp: str | None
51
- tts: bool
52
- mention_everyone: bool
53
- mentions: list[UserPayload]
54
- mention_roles: list[str]
55
- attachments: list[dict]
56
- embeds: list[dict]
57
- reactions: NotRequired[list[dict]]
58
- pinned: bool
59
- type: int
60
- guild_id: NotRequired[str]
61
- member: NotRequired[MemberPayload]
62
- referenced_message: NotRequired["MessagePayload" | None]
63
-
64
-
65
- class GuildPayload(TypedDict):
66
- id: str
67
- name: str
68
- icon: str | None
69
- splash: str | None
70
- owner_id: str
71
- region: NotRequired[str]
72
- afk_channel_id: str | None
73
- afk_timeout: int
74
- verification_level: int
75
- default_message_notifications: int
76
- explicit_content_filter: int
77
- roles: list[dict]
78
- emojis: list[dict]
79
- features: list[str]
80
- mfa_level: int
81
- system_channel_id: str | None
82
- system_channel_flags: int
83
- max_presences: NotRequired[int | None]
84
- max_members: NotRequired[int]
85
- vanity_url_code: str | None
86
- description: str | None
87
- banner: str | None
88
- premium_tier: int
89
- premium_subscription_count: NotRequired[int]
90
- preferred_locale: str
91
- public_updates_channel_id: str | None
92
- max_video_channel_users: NotRequired[int]
93
- approximate_member_count: NotRequired[int]
94
- approximate_presence_count: NotRequired[int]
95
- nsfw_level: int
96
- members: NotRequired[list[MemberPayload]]
97
- channels: NotRequired[list[ChannelPayload]]
98
-
99
-
100
- class ReadyPayload(TypedDict):
101
- v: int
102
- user: UserPayload
103
- guilds: list[dict]
104
- session_id: str
105
- shard: NotRequired[list[int]]
106
- application: NotRequired[dict]
File without changes
File without changes
File without changes