modifyself 0.2.1__tar.gz → 0.2.3__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 (56) hide show
  1. {modifyself-0.2.1 → modifyself-0.2.3}/PKG-INFO +3 -1
  2. modifyself-0.2.3/modifyself/components.py +286 -0
  3. modifyself-0.2.3/modifyself/models/billing.py +88 -0
  4. modifyself-0.2.3/modifyself/models/message.py +222 -0
  5. modifyself-0.2.3/modifyself/models/relationship.py +47 -0
  6. modifyself-0.2.3/modifyself/models/settings.py +54 -0
  7. modifyself-0.2.3/modifyself/models/webhook.py +168 -0
  8. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself.egg-info/PKG-INFO +3 -1
  9. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself.egg-info/requires.txt +2 -0
  10. {modifyself-0.2.1 → modifyself-0.2.3}/pyproject.toml +5 -1
  11. modifyself-0.2.1/modifyself/components.py +0 -0
  12. modifyself-0.2.1/modifyself/models/billing.py +0 -0
  13. modifyself-0.2.1/modifyself/models/message.py +0 -180
  14. modifyself-0.2.1/modifyself/models/relationship.py +0 -0
  15. modifyself-0.2.1/modifyself/models/settings.py +0 -0
  16. modifyself-0.2.1/modifyself/models/webhook.py +0 -0
  17. {modifyself-0.2.1 → modifyself-0.2.3}/LICENSE +0 -0
  18. {modifyself-0.2.1 → modifyself-0.2.3}/README.md +0 -0
  19. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/__init__.py +0 -0
  20. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/activity.py +0 -0
  21. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/client.py +0 -0
  22. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/__init__.py +0 -0
  23. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/checks.py +0 -0
  24. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/cog.py +0 -0
  25. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/context.py +0 -0
  26. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/converters.py +0 -0
  27. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/commands/core.py +0 -0
  28. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/core/bitfield.py +0 -0
  29. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/core/enums.py +0 -0
  30. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/core/mixins.py +0 -0
  31. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/core/snowflake.py +0 -0
  32. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/errors.py +0 -0
  33. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/gateway/__init__.py +0 -0
  34. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/gateway/dispatcher.py +0 -0
  35. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/gateway/heartbeat.py +0 -0
  36. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/gateway/websocket.py +0 -0
  37. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/headers.py +0 -0
  38. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/http/__init__.py +0 -0
  39. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/http/client.py +0 -0
  40. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/http/ratelimit.py +0 -0
  41. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/http/route.py +0 -0
  42. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/interactions.py +0 -0
  43. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/__init__.py +0 -0
  44. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/base.py +0 -0
  45. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/channel.py +0 -0
  46. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/guild.py +0 -0
  47. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/member.py +0 -0
  48. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/models/user.py +0 -0
  49. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/state.py +0 -0
  50. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/types.py +0 -0
  51. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/utils.py +0 -0
  52. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself/voice.py +0 -0
  53. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself.egg-info/SOURCES.txt +0 -0
  54. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself.egg-info/dependency_links.txt +0 -0
  55. {modifyself-0.2.1 → modifyself-0.2.3}/modifyself.egg-info/top_level.txt +0 -0
  56. {modifyself-0.2.1 → modifyself-0.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A clean, pythonic Discord self-bot library
5
5
  Author-email: modifyself <modifyself@example.com>
6
6
  License-Expression: MIT
@@ -16,6 +16,8 @@ Requires-Dist: aiohttp>=3.8.0
16
16
  Requires-Dist: plyer>=0.9
17
17
  Requires-Dist: pillow>=10.0
18
18
  Requires-Dist: requests>=2.28
19
+ Requires-Dist: PyNaCl>=1.5.0
20
+ Requires-Dist: opuslib>=3.0.0
19
21
  Dynamic: license-file
20
22
 
21
23
  # modifyself
@@ -0,0 +1,286 @@
1
+ """
2
+ Discord components (buttons, selects, modals).
3
+ """
4
+
5
+ from typing import Optional, List, Dict, Any, Union, Callable, Awaitable
6
+ from enum import IntEnum
7
+ import json
8
+
9
+ class ComponentType(IntEnum):
10
+ ACTION_ROW = 1
11
+ BUTTON = 2
12
+ SELECT_MENU = 3
13
+ TEXT_INPUT = 4
14
+ CHANNEL_SELECT = 5
15
+ ROLE_SELECT = 6
16
+ MENTIONABLE_SELECT = 7
17
+ USER_SELECT = 8
18
+
19
+ class ButtonStyle(IntEnum):
20
+ PRIMARY = 1
21
+ SECONDARY = 2
22
+ SUCCESS = 3
23
+ DANGER = 4
24
+ LINK = 5
25
+
26
+ class TextInputStyle(IntEnum):
27
+ SHORT = 1
28
+ PARAGRAPH = 2
29
+
30
+ class Component:
31
+ def __init__(self, data: dict):
32
+ self.type = data.get("type")
33
+ self._data = data
34
+
35
+ def to_dict(self) -> dict:
36
+ return self._data
37
+
38
+ class Button(Component):
39
+ def __init__(
40
+ self,
41
+ style: Union[ButtonStyle, int] = ButtonStyle.PRIMARY,
42
+ label: Optional[str] = None,
43
+ custom_id: Optional[str] = None,
44
+ url: Optional[str] = None,
45
+ emoji: Optional[Dict[str, Any]] = None,
46
+ disabled: bool = False,
47
+ ):
48
+ data = {
49
+ "type": ComponentType.BUTTON,
50
+ "style": int(style),
51
+ "disabled": disabled,
52
+ }
53
+ if label:
54
+ data["label"] = label
55
+ if custom_id:
56
+ data["custom_id"] = custom_id
57
+ if url:
58
+ data["url"] = url
59
+ if emoji:
60
+ data["emoji"] = emoji
61
+ super().__init__(data)
62
+ self.style = style
63
+ self.label = label
64
+ self.custom_id = custom_id
65
+ self.url = url
66
+ self.emoji = emoji
67
+ self.disabled = disabled
68
+
69
+ @classmethod
70
+ def primary(cls, label: str, custom_id: str, emoji: Optional[Dict[str, Any]] = None) -> "Button":
71
+ return cls(style=ButtonStyle.PRIMARY, label=label, custom_id=custom_id, emoji=emoji)
72
+
73
+ @classmethod
74
+ def secondary(cls, label: str, custom_id: str, emoji: Optional[Dict[str, Any]] = None) -> "Button":
75
+ return cls(style=ButtonStyle.SECONDARY, label=label, custom_id=custom_id, emoji=emoji)
76
+
77
+ @classmethod
78
+ def success(cls, label: str, custom_id: str, emoji: Optional[Dict[str, Any]] = None) -> "Button":
79
+ return cls(style=ButtonStyle.SUCCESS, label=label, custom_id=custom_id, emoji=emoji)
80
+
81
+ @classmethod
82
+ def danger(cls, label: str, custom_id: str, emoji: Optional[Dict[str, Any]] = None) -> "Button":
83
+ return cls(style=ButtonStyle.DANGER, label=label, custom_id=custom_id, emoji=emoji)
84
+
85
+ @classmethod
86
+ def link(cls, label: str, url: str, emoji: Optional[Dict[str, Any]] = None) -> "Button":
87
+ return cls(style=ButtonStyle.LINK, label=label, url=url, emoji=emoji)
88
+
89
+ class SelectOption:
90
+ def __init__(
91
+ self,
92
+ label: str,
93
+ value: str,
94
+ description: Optional[str] = None,
95
+ emoji: Optional[Dict[str, Any]] = None,
96
+ default: bool = False,
97
+ ):
98
+ self.label = label
99
+ self.value = value
100
+ self.description = description
101
+ self.emoji = emoji
102
+ self.default = default
103
+
104
+ def to_dict(self) -> dict:
105
+ data = {"label": self.label, "value": self.value}
106
+ if self.description:
107
+ data["description"] = self.description
108
+ if self.emoji:
109
+ data["emoji"] = self.emoji
110
+ if self.default:
111
+ data["default"] = True
112
+ return data
113
+
114
+ class SelectMenu(Component):
115
+ def __init__(
116
+ self,
117
+ custom_id: str,
118
+ options: List[SelectOption],
119
+ placeholder: Optional[str] = None,
120
+ min_values: int = 1,
121
+ max_values: int = 1,
122
+ disabled: bool = False,
123
+ ):
124
+ data = {
125
+ "type": ComponentType.SELECT_MENU,
126
+ "custom_id": custom_id,
127
+ "options": [opt.to_dict() for opt in options],
128
+ "min_values": min_values,
129
+ "max_values": max_values,
130
+ "disabled": disabled,
131
+ }
132
+ if placeholder:
133
+ data["placeholder"] = placeholder
134
+ super().__init__(data)
135
+ self.custom_id = custom_id
136
+ self.options = options
137
+ self.placeholder = placeholder
138
+ self.min_values = min_values
139
+ self.max_values = max_values
140
+ self.disabled = disabled
141
+
142
+ class ChannelSelect(Component):
143
+ def __init__(
144
+ self,
145
+ custom_id: str,
146
+ placeholder: Optional[str] = None,
147
+ min_values: int = 1,
148
+ max_values: int = 1,
149
+ disabled: bool = False,
150
+ channel_types: Optional[List[int]] = None,
151
+ ):
152
+ data = {
153
+ "type": ComponentType.CHANNEL_SELECT,
154
+ "custom_id": custom_id,
155
+ "min_values": min_values,
156
+ "max_values": max_values,
157
+ "disabled": disabled,
158
+ }
159
+ if placeholder:
160
+ data["placeholder"] = placeholder
161
+ if channel_types:
162
+ data["channel_types"] = channel_types
163
+ super().__init__(data)
164
+
165
+ class RoleSelect(Component):
166
+ def __init__(
167
+ self,
168
+ custom_id: str,
169
+ placeholder: Optional[str] = None,
170
+ min_values: int = 1,
171
+ max_values: int = 1,
172
+ disabled: bool = False,
173
+ ):
174
+ data = {
175
+ "type": ComponentType.ROLE_SELECT,
176
+ "custom_id": custom_id,
177
+ "min_values": min_values,
178
+ "max_values": max_values,
179
+ "disabled": disabled,
180
+ }
181
+ if placeholder:
182
+ data["placeholder"] = placeholder
183
+ super().__init__(data)
184
+
185
+ class MentionableSelect(Component):
186
+ def __init__(
187
+ self,
188
+ custom_id: str,
189
+ placeholder: Optional[str] = None,
190
+ min_values: int = 1,
191
+ max_values: int = 1,
192
+ disabled: bool = False,
193
+ ):
194
+ data = {
195
+ "type": ComponentType.MENTIONABLE_SELECT,
196
+ "custom_id": custom_id,
197
+ "min_values": min_values,
198
+ "max_values": max_values,
199
+ "disabled": disabled,
200
+ }
201
+ if placeholder:
202
+ data["placeholder"] = placeholder
203
+ super().__init__(data)
204
+
205
+ class UserSelect(Component):
206
+ def __init__(
207
+ self,
208
+ custom_id: str,
209
+ placeholder: Optional[str] = None,
210
+ min_values: int = 1,
211
+ max_values: int = 1,
212
+ disabled: bool = False,
213
+ ):
214
+ data = {
215
+ "type": ComponentType.USER_SELECT,
216
+ "custom_id": custom_id,
217
+ "min_values": min_values,
218
+ "max_values": max_values,
219
+ "disabled": disabled,
220
+ }
221
+ if placeholder:
222
+ data["placeholder"] = placeholder
223
+ super().__init__(data)
224
+
225
+ class TextInput(Component):
226
+ def __init__(
227
+ self,
228
+ custom_id: str,
229
+ label: str,
230
+ style: Union[TextInputStyle, int] = TextInputStyle.SHORT,
231
+ placeholder: Optional[str] = None,
232
+ value: Optional[str] = None,
233
+ min_length: Optional[int] = None,
234
+ max_length: Optional[int] = None,
235
+ required: bool = True,
236
+ ):
237
+ data = {
238
+ "type": ComponentType.TEXT_INPUT,
239
+ "custom_id": custom_id,
240
+ "label": label,
241
+ "style": int(style),
242
+ "required": required,
243
+ }
244
+ if placeholder:
245
+ data["placeholder"] = placeholder
246
+ if value:
247
+ data["value"] = value
248
+ if min_length:
249
+ data["min_length"] = min_length
250
+ if max_length:
251
+ data["max_length"] = max_length
252
+ super().__init__(data)
253
+
254
+ class ActionRow:
255
+ def __init__(self, *components: Component):
256
+ self.components = list(components)
257
+
258
+ def add_component(self, component: Component):
259
+ self.components.append(component)
260
+
261
+ def to_dict(self) -> dict:
262
+ return {
263
+ "type": ComponentType.ACTION_ROW,
264
+ "components": [c.to_dict() for c in self.components],
265
+ }
266
+
267
+ class Modal:
268
+ def __init__(
269
+ self,
270
+ custom_id: str,
271
+ title: str,
272
+ components: Optional[List[Component]] = None,
273
+ ):
274
+ self.custom_id = custom_id
275
+ self.title = title
276
+ self.components = components or []
277
+
278
+ def add_component(self, component: Component):
279
+ self.components.append(component)
280
+
281
+ def to_dict(self) -> dict:
282
+ return {
283
+ "custom_id": self.custom_id,
284
+ "title": self.title,
285
+ "components": [ActionRow(c).to_dict() for c in self.components],
286
+ }
@@ -0,0 +1,88 @@
1
+ """
2
+ Billing models (payment sources, etc.)
3
+ """
4
+
5
+ from typing import Optional, Dict, Any
6
+ from .base import DiscordObject
7
+
8
+ class PaymentSource(DiscordObject):
9
+ """Represents a payment source (card, PayPal, etc.)."""
10
+
11
+ __slots__ = (
12
+ "type",
13
+ "brand",
14
+ "last_4",
15
+ "expires_month",
16
+ "expires_year",
17
+ "default",
18
+ "invalid",
19
+ "billing_address",
20
+ )
21
+
22
+ def __init__(self, *, state, data: dict):
23
+ super().__init__(state=state, data=data)
24
+ self._update(data)
25
+
26
+ def _update(self, data: dict):
27
+ self.type = data.get("type")
28
+ self.brand = data.get("brand")
29
+ self.last_4 = data.get("last_4")
30
+ self.expires_month = data.get("expires_month")
31
+ self.expires_year = data.get("expires_year")
32
+ self.default = data.get("default", False)
33
+ self.invalid = data.get("invalid", False)
34
+ self.billing_address = data.get("billing_address")
35
+
36
+ @property
37
+ def is_card(self) -> bool:
38
+ return self.type == 1
39
+
40
+ @property
41
+ def is_paypal(self) -> bool:
42
+ return self.type == 2
43
+
44
+ def __repr__(self) -> str:
45
+ return f"<PaymentSource id={self.id} type={self.type} last4={self.last_4}>"
46
+
47
+ class Subscription(DiscordObject):
48
+ """Represents a subscription (Nitro, etc.)."""
49
+
50
+ __slots__ = (
51
+ "type",
52
+ "plan_id",
53
+ "status",
54
+ "current_period_start",
55
+ "current_period_end",
56
+ "renewal_interval",
57
+ "renewal_interval_count",
58
+ "entitlement_id",
59
+ )
60
+
61
+ def __init__(self, *, state, data: dict):
62
+ super().__init__(state=state, data=data)
63
+ self._update(data)
64
+
65
+ def _update(self, data: dict):
66
+ self.type = data.get("type")
67
+ self.plan_id = data.get("plan_id")
68
+ self.status = data.get("status")
69
+ self.current_period_start = data.get("current_period_start")
70
+ self.current_period_end = data.get("current_period_end")
71
+ self.renewal_interval = data.get("renewal_interval")
72
+ self.renewal_interval_count = data.get("renewal_interval_count")
73
+ self.entitlement_id = data.get("entitlement_id")
74
+
75
+ @property
76
+ def is_nitro(self) -> bool:
77
+ return self.type == 1
78
+
79
+ @property
80
+ def is_nitro_classic(self) -> bool:
81
+ return self.type == 2
82
+
83
+ @property
84
+ def is_active(self) -> bool:
85
+ return self.status == 1
86
+
87
+ def __repr__(self) -> str:
88
+ return f"<Subscription id={self.id} type={self.type} status={self.status}>"
@@ -0,0 +1,222 @@
1
+ """
2
+ Discord Message model.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING, Optional, List, Dict, Any, Union
6
+ from datetime import datetime
7
+
8
+ from .base import DiscordObject
9
+ from .user import User
10
+ from .member import Member
11
+ from ..core.enums import MessageType
12
+ from ..utils import parse_time
13
+ from ..components import Component, ActionRow, ComponentType
14
+
15
+ if TYPE_CHECKING:
16
+ from ..state import ConnectionState
17
+
18
+
19
+ class Message(DiscordObject):
20
+ """Represents a Discord message."""
21
+
22
+ __slots__ = (
23
+ "channel_id",
24
+ "author",
25
+ "content",
26
+ "timestamp",
27
+ "edited_timestamp",
28
+ "tts",
29
+ "mention_everyone",
30
+ "mentions",
31
+ "mention_roles",
32
+ "attachments",
33
+ "embeds",
34
+ "reactions",
35
+ "pinned",
36
+ "type",
37
+ "guild_id",
38
+ "member",
39
+ "referenced_message",
40
+ "flags",
41
+ "thread",
42
+ "application",
43
+ "message_reference",
44
+ "interaction",
45
+ "components",
46
+ "sticker_items",
47
+ "position",
48
+ "role_subscription_data",
49
+ "resolved",
50
+ "poll",
51
+ "call",
52
+ "author",
53
+ )
54
+
55
+ def __init__(self, *, state: "ConnectionState", data: dict):
56
+ super().__init__(state=state, data=data)
57
+ self._update(data)
58
+
59
+ def _update(self, data: dict):
60
+ self.id = int(data["id"])
61
+ self.channel_id = int(data["channel_id"])
62
+ self.content = data.get("content", "")
63
+ self.timestamp = parse_time(data["timestamp"])
64
+ self.edited_timestamp = parse_time(data["edited_timestamp"]) if data.get("edited_timestamp") else None
65
+ self.tts = data.get("tts", False)
66
+ self.mention_everyone = data.get("mention_everyone", False)
67
+ self.mentions = [User(state=self._state, data=u) for u in data.get("mentions", [])]
68
+ self.mention_roles = data.get("mention_roles", [])
69
+ self.attachments = data.get("attachments", [])
70
+ self.embeds = data.get("embeds", [])
71
+ self.reactions = data.get("reactions", [])
72
+ self.pinned = data.get("pinned", False)
73
+ self.type = MessageType(data.get("type", 0))
74
+ self.guild_id = int(data["guild_id"]) if data.get("guild_id") else None
75
+ self.flags = data.get("flags", 0)
76
+ self.sticker_items = data.get("sticker_items", [])
77
+ self.position = data.get("position", 0)
78
+ self.role_subscription_data = data.get("role_subscription_data")
79
+ self.resolved = data.get("resolved")
80
+ self.poll = data.get("poll")
81
+ self.call = data.get("call")
82
+
83
+ # Author
84
+ self.author = User(state=self._state, data=data["author"])
85
+
86
+ # Member (if in guild)
87
+ self.member = None
88
+ if data.get("member"):
89
+ self.member = Member(state=self._state, data=data["member"], guild_id=self.guild_id)
90
+
91
+ # Referenced message
92
+ self.referenced_message = None
93
+ if data.get("referenced_message"):
94
+ self.referenced_message = Message(state=self._state, data=data["referenced_message"])
95
+
96
+ # Thread
97
+ self.thread = data.get("thread")
98
+
99
+ # Application
100
+ self.application = data.get("application")
101
+
102
+ # Message reference
103
+ self.message_reference = data.get("message_reference")
104
+
105
+ # Interaction
106
+ self.interaction = data.get("interaction")
107
+
108
+ # Components
109
+ self._components = []
110
+ for comp_data in data.get("components", []):
111
+ if comp_data.get("type") == ComponentType.ACTION_ROW:
112
+ row = ActionRow()
113
+ for child_data in comp_data.get("components", []):
114
+ row.components.append(Component(child_data))
115
+ self._components.append(row)
116
+
117
+ @property
118
+ def jump_url(self) -> str:
119
+ """The jump URL for this message."""
120
+ return f"https://discord.com/channels/{self.guild_id or '@me'}/{self.channel_id}/{self.id}"
121
+
122
+ @property
123
+ def clean_content(self) -> str:
124
+ """The content of the message with mentions cleaned."""
125
+ content = self.content
126
+ for user in self.mentions:
127
+ content = content.replace(f"<@{user.id}>", f"@{user.name}")
128
+ content = content.replace(f"<@!{user.id}>", f"@{user.name}")
129
+ for role_id in self.mention_roles:
130
+ content = content.replace(f"<@&{role_id}>", f"@<role>")
131
+ if self.mention_everyone:
132
+ content = content.replace("@everyone", "@everyone")
133
+ content = content.replace("@here", "@here")
134
+ return content
135
+
136
+ @property
137
+ def components(self) -> List[ActionRow]:
138
+ """The components in this message."""
139
+ return self._components
140
+
141
+ async def edit(
142
+ self,
143
+ content: Optional[str] = None,
144
+ embeds: Optional[List[Dict[str, Any]]] = None,
145
+ components: Optional[List[ActionRow]] = None,
146
+ **kwargs,
147
+ ) -> "Message":
148
+ """Edit this message."""
149
+ payload = {}
150
+ if content is not None:
151
+ payload["content"] = content
152
+ if embeds is not None:
153
+ payload["embeds"] = embeds
154
+ if components is not None:
155
+ payload["components"] = [c.to_dict() for c in components]
156
+ payload.update(kwargs)
157
+
158
+ data = await self._state.http.edit_message(
159
+ self.channel_id,
160
+ self.id,
161
+ **payload,
162
+ )
163
+ self._update(data)
164
+ return self
165
+
166
+ async def delete(self) -> None:
167
+ """Delete this message."""
168
+ await self._state.http.delete_message(self.channel_id, self.id)
169
+
170
+ async def reply(
171
+ self,
172
+ content: Optional[str] = None,
173
+ embeds: Optional[List[Dict[str, Any]]] = None,
174
+ components: Optional[List[ActionRow]] = None,
175
+ **kwargs,
176
+ ) -> "Message":
177
+ """Reply to this message."""
178
+ payload = {
179
+ "message_reference": {"message_id": self.id},
180
+ }
181
+ if content is not None:
182
+ payload["content"] = content
183
+ if embeds is not None:
184
+ payload["embeds"] = embeds
185
+ if components is not None:
186
+ payload["components"] = [c.to_dict() for c in components]
187
+ payload.update(kwargs)
188
+
189
+ data = await self._state.http.send_message(
190
+ self.channel_id,
191
+ **payload,
192
+ )
193
+ return self._state._store_message(data)
194
+
195
+ async def add_reaction(self, emoji: str) -> None:
196
+ """Add a reaction to this message."""
197
+ await self._state.http.add_reaction(self.channel_id, self.id, emoji)
198
+
199
+ async def remove_reaction(self, emoji: str) -> None:
200
+ """Remove a reaction from this message."""
201
+ await self._state.http.remove_reaction(self.channel_id, self.id, emoji)
202
+
203
+ async def clear_reactions(self) -> None:
204
+ """Clear all reactions from this message."""
205
+ await self._state.http.clear_reactions(self.channel_id, self.id)
206
+
207
+ async def pin(self) -> None:
208
+ """Pin this message."""
209
+ await self._state.http.pin_message(self.channel_id, self.id)
210
+
211
+ async def unpin(self) -> None:
212
+ """Unpin this message."""
213
+ await self._state.http.unpin_message(self.channel_id, self.id)
214
+
215
+ async def get_reaction_users(self, emoji: str, limit: int = 100) -> List[User]:
216
+ """Get users who reacted with a specific emoji."""
217
+ # This would need to be implemented in the HTTP client
218
+ # For now, just return an empty list
219
+ return []
220
+
221
+ def __repr__(self) -> str:
222
+ return f"<Message id={self.id} content={self.content[:50]!r}>"
@@ -0,0 +1,47 @@
1
+ """
2
+ Relationship models (friends, blocks, etc.)
3
+ """
4
+
5
+ from typing import Optional, Dict, Any
6
+ from .base import DiscordObject
7
+
8
+ class Relationship(DiscordObject):
9
+ """Represents a relationship (friend, block, etc.)."""
10
+
11
+ __slots__ = (
12
+ "user_id",
13
+ "type",
14
+ "nickname",
15
+ "since",
16
+ "user",
17
+ )
18
+
19
+ def __init__(self, *, state, data: dict):
20
+ super().__init__(state=state, data=data)
21
+ self._update(data)
22
+
23
+ def _update(self, data: dict):
24
+ self.user_id = int(data.get("user_id", 0))
25
+ self.type = data.get("type", 0) # 1=friends, 2=blocked, 3=incoming, 4=outgoing
26
+ self.nickname = data.get("nickname")
27
+ self.since = data.get("since")
28
+ self.user = data.get("user")
29
+
30
+ @property
31
+ def is_friend(self) -> bool:
32
+ return self.type == 1
33
+
34
+ @property
35
+ def is_blocked(self) -> bool:
36
+ return self.type == 2
37
+
38
+ @property
39
+ def is_incoming_request(self) -> bool:
40
+ return self.type == 3
41
+
42
+ @property
43
+ def is_outgoing_request(self) -> bool:
44
+ return self.type == 4
45
+
46
+ def __repr__(self) -> str:
47
+ return f"<Relationship id={self.id} type={self.type}>"
@@ -0,0 +1,54 @@
1
+ """
2
+ User settings models.
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+ from .base import DiscordObject
7
+
8
+ class GuildSettings(DiscordObject):
9
+ """Settings for a specific guild."""
10
+
11
+ __slots__ = (
12
+ "guild_id",
13
+ "channel_overwrites",
14
+ "muted",
15
+ "mute_config",
16
+ "mobile_push",
17
+ "message_notifications",
18
+ "flags",
19
+ "notify_highlights",
20
+ )
21
+
22
+ def __init__(self, *, state, data: dict):
23
+ super().__init__(state=state, data=data)
24
+ self._update(data)
25
+
26
+ def _update(self, data: dict):
27
+ self.guild_id = int(data.get("guild_id", 0))
28
+ self.channel_overwrites = data.get("channel_overwrites", {})
29
+ self.muted = data.get("muted", False)
30
+ self.mute_config = data.get("mute_config", {})
31
+ self.mobile_push = data.get("mobile_push", False)
32
+ self.message_notifications = data.get("message_notifications", 0)
33
+ self.flags = data.get("flags", 0)
34
+ self.notify_highlights = data.get("notify_highlights", {})
35
+
36
+ def __repr__(self) -> str:
37
+ return f"<GuildSettings guild_id={self.guild_id}>"
38
+
39
+ class UserSettings:
40
+ """Container for user settings (not a model)."""
41
+
42
+ def __init__(self, data: dict):
43
+ self.theme = data.get("theme", "dark")
44
+ self.locale = data.get("locale", "en-US")
45
+ self.status = data.get("status", "online")
46
+ self.developer_mode = data.get("developer_mode", False)
47
+ self.afk_timeout = data.get("afk_timeout", 600)
48
+ self.animate_emoji = data.get("animate_emoji", True)
49
+ self.animate_stickers = data.get("animate_stickers", True)
50
+ self.enable_tts = data.get("enable_tts", False)
51
+ self.guilds = data.get("guild_settings", {})
52
+
53
+ def __repr__(self) -> str:
54
+ return f"<UserSettings theme={self.theme} status={self.status}>"
@@ -0,0 +1,168 @@
1
+ """
2
+ Discord Webhook models.
3
+ """
4
+
5
+ from typing import TYPE_CHECKING, Optional, List, Dict, Any, Union
6
+ from .base import DiscordObject
7
+ from .user import User
8
+ from .channel import Channel
9
+ from ..utils import parse_time
10
+
11
+ if TYPE_CHECKING:
12
+ from ..state import ConnectionState
13
+
14
+
15
+ class Webhook(DiscordObject):
16
+ """Represents a Discord webhook."""
17
+
18
+ __slots__ = (
19
+ "type",
20
+ "name",
21
+ "avatar",
22
+ "channel_id",
23
+ "guild_id",
24
+ "user",
25
+ "token",
26
+ "source_guild",
27
+ "source_channel",
28
+ "url",
29
+ )
30
+
31
+ def __init__(self, *, state: "ConnectionState", data: dict):
32
+ super().__init__(state=state, data=data)
33
+ self._update(data)
34
+
35
+ def _update(self, data: dict):
36
+ self.id = int(data["id"])
37
+ self.type = data.get("type", 1) # 1=incoming, 2=channel follower
38
+ self.name = data.get("name")
39
+ self.avatar = data.get("avatar")
40
+ self.channel_id = int(data["channel_id"])
41
+ self.guild_id = int(data["guild_id"]) if data.get("guild_id") else None
42
+ self.token = data.get("token")
43
+ self.source_guild = data.get("source_guild")
44
+ self.source_channel = data.get("source_channel")
45
+
46
+ self.user = None
47
+ if data.get("user"):
48
+ self.user = User(state=self._state, data=data["user"])
49
+
50
+ self.url = data.get("url")
51
+
52
+ @property
53
+ def avatar_url(self) -> Optional[str]:
54
+ """The URL of the webhook's avatar."""
55
+ if self.avatar:
56
+ return f"https://cdn.discordapp.com/avatars/{self.id}/{self.avatar}.png"
57
+ return None
58
+
59
+ @property
60
+ def display_name(self) -> str:
61
+ """The display name of the webhook."""
62
+ return self.name or "Unknown Webhook"
63
+
64
+ async def send(
65
+ self,
66
+ content: Optional[str] = None,
67
+ embeds: Optional[List[Dict[str, Any]]] = None,
68
+ username: Optional[str] = None,
69
+ avatar_url: Optional[str] = None,
70
+ tts: bool = False,
71
+ components: Optional[List] = None,
72
+ **kwargs,
73
+ ) -> Dict[str, Any]:
74
+ """Send a message with this webhook."""
75
+ payload = {}
76
+ if content is not None:
77
+ payload["content"] = content
78
+ if embeds is not None:
79
+ payload["embeds"] = embeds
80
+ if username is not None:
81
+ payload["username"] = username
82
+ if avatar_url is not None:
83
+ payload["avatar_url"] = avatar_url
84
+ if tts:
85
+ payload["tts"] = tts
86
+ if components is not None:
87
+ payload["components"] = [c.to_dict() for c in components]
88
+ payload.update(kwargs)
89
+
90
+ url = f"/webhooks/{self.id}/{self.token}"
91
+ return await self._state.http.request(
92
+ method="POST",
93
+ url=url,
94
+ json=payload,
95
+ )
96
+
97
+ async def edit(
98
+ self,
99
+ name: Optional[str] = None,
100
+ avatar: Optional[str] = None,
101
+ channel_id: Optional[int] = None,
102
+ **kwargs,
103
+ ) -> "Webhook":
104
+ """Edit this webhook."""
105
+ payload = {}
106
+ if name is not None:
107
+ payload["name"] = name
108
+ if avatar is not None:
109
+ payload["avatar"] = avatar
110
+ if channel_id is not None:
111
+ payload["channel_id"] = str(channel_id)
112
+ payload.update(kwargs)
113
+
114
+ data = await self._state.http.request(
115
+ method="PATCH",
116
+ url=f"/webhooks/{self.id}",
117
+ json=payload,
118
+ )
119
+ self._update(data)
120
+ return self
121
+
122
+ async def delete(self) -> None:
123
+ """Delete this webhook."""
124
+ await self._state.http.request(
125
+ method="DELETE",
126
+ url=f"/webhooks/{self.id}",
127
+ )
128
+
129
+ def __repr__(self) -> str:
130
+ return f"<Webhook id={self.id} name={self.name!r}>"
131
+
132
+
133
+ class WebhookMessage:
134
+ """A message sent via webhook."""
135
+
136
+ def __init__(self, data: dict):
137
+ self.id = int(data["id"])
138
+ self.channel_id = int(data["channel_id"])
139
+ self.content = data.get("content", "")
140
+ self.timestamp = parse_time(data["timestamp"])
141
+ self.edited_timestamp = parse_time(data["edited_timestamp"]) if data.get("edited_timestamp") else None
142
+ self.embeds = data.get("embeds", [])
143
+ self.attachments = data.get("attachments", [])
144
+ self.components = data.get("components", [])
145
+ self.flags = data.get("flags", 0)
146
+ self.webhook_id = int(data.get("webhook_id", 0))
147
+
148
+ async def edit(
149
+ self,
150
+ content: Optional[str] = None,
151
+ embeds: Optional[List[Dict[str, Any]]] = None,
152
+ components: Optional[List] = None,
153
+ **kwargs,
154
+ ) -> "WebhookMessage":
155
+ """Edit this webhook message."""
156
+ payload = {}
157
+ if content is not None:
158
+ payload["content"] = content
159
+ if embeds is not None:
160
+ payload["embeds"] = embeds
161
+ if components is not None:
162
+ payload["components"] = [c.to_dict() for c in components]
163
+ payload.update(kwargs)
164
+ return self
165
+
166
+ async def delete(self) -> None:
167
+ """Delete this webhook message."""
168
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modifyself
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A clean, pythonic Discord self-bot library
5
5
  Author-email: modifyself <modifyself@example.com>
6
6
  License-Expression: MIT
@@ -16,6 +16,8 @@ Requires-Dist: aiohttp>=3.8.0
16
16
  Requires-Dist: plyer>=0.9
17
17
  Requires-Dist: pillow>=10.0
18
18
  Requires-Dist: requests>=2.28
19
+ Requires-Dist: PyNaCl>=1.5.0
20
+ Requires-Dist: opuslib>=3.0.0
19
21
  Dynamic: license-file
20
22
 
21
23
  # modifyself
@@ -4,3 +4,5 @@ aiohttp>=3.8.0
4
4
  plyer>=0.9
5
5
  pillow>=10.0
6
6
  requests>=2.28
7
+ PyNaCl>=1.5.0
8
+ opuslib>=3.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modifyself"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "A clean, pythonic Discord self-bot library"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -19,6 +19,8 @@ dependencies = [
19
19
  "plyer>=0.9",
20
20
  "pillow>=10.0",
21
21
  "requests>=2.28",
22
+ "PyNaCl>=1.5.0",
23
+ "opuslib>=3.0.0",
22
24
  ]
