cordscript 1.0.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.
- cordscript-1.0.0/PKG-INFO +17 -0
- cordscript-1.0.0/cordscript/__init__.py +6 -0
- cordscript-1.0.0/cordscript/client.py +590 -0
- cordscript-1.0.0/cordscript/core/__init__.py +9 -0
- cordscript-1.0.0/cordscript/core/context.py +33 -0
- cordscript-1.0.0/cordscript/core/runtime.py +55 -0
- cordscript-1.0.0/cordscript/core/variables.py +16 -0
- cordscript-1.0.0/cordscript/exceptions/__init__.py +3 -0
- cordscript-1.0.0/cordscript/exceptions/errors.py +9 -0
- cordscript-1.0.0/cordscript/functions/__init__.py +45 -0
- cordscript-1.0.0/cordscript/functions/database/__init__.py +4 -0
- cordscript-1.0.0/cordscript/functions/database/database.py +846 -0
- cordscript-1.0.0/cordscript/functions/database/store.py +69 -0
- cordscript-1.0.0/cordscript/functions/discord/__init__.py +15 -0
- cordscript-1.0.0/cordscript/functions/discord/components.py +134 -0
- cordscript-1.0.0/cordscript/functions/discord/componentsv2.py +92 -0
- cordscript-1.0.0/cordscript/functions/discord/embed.py +63 -0
- cordscript-1.0.0/cordscript/functions/discord/interaction.py +48 -0
- cordscript-1.0.0/cordscript/functions/discord/logic.py +87 -0
- cordscript-1.0.0/cordscript/functions/discord/message.py +51 -0
- cordscript-1.0.0/cordscript/functions/discord/moderation.py +179 -0
- cordscript-1.0.0/cordscript/functions/discord/status.py +56 -0
- cordscript-1.0.0/cordscript/functions/users/__init__.py +3 -0
- cordscript-1.0.0/cordscript/functions/users/user.py +103 -0
- cordscript-1.0.0/cordscript/functions/utility/__init__.py +14 -0
- cordscript-1.0.0/cordscript/functions/utility/cooldown.py +35 -0
- cordscript-1.0.0/cordscript/functions/utility/http.py +73 -0
- cordscript-1.0.0/cordscript/functions/utility/json.py +201 -0
- cordscript-1.0.0/cordscript/functions/utility/math.py +102 -0
- cordscript-1.0.0/cordscript/functions/utility/random.py +27 -0
- cordscript-1.0.0/cordscript/functions/utility/strings.py +201 -0
- cordscript-1.0.0/cordscript/functions/utility/time.py +41 -0
- cordscript-1.0.0/cordscript/parser.py +190 -0
- cordscript-1.0.0/cordscript/setup.py +38 -0
- cordscript-1.0.0/cordscript.egg-info/PKG-INFO +17 -0
- cordscript-1.0.0/cordscript.egg-info/SOURCES.txt +39 -0
- cordscript-1.0.0/cordscript.egg-info/dependency_links.txt +1 -0
- cordscript-1.0.0/cordscript.egg-info/requires.txt +3 -0
- cordscript-1.0.0/cordscript.egg-info/top_level.txt +1 -0
- cordscript-1.0.0/setup.cfg +4 -0
- cordscript-1.0.0/setup.py +21 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cordscript
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Home-page:
|
|
5
|
+
Author: Arimo
|
|
6
|
+
Author-email: alysonk078@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: discord.py
|
|
11
|
+
Requires-Dist: aiohttp
|
|
12
|
+
Requires-Dist: tinydb
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: requires-dist
|
|
17
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import importlib.util
|
|
6
|
+
import inspect
|
|
7
|
+
import aiohttp
|
|
8
|
+
import discord
|
|
9
|
+
from discord import ui
|
|
10
|
+
from .parser import Parser
|
|
11
|
+
from .functions.database import Database
|
|
12
|
+
|
|
13
|
+
logging.getLogger("discord").setLevel(logging.WARNING)
|
|
14
|
+
logging.getLogger("discord.http").setLevel(logging.WARNING)
|
|
15
|
+
logging.getLogger("discord.gateway").setLevel(logging.WARNING)
|
|
16
|
+
logging.getLogger("discord.client").setLevel(logging.WARNING)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _Color:
|
|
20
|
+
RESET = "\033[0m"
|
|
21
|
+
BOLD = "\033[1m"
|
|
22
|
+
DIM = "\033[2m"
|
|
23
|
+
ITALIC = "\033[3m"
|
|
24
|
+
UNDERLINE = "\033[4m"
|
|
25
|
+
|
|
26
|
+
BLACK = "\033[30m"
|
|
27
|
+
RED = "\033[31m"
|
|
28
|
+
GREEN = "\033[32m"
|
|
29
|
+
YELLOW = "\033[33m"
|
|
30
|
+
BLUE = "\033[34m"
|
|
31
|
+
MAGENTA = "\033[35m"
|
|
32
|
+
CYAN = "\033[36m"
|
|
33
|
+
WHITE = "\033[37m"
|
|
34
|
+
|
|
35
|
+
BRIGHT_BLACK = "\033[90m"
|
|
36
|
+
BRIGHT_RED = "\033[91m"
|
|
37
|
+
BRIGHT_GREEN = "\033[92m"
|
|
38
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
39
|
+
BRIGHT_BLUE = "\033[94m"
|
|
40
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
41
|
+
BRIGHT_CYAN = "\033[96m"
|
|
42
|
+
BRIGHT_WHITE = "\033[97m"
|
|
43
|
+
|
|
44
|
+
ORANGE = "\033[38;5;208m"
|
|
45
|
+
PURPLE = "\033[38;5;93m"
|
|
46
|
+
PINK = "\033[38;5;205m"
|
|
47
|
+
GOLD = "\033[38;5;220m"
|
|
48
|
+
LIME = "\033[38;5;154m"
|
|
49
|
+
TEAL = "\033[38;5;30m"
|
|
50
|
+
NAVY = "\033[38;5;17m"
|
|
51
|
+
BROWN = "\033[38;5;94m"
|
|
52
|
+
GRAY = "\033[38;5;245m"
|
|
53
|
+
LIGHT_GRAY = "\033[38;5;250m"
|
|
54
|
+
DARK_RED = "\033[38;5;88m"
|
|
55
|
+
DARK_GREEN = "\033[38;5;22m"
|
|
56
|
+
SKY_BLUE = "\033[38;5;117m"
|
|
57
|
+
INDIGO = "\033[38;5;54m"
|
|
58
|
+
SALMON = "\033[38;5;209m"
|
|
59
|
+
TURQUOISE = "\033[38;5;80m"
|
|
60
|
+
|
|
61
|
+
BG_BLACK = "\033[40m"
|
|
62
|
+
BG_RED = "\033[41m"
|
|
63
|
+
BG_GREEN = "\033[42m"
|
|
64
|
+
BG_YELLOW = "\033[43m"
|
|
65
|
+
BG_BLUE = "\033[44m"
|
|
66
|
+
BG_MAGENTA = "\033[45m"
|
|
67
|
+
BG_CYAN = "\033[46m"
|
|
68
|
+
BG_WHITE = "\033[47m"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _tag() -> str:
|
|
72
|
+
return f"{_Color.PURPLE}{_Color.BOLD}[cordscript]{_Color.RESET}"
|
|
73
|
+
|
|
74
|
+
_CV2_FLAG = 1 << 15
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _build_view(components: list[dict], client: "Client") -> ui.View | None:
|
|
78
|
+
view = ui.View(timeout=None)
|
|
79
|
+
added = False
|
|
80
|
+
for comp in components:
|
|
81
|
+
if comp.get("type") == 1:
|
|
82
|
+
for item in comp.get("components", []):
|
|
83
|
+
if _add_item(view, item, client):
|
|
84
|
+
added = True
|
|
85
|
+
elif comp.get("type") in (2, 3):
|
|
86
|
+
if _add_item(view, comp, client):
|
|
87
|
+
added = True
|
|
88
|
+
return view if added else None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _add_item(view: ui.View, item: dict, client: "Client") -> bool:
|
|
92
|
+
try:
|
|
93
|
+
t = item.get("type")
|
|
94
|
+
if t == 2:
|
|
95
|
+
style_map = {
|
|
96
|
+
1: discord.ButtonStyle.primary,
|
|
97
|
+
2: discord.ButtonStyle.secondary,
|
|
98
|
+
3: discord.ButtonStyle.success,
|
|
99
|
+
4: discord.ButtonStyle.danger,
|
|
100
|
+
}
|
|
101
|
+
style = style_map.get(item.get("style", 1), discord.ButtonStyle.primary)
|
|
102
|
+
emoji = None
|
|
103
|
+
if "emoji" in item:
|
|
104
|
+
e = item["emoji"]
|
|
105
|
+
if e.get("id"):
|
|
106
|
+
emoji = discord.PartialEmoji(
|
|
107
|
+
name=e.get("name", ""),
|
|
108
|
+
id=int(e["id"]),
|
|
109
|
+
animated=e.get("animated", False)
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
emoji = e.get("name")
|
|
113
|
+
btn = ui.Button(
|
|
114
|
+
label=item.get("label", ""),
|
|
115
|
+
custom_id=item.get("custom_id"),
|
|
116
|
+
style=style,
|
|
117
|
+
emoji=emoji,
|
|
118
|
+
disabled=item.get("disabled", False),
|
|
119
|
+
url=item.get("url"),
|
|
120
|
+
)
|
|
121
|
+
async def btn_cb(interaction: discord.Interaction, cid=item.get("custom_id")):
|
|
122
|
+
await client._dispatch_interaction(interaction, cid)
|
|
123
|
+
btn.callback = btn_cb
|
|
124
|
+
view.add_item(btn)
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
elif t == 3:
|
|
128
|
+
options = [
|
|
129
|
+
discord.SelectOption(
|
|
130
|
+
label=o.get("label", ""),
|
|
131
|
+
value=o.get("value", ""),
|
|
132
|
+
description=o.get("description"),
|
|
133
|
+
default=o.get("default", False),
|
|
134
|
+
emoji=o.get("emoji", {}).get("name") if "emoji" in o else None,
|
|
135
|
+
)
|
|
136
|
+
for o in item.get("options", [])
|
|
137
|
+
]
|
|
138
|
+
sel = ui.Select(
|
|
139
|
+
custom_id=item.get("custom_id"),
|
|
140
|
+
placeholder=item.get("placeholder"),
|
|
141
|
+
min_values=item.get("min_values", 1),
|
|
142
|
+
max_values=item.get("max_values", 1),
|
|
143
|
+
disabled=item.get("disabled", False),
|
|
144
|
+
options=options or [discord.SelectOption(label="placeholder", value="placeholder")],
|
|
145
|
+
)
|
|
146
|
+
async def sel_cb(interaction: discord.Interaction, cid=item.get("custom_id")):
|
|
147
|
+
await client._dispatch_interaction(interaction, cid)
|
|
148
|
+
sel.callback = sel_cb
|
|
149
|
+
view.add_item(sel)
|
|
150
|
+
return True
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"[cordscript] Error building component: {e}")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def _send_payload(
|
|
157
|
+
channel,
|
|
158
|
+
text: str,
|
|
159
|
+
embeds: list,
|
|
160
|
+
components: list,
|
|
161
|
+
cv2: list,
|
|
162
|
+
token: str,
|
|
163
|
+
delete_after=None,
|
|
164
|
+
reply_to=None,
|
|
165
|
+
client=None,
|
|
166
|
+
):
|
|
167
|
+
if cv2:
|
|
168
|
+
url = f"https://discord.com/api/v10/channels/{channel.id}/messages"
|
|
169
|
+
headers = {"Authorization": f"Bot {token}", "Content-Type": "application/json"}
|
|
170
|
+
payload: dict = {"flags": _CV2_FLAG, "components": cv2}
|
|
171
|
+
if reply_to is not None:
|
|
172
|
+
msg_id = reply_to.id if isinstance(reply_to, discord.Message) else None
|
|
173
|
+
if msg_id:
|
|
174
|
+
payload["message_reference"] = {"message_id": str(msg_id)}
|
|
175
|
+
async with aiohttp.ClientSession() as session:
|
|
176
|
+
resp = await session.post(url, headers=headers, data=json.dumps(payload))
|
|
177
|
+
if not resp.ok:
|
|
178
|
+
print(f"[cordscript cv2] Error {resp.status}: {await resp.text()}")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
kwargs: dict = {}
|
|
182
|
+
if text is not None and len(text.strip()) > 0:
|
|
183
|
+
kwargs["content"] = text
|
|
184
|
+
if embeds:
|
|
185
|
+
kwargs["embeds"] = [discord.Embed.from_dict(e) for e in embeds]
|
|
186
|
+
if components and client:
|
|
187
|
+
view = _build_view(components, client)
|
|
188
|
+
if view:
|
|
189
|
+
kwargs["view"] = view
|
|
190
|
+
|
|
191
|
+
if not kwargs:
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
if delete_after is not None:
|
|
195
|
+
kwargs["delete_after"] = delete_after
|
|
196
|
+
|
|
197
|
+
if reply_to is not None:
|
|
198
|
+
try:
|
|
199
|
+
return await reply_to.reply(**kwargs)
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
return await channel.send(**kwargs)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Client(discord.Client):
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
*,
|
|
210
|
+
prefix: str | list[str],
|
|
211
|
+
token: str,
|
|
212
|
+
insensitive: bool = False,
|
|
213
|
+
ignoreBots: bool = True,
|
|
214
|
+
intents: discord.Intents | None = None,
|
|
215
|
+
database=None,
|
|
216
|
+
database_url: str | None = None,
|
|
217
|
+
editMessage: bool = False,
|
|
218
|
+
**kwargs,
|
|
219
|
+
):
|
|
220
|
+
resolved_intents = intents or discord.Intents.all()
|
|
221
|
+
resolved_intents.message_content = True
|
|
222
|
+
resolved_intents.members = True
|
|
223
|
+
super().__init__(intents=resolved_intents, **kwargs)
|
|
224
|
+
|
|
225
|
+
self._prefixes: list[str] = [prefix] if isinstance(prefix, str) else list(prefix)
|
|
226
|
+
self._token = token
|
|
227
|
+
self._insensitive = insensitive
|
|
228
|
+
self._ignore_bots = ignoreBots
|
|
229
|
+
self._edit_message = editMessage
|
|
230
|
+
self._commands: dict[str, dict] = {}
|
|
231
|
+
self._static_commands: set[str] = set()
|
|
232
|
+
self._static_entries: dict[str, tuple] = {}
|
|
233
|
+
self._interactions: list[dict] = []
|
|
234
|
+
self._ready_command: dict | None = None
|
|
235
|
+
self._memberadd_command: dict | None = None
|
|
236
|
+
self._memberremove_command: dict | None = None
|
|
237
|
+
self._commands_path: str | None = None
|
|
238
|
+
self._main_file: str | None = None
|
|
239
|
+
self._loaded_commands: int = 0
|
|
240
|
+
|
|
241
|
+
if database is None and database_url is None:
|
|
242
|
+
database = Database()
|
|
243
|
+
|
|
244
|
+
self._database = database
|
|
245
|
+
self._database_url = database_url
|
|
246
|
+
self._client_var_name: str | None = None
|
|
247
|
+
|
|
248
|
+
async def setup_hook(self):
|
|
249
|
+
if self._database_url and not isinstance(self._database, Database):
|
|
250
|
+
from .functions.database import connect_from_url, _init_table
|
|
251
|
+
self._database = await connect_from_url(self._database_url)
|
|
252
|
+
await _init_table(self._database)
|
|
253
|
+
elif self._database is not None and not isinstance(self._database, Database):
|
|
254
|
+
from .functions.database import _init_table
|
|
255
|
+
await _init_table(self._database)
|
|
256
|
+
|
|
257
|
+
def command(self, *, name: str, code: str, aliases: list[str] | None = None):
|
|
258
|
+
key = name.lower() if self._insensitive else name
|
|
259
|
+
entry = {"name": name, "code": code}
|
|
260
|
+
self._commands[key] = entry
|
|
261
|
+
self._static_commands.add(key)
|
|
262
|
+
self._static_entries[key] = (name, code, aliases)
|
|
263
|
+
for alias in (aliases or []):
|
|
264
|
+
alias_key = alias.lower() if self._insensitive else alias
|
|
265
|
+
self._commands[alias_key] = entry
|
|
266
|
+
self._static_commands.add(alias_key)
|
|
267
|
+
|
|
268
|
+
def interaction(self, *, code: str, customId: str | None = None):
|
|
269
|
+
self._interactions.append({"customId": customId, "code": code})
|
|
270
|
+
|
|
271
|
+
def readyCommand(self, *, code: str):
|
|
272
|
+
self._ready_command = {"code": code}
|
|
273
|
+
|
|
274
|
+
def memberadd(self, *, code: str, channelId: int | str | None = None):
|
|
275
|
+
self._memberadd_command = {"code": code, "channelId": channelId}
|
|
276
|
+
|
|
277
|
+
def memberremove(self, *, code: str, channelId: int | str | None = None):
|
|
278
|
+
self._memberremove_command = {"code": code, "channelId": channelId}
|
|
279
|
+
|
|
280
|
+
def loadCommands(self, path: str):
|
|
281
|
+
self._commands_path = os.path.abspath(path)
|
|
282
|
+
self._load_from_path(self._commands_path)
|
|
283
|
+
|
|
284
|
+
def reloadCommands(self, path: str | None = None):
|
|
285
|
+
if self._commands_path:
|
|
286
|
+
for k in list(self._commands.keys()):
|
|
287
|
+
if k not in self._static_commands:
|
|
288
|
+
del self._commands[k]
|
|
289
|
+
self._load_from_path(os.path.abspath(path) if path else self._commands_path)
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
if not self._main_file:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
for k in list(self._commands.keys()):
|
|
296
|
+
if k not in self._static_commands:
|
|
297
|
+
del self._commands[k]
|
|
298
|
+
|
|
299
|
+
spec = importlib.util.spec_from_file_location("__main_reload__", self._main_file)
|
|
300
|
+
module = importlib.util.module_from_spec(spec)
|
|
301
|
+
var = self._client_var_name or "bot"
|
|
302
|
+
setattr(module, var, self)
|
|
303
|
+
module.bot = self
|
|
304
|
+
|
|
305
|
+
_original_run = self.__class__.run
|
|
306
|
+
|
|
307
|
+
def _noop_run(self_, *a, **kw):
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
self.__class__.run = _noop_run
|
|
311
|
+
try:
|
|
312
|
+
spec.loader.exec_module(module)
|
|
313
|
+
print("[cordscript] Reloaded main file")
|
|
314
|
+
except Exception as e:
|
|
315
|
+
print(f"[cordscript] Failed to reload: {e}")
|
|
316
|
+
finally:
|
|
317
|
+
self.__class__.run = _original_run
|
|
318
|
+
|
|
319
|
+
def _load_from_path(self, path: str):
|
|
320
|
+
loaded = 0
|
|
321
|
+
var = self._client_var_name or "bot"
|
|
322
|
+
for root, _, files in os.walk(path):
|
|
323
|
+
for filename in files:
|
|
324
|
+
if not filename.endswith(".py") or filename.startswith("_"):
|
|
325
|
+
continue
|
|
326
|
+
filepath = os.path.join(root, filename)
|
|
327
|
+
spec = importlib.util.spec_from_file_location(filename[:-3], filepath)
|
|
328
|
+
module = importlib.util.module_from_spec(spec)
|
|
329
|
+
setattr(module, var, self)
|
|
330
|
+
module.bot = self
|
|
331
|
+
try:
|
|
332
|
+
spec.loader.exec_module(module)
|
|
333
|
+
loaded += 1
|
|
334
|
+
except Exception as e:
|
|
335
|
+
print(f"{_tag()} {_Color.YELLOW}failed to load {filepath}: {e}{_Color.RESET}")
|
|
336
|
+
self._loaded_commands = loaded
|
|
337
|
+
|
|
338
|
+
def run(self, token: str | None = None):
|
|
339
|
+
frame = inspect.stack()[1]
|
|
340
|
+
self._main_file = os.path.abspath(frame.filename)
|
|
341
|
+
for var, val in frame[0].f_locals.items():
|
|
342
|
+
if val is self and not var.startswith("_"):
|
|
343
|
+
self._client_var_name = var
|
|
344
|
+
break
|
|
345
|
+
super().run(token or self._token)
|
|
346
|
+
|
|
347
|
+
async def on_ready(self):
|
|
348
|
+
print(
|
|
349
|
+
f"{_tag()} Logged in as "
|
|
350
|
+
f"{_Color.GREEN}{self.user}{_Color.RESET} "
|
|
351
|
+
f"(ID: {_Color.YELLOW}{self.user.id}{_Color.RESET})"
|
|
352
|
+
)
|
|
353
|
+
print(f"{_tag()} Registered commands: {_Color.GREEN}{self._loaded_commands}{_Color.RESET}")
|
|
354
|
+
|
|
355
|
+
if self._ready_command:
|
|
356
|
+
await self._execute_ready(self._ready_command["code"])
|
|
357
|
+
|
|
358
|
+
async def on_member_join(self, member: discord.Member):
|
|
359
|
+
if self._memberadd_command:
|
|
360
|
+
await self._execute_member_event(
|
|
361
|
+
member, self._memberadd_command["code"],
|
|
362
|
+
channel_id=self._memberadd_command.get("channelId"),
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
async def on_member_remove(self, member: discord.Member):
|
|
366
|
+
if self._memberremove_command:
|
|
367
|
+
await self._execute_member_event(
|
|
368
|
+
member, self._memberremove_command["code"],
|
|
369
|
+
channel_id=self._memberremove_command.get("channelId"),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
async def on_message(self, message: discord.Message):
|
|
373
|
+
if self._ignore_bots and message.author.bot:
|
|
374
|
+
return
|
|
375
|
+
await self._handle_message(message)
|
|
376
|
+
|
|
377
|
+
async def on_message_edit(self, before: discord.Message, after: discord.Message):
|
|
378
|
+
if not self._edit_message:
|
|
379
|
+
return
|
|
380
|
+
if self._ignore_bots and after.author.bot:
|
|
381
|
+
return
|
|
382
|
+
if before.content == after.content:
|
|
383
|
+
return
|
|
384
|
+
await self._handle_message(after)
|
|
385
|
+
|
|
386
|
+
async def _handle_message(self, message: discord.Message):
|
|
387
|
+
content = message.content
|
|
388
|
+
matched_prefix: str | None = None
|
|
389
|
+
for prefix in self._prefixes:
|
|
390
|
+
cc = content.lower() if self._insensitive else content
|
|
391
|
+
cp = prefix.lower() if self._insensitive else prefix
|
|
392
|
+
if cc.startswith(cp):
|
|
393
|
+
matched_prefix = prefix
|
|
394
|
+
break
|
|
395
|
+
if matched_prefix is None:
|
|
396
|
+
return
|
|
397
|
+
raw = content[len(matched_prefix):]
|
|
398
|
+
parts = raw.split(None, 1)
|
|
399
|
+
if not parts:
|
|
400
|
+
return
|
|
401
|
+
cmd_name = parts[0].lower() if self._insensitive else parts[0]
|
|
402
|
+
command = self._commands.get(cmd_name)
|
|
403
|
+
if not command:
|
|
404
|
+
return
|
|
405
|
+
await self._execute(message, command["code"])
|
|
406
|
+
|
|
407
|
+
async def _dispatch_interaction(self, interaction: discord.Interaction, custom_id: str | None):
|
|
408
|
+
for entry in self._interactions:
|
|
409
|
+
if entry["customId"] is None or entry["customId"] == custom_id:
|
|
410
|
+
await self._execute_interaction(interaction, entry["code"])
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
async def _execute(self, ctx: discord.Message, code: str):
|
|
414
|
+
parser = Parser(ctx, self)
|
|
415
|
+
text, embeds, extra_messages = await parser.evaluate(code)
|
|
416
|
+
|
|
417
|
+
reply_content = parser._reply_content
|
|
418
|
+
has_reply = parser._reply
|
|
419
|
+
has_content = (text is not None and len(text.strip()) > 0) or (has_reply and reply_content and len(reply_content.strip()) > 0)
|
|
420
|
+
has_embeds = len(embeds) > 0
|
|
421
|
+
has_cv2 = len(parser._cv2_pending) > 0
|
|
422
|
+
has_components = len(parser._components) > 0
|
|
423
|
+
|
|
424
|
+
send_text = reply_content if (has_reply and reply_content) else text
|
|
425
|
+
reply_to = ctx if has_reply else None
|
|
426
|
+
|
|
427
|
+
if has_cv2:
|
|
428
|
+
# ComponentsV2 messages cannot contain content or embeds, only components.
|
|
429
|
+
await _send_payload(
|
|
430
|
+
ctx.channel, None, [], [],
|
|
431
|
+
parser._cv2_pending, self._token,
|
|
432
|
+
delete_after=parser._delete_after,
|
|
433
|
+
reply_to=reply_to, client=self
|
|
434
|
+
)
|
|
435
|
+
elif has_content or has_embeds or has_components:
|
|
436
|
+
await _send_payload(
|
|
437
|
+
ctx.channel, send_text, embeds, parser._components,
|
|
438
|
+
[], self._token,
|
|
439
|
+
delete_after=parser._delete_after,
|
|
440
|
+
reply_to=reply_to, client=self
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
for msg in extra_messages:
|
|
444
|
+
await self._send_extra(msg)
|
|
445
|
+
|
|
446
|
+
async def _send_extra(self, msg: dict):
|
|
447
|
+
channel_id = msg["channel_id"]
|
|
448
|
+
channel = self.get_channel(channel_id)
|
|
449
|
+
if not channel:
|
|
450
|
+
try:
|
|
451
|
+
channel = await self.fetch_channel(channel_id)
|
|
452
|
+
except Exception as e:
|
|
453
|
+
print(f"[cordscript] _send_extra fetch_channel failed: {e}")
|
|
454
|
+
return
|
|
455
|
+
|
|
456
|
+
sent = await _send_payload(
|
|
457
|
+
channel,
|
|
458
|
+
msg.get("content", ""),
|
|
459
|
+
msg.get("embeds", []),
|
|
460
|
+
msg.get("components", []),
|
|
461
|
+
msg.get("cv2", []),
|
|
462
|
+
self._token,
|
|
463
|
+
delete_after=msg.get("delete_after"),
|
|
464
|
+
client=self,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if msg.get("return_id") and sent is not None:
|
|
468
|
+
parser = msg.get("_parser")
|
|
469
|
+
var_name = msg.get("return_var")
|
|
470
|
+
if parser and var_name:
|
|
471
|
+
parser._vars[var_name] = str(sent.id)
|
|
472
|
+
|
|
473
|
+
async def _execute_interaction(self, interaction: discord.Interaction, code: str):
|
|
474
|
+
parser = Parser(interaction, self)
|
|
475
|
+
text, embeds, extra_messages = await parser.evaluate(code)
|
|
476
|
+
|
|
477
|
+
reply_content = parser._reply_content
|
|
478
|
+
has_reply = parser._reply
|
|
479
|
+
has_content = (text is not None and len(text.strip()) > 0) or (has_reply and reply_content and len(reply_content.strip()) > 0)
|
|
480
|
+
has_embeds = len(embeds) > 0
|
|
481
|
+
has_cv2 = len(parser._cv2_pending) > 0
|
|
482
|
+
has_components = len(parser._components) > 0
|
|
483
|
+
|
|
484
|
+
send_text = reply_content if (has_reply and reply_content) else text
|
|
485
|
+
|
|
486
|
+
if has_cv2:
|
|
487
|
+
# ComponentsV2 responses cannot contain content or embeds, only components.
|
|
488
|
+
if not interaction.response.is_done():
|
|
489
|
+
await interaction.response.defer()
|
|
490
|
+
url = f"https://discord.com/api/v10/webhooks/{self.application_id}/{interaction.token}/messages/@original"
|
|
491
|
+
headers = {"Content-Type": "application/json"}
|
|
492
|
+
payload: dict = {"flags": _CV2_FLAG, "components": parser._cv2_pending}
|
|
493
|
+
async with aiohttp.ClientSession() as session:
|
|
494
|
+
resp = await session.patch(url, headers=headers, data=json.dumps(payload))
|
|
495
|
+
if not resp.ok:
|
|
496
|
+
print(f"[cordscript cv2] Error {resp.status}: {await resp.text()}")
|
|
497
|
+
else:
|
|
498
|
+
kwargs: dict = {}
|
|
499
|
+
if send_text is not None and len(send_text.strip()) > 0:
|
|
500
|
+
kwargs["content"] = send_text
|
|
501
|
+
if has_embeds:
|
|
502
|
+
kwargs["embeds"] = [discord.Embed.from_dict(e) for e in embeds]
|
|
503
|
+
if has_components:
|
|
504
|
+
view = _build_view(parser._components, self)
|
|
505
|
+
if view:
|
|
506
|
+
kwargs["view"] = view
|
|
507
|
+
|
|
508
|
+
if kwargs:
|
|
509
|
+
if not interaction.response.is_done():
|
|
510
|
+
await interaction.response.send_message(**kwargs, ephemeral=parser._ephemeral)
|
|
511
|
+
else:
|
|
512
|
+
await interaction.edit_original_response(**kwargs)
|
|
513
|
+
|
|
514
|
+
for msg in extra_messages:
|
|
515
|
+
await self._send_extra(msg)
|
|
516
|
+
|
|
517
|
+
async def _execute_member_event(self, member: discord.Member, code: str, channel_id: int | str | None = None):
|
|
518
|
+
channel = None
|
|
519
|
+
if channel_id:
|
|
520
|
+
try:
|
|
521
|
+
channel = self.get_channel(int(channel_id)) or await self.fetch_channel(int(channel_id))
|
|
522
|
+
except Exception as e:
|
|
523
|
+
print(f"[cordscript] member event: could not resolve channelId {channel_id}: {e}")
|
|
524
|
+
if channel is None:
|
|
525
|
+
channel = getattr(member.guild, "system_channel", None)
|
|
526
|
+
|
|
527
|
+
class MemberCtx:
|
|
528
|
+
def __init__(self, m: discord.Member, ch):
|
|
529
|
+
self.author = m
|
|
530
|
+
self.guild = m.guild
|
|
531
|
+
self.channel = ch
|
|
532
|
+
self.mentions = []
|
|
533
|
+
self.id = 0
|
|
534
|
+
self.content = ""
|
|
535
|
+
|
|
536
|
+
parser = Parser(MemberCtx(member, channel), self)
|
|
537
|
+
text, embeds, extra_messages = await parser.evaluate(code)
|
|
538
|
+
|
|
539
|
+
has_content = text is not None and len(text.strip()) > 0
|
|
540
|
+
has_embeds = len(embeds) > 0
|
|
541
|
+
has_cv2 = len(parser._cv2_pending) > 0
|
|
542
|
+
has_components = len(parser._components) > 0
|
|
543
|
+
|
|
544
|
+
if channel is None:
|
|
545
|
+
if has_content or has_embeds or has_cv2 or has_components:
|
|
546
|
+
print(f"[cordscript memberadd] No channel resolved, message not sent: {text}")
|
|
547
|
+
elif has_cv2:
|
|
548
|
+
await _send_payload(
|
|
549
|
+
channel, None, [], [],
|
|
550
|
+
parser._cv2_pending, self._token,
|
|
551
|
+
client=self,
|
|
552
|
+
)
|
|
553
|
+
elif has_content or has_embeds or has_components:
|
|
554
|
+
await _send_payload(
|
|
555
|
+
channel, text, embeds, parser._components,
|
|
556
|
+
[], self._token,
|
|
557
|
+
client=self,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
for msg in extra_messages:
|
|
561
|
+
await self._send_extra(msg)
|
|
562
|
+
|
|
563
|
+
async def _execute_ready(self, code: str):
|
|
564
|
+
class FakeCtx:
|
|
565
|
+
class author:
|
|
566
|
+
id = 0
|
|
567
|
+
name = "System"
|
|
568
|
+
global_name = None
|
|
569
|
+
bot = False
|
|
570
|
+
display_avatar = None
|
|
571
|
+
class channel:
|
|
572
|
+
id = 0
|
|
573
|
+
class guild:
|
|
574
|
+
id = 0
|
|
575
|
+
name = ""
|
|
576
|
+
member_count = 0
|
|
577
|
+
icon = None
|
|
578
|
+
banner = None
|
|
579
|
+
owner_id = 0
|
|
580
|
+
premium_subscription_count = 0
|
|
581
|
+
roles = []
|
|
582
|
+
members = []
|
|
583
|
+
mentions = []
|
|
584
|
+
content = ""
|
|
585
|
+
id = 0
|
|
586
|
+
|
|
587
|
+
parser = Parser(FakeCtx(), self)
|
|
588
|
+
text, _, _ = await parser.evaluate(code)
|
|
589
|
+
if text:
|
|
590
|
+
print(f"[cordscript ready] {text}")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .runtime import find_functions, split_args
|
|
2
|
+
from .context import get_author, get_author_id, get_guild, get_channel, get_channel_id, is_interaction
|
|
3
|
+
from .variables import VariableStore
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"find_functions", "split_args",
|
|
7
|
+
"get_author", "get_author_id", "get_guild", "get_channel", "get_channel_id", "is_interaction",
|
|
8
|
+
"VariableStore",
|
|
9
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import discord
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_author(ctx):
|
|
6
|
+
if isinstance(ctx, discord.Interaction):
|
|
7
|
+
return ctx.user
|
|
8
|
+
return getattr(ctx, "author", None)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_author_id(ctx) -> str:
|
|
12
|
+
a = get_author(ctx)
|
|
13
|
+
return str(a.id) if a else ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_guild(ctx):
|
|
17
|
+
return getattr(ctx, "guild", None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_channel(ctx):
|
|
21
|
+
return getattr(ctx, "channel", None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_channel_id(ctx) -> str:
|
|
25
|
+
if hasattr(ctx, "channel") and ctx.channel:
|
|
26
|
+
return str(ctx.channel.id)
|
|
27
|
+
if hasattr(ctx, "channel_id") and ctx.channel_id:
|
|
28
|
+
return str(ctx.channel_id)
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_interaction(ctx) -> bool:
|
|
33
|
+
return isinstance(ctx, discord.Interaction)
|