23
25
 
24
26
  [project.urls]
@@ -27,4 +29,6 @@ Bug-Reports = "https://github.com/clashings/modifyself/issues"
27
29
  Source = "https://github.com/clashings/modifyself"
28
30
 
29
31
  [tool.setuptools.packages.find]
32
+ where = ["."]
30
33
  include = ["modifyself*"]
34
+ exclude = ["tests*", "examples*", "docs*"]
File without changes
File without changes
@@ -1,180 +0,0 @@
1
- """
2
- Discord Message model.
3
- """
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- from .base import DiscordObject
8
- from .user import User
9
- from ..core.snowflake import Snowflake
10
- from ..utils import parse_time
11
-
12
- if TYPE_CHECKING:
13
- from ..state import ConnectionState
14
- from .member import Member
15
- from .channel import Channel
16
- from .guild import Guild
17
-
18
-
19
- class Message(DiscordObject):
20
- """Represents a message in Discord."""
21
-
22
- __slots__ = (
23
- "channel_id",
24
- "author",
25
- "content",
26
- "timestamp",
27
- "edited_at",
28
- "tts",
29
- "mention_everyone",
30
- "mentions",
31
- "mention_roles",
32
- "attachments",
33
- "embeds",
34
- "reactions",
35
- "pinned",
36
- "type",
37
- "guild_id",
38
- "member",
39
- "referenced_message",
40
- "_cs_channel",
41
- "_cs_guild",
42
- )
43
-
44
- def __init__(self, *, state: "ConnectionState", data: dict):
45
- super().__init__(state=state, data=data)
46
- self._cs_channel = None
47
- self._cs_guild = None
48
- self._update(data)
49
-
50
- def _update(self, data: dict):
51
- self.channel_id = Snowflake(data.get("channel_id", 0))
52
- author_data = data.get("author", {})
53
- self.author = User(state=self._state, data=author_data)
54
- self.content = data.get("content", "")
55
- self.timestamp = parse_time(data["timestamp"]) if "timestamp" in data else None
56
- self.edited_at = (
57
- parse_time(data["edited_timestamp"]) if data.get("edited_timestamp") else None
58
- )
59
- self.tts = data.get("tts", False)
60
- self.mention_everyone = data.get("mention_everyone", False)
61
- self.mentions = [
62
- User(state=self._state, data=u) for u in data.get("mentions", [])
63
- ]
64
- self.mention_roles = [int(r) for r in data.get("mention_roles", [])]
65
- self.attachments = data.get("attachments", [])
66
- self.embeds = data.get("embeds", [])
67
- self.reactions = data.get("reactions", [])
68
- self.pinned = data.get("pinned", False)
69
- self.type = data.get("type", 0)
70
- self.guild_id = (
71
- Snowflake(data["guild_id"]) if data.get("guild_id") else None
72
- )
73
-
74
- # Member info if present
75
- member_data = data.get("member")
76
- if member_data and self.guild_id:
77
- from .member import Member
78
- self.member = Member(
79
- state=self._state, data={**member_data, "user": author_data},
80
- guild_id=self.guild_id
81
- )
82
- else:
83
- self.member = None
84
-
85
- # Referenced message
86
- ref_data = data.get("referenced_message")
87
- if ref_data:
88
- self.referenced_message = Message(state=self._state, data=ref_data)
89
- else:
90
- self.referenced_message = None
91
-
92
- def __repr__(self) -> str:
93
- return f"<Message id={self.id} author={self.author!r} channel={self.channel_id}>"
94
-
95
- def __str__(self) -> str:
96
- return self.content
97
-
98
- @property
99
- def channel(self) -> "Channel | None":
100
- if self._cs_channel is None:
101
- self._cs_channel = self._state._channels.get(self.channel_id)
102
- return self._cs_channel
103
-
104
- @property
105
- def guild(self) -> "Guild | None":
106
- if self.guild_id is None:
107
- return None
108
- if self._cs_guild is None:
109
- self._cs_guild = self._state._guilds.get(self.guild_id)
110
- return self._cs_guild
111
-
112
- @property
113
- def jump_url(self) -> str:
114
- guild_id = self.guild_id or "@me"
115
- return f"https://discord.com/channels/{guild_id}/{self.channel_id}/{self.id}"
116
-
117
- @property
118
- def created_at(self):
119
- return self.id.created_at
120
-
121
- @property
122
- def edited_timestamp(self):
123
- return self.edited_at
124
-
125
- @property
126
- def clean_content(self) -> str:
127
- """The message content with mentions replaced with names."""
128
- content = self.content
129
- for user in self.mentions:
130
- content = content.replace(f"<@{user.id}>", f"@{user.display_name}")
131
- content = content.replace(f"<@!{user.id}>", f"@{user.display_name}")
132
- return content
133
-
134
- async def delete(self, *, delay: float | None = None):
135
- """Delete this message."""
136
- if delay:
137
- import asyncio
138
- await asyncio.sleep(delay)
139
- await self._state.http.delete_message(self.channel_id, self.id)
140
-
141
- async def edit(self, content: str | None = None, **kwargs):
142
- """Edit this message."""
143
- data = await self._state.http.edit_message(
144
- self.channel_id, self.id, content, **kwargs
145
- )
146
- self._update(data)
147
- return self
148
-
149
- async def reply(self, content: str | None = None, **kwargs):
150
- """Reply to this message."""
151
- ref = {
152
- "type": 0,
153
- "message_id": str(self.id),
154
- "channel_id": str(self.channel_id),
155
- }
156
- if self.guild_id:
157
- ref["guild_id"] = str(self.guild_id)
158
- kwargs["message_reference"] = ref
159
- data = await self._state.http.send_message(self.channel_id, content, **kwargs)
160
- return self._state._store_message(data)
161
-
162
- async def add_reaction(self, emoji: str):
163
- """Add a reaction to this message."""
164
- await self._state.http.add_reaction(self.channel_id, self.id, emoji)
165
-
166
- async def remove_reaction(self, emoji: str):
167
- """Remove a reaction from this message."""
168
- await self._state.http.remove_reaction(self.channel_id, self.id, emoji)
169
-
170
- async def clear_reactions(self):
171
- """Clear all reactions from this message."""
172
- await self._state.http.clear_reactions(self.channel_id, self.id)
173
-
174
- async def pin(self):
175
- """Pin this message."""
176
- await self._state.http.pin_message(self.channel_id, self.id)
177
-
178
- async def unpin(self):
179
- """Unpin this message."""
180
- await self._state.http.unpin_message(self.channel_id, self.id)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes