cordscript 1.0.0__py3-none-any.whl

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 (37) hide show
  1. cordscript/__init__.py +6 -0
  2. cordscript/client.py +590 -0
  3. cordscript/core/__init__.py +9 -0
  4. cordscript/core/context.py +33 -0
  5. cordscript/core/runtime.py +55 -0
  6. cordscript/core/variables.py +16 -0
  7. cordscript/exceptions/__init__.py +3 -0
  8. cordscript/exceptions/errors.py +9 -0
  9. cordscript/functions/__init__.py +45 -0
  10. cordscript/functions/database/__init__.py +4 -0
  11. cordscript/functions/database/database.py +846 -0
  12. cordscript/functions/database/store.py +69 -0
  13. cordscript/functions/discord/__init__.py +15 -0
  14. cordscript/functions/discord/components.py +134 -0
  15. cordscript/functions/discord/componentsv2.py +92 -0
  16. cordscript/functions/discord/embed.py +63 -0
  17. cordscript/functions/discord/interaction.py +48 -0
  18. cordscript/functions/discord/logic.py +87 -0
  19. cordscript/functions/discord/message.py +51 -0
  20. cordscript/functions/discord/moderation.py +179 -0
  21. cordscript/functions/discord/status.py +56 -0
  22. cordscript/functions/users/__init__.py +3 -0
  23. cordscript/functions/users/user.py +103 -0
  24. cordscript/functions/utility/__init__.py +14 -0
  25. cordscript/functions/utility/cooldown.py +35 -0
  26. cordscript/functions/utility/http.py +73 -0
  27. cordscript/functions/utility/json.py +201 -0
  28. cordscript/functions/utility/math.py +102 -0
  29. cordscript/functions/utility/random.py +27 -0
  30. cordscript/functions/utility/strings.py +201 -0
  31. cordscript/functions/utility/time.py +41 -0
  32. cordscript/parser.py +190 -0
  33. cordscript/setup.py +38 -0
  34. cordscript-1.0.0.dist-info/METADATA +17 -0
  35. cordscript-1.0.0.dist-info/RECORD +37 -0
  36. cordscript-1.0.0.dist-info/WHEEL +5 -0
  37. cordscript-1.0.0.dist-info/top_level.txt +1 -0
cordscript/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from .client import Client
2
+ from .parser import Parser
3
+ from .functions.database import Database
4
+
5
+ __version__ = "1.0.0"
6
+ __all__ = ["Client", "Parser", "Database"]
cordscript/client.py ADDED
@@ -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)
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def find_functions(code: str) -> list[tuple[int, int, str, str | None]]:
5
+ results = []
6
+ i = 0
7
+ length = len(code)
8
+ while i < length:
9
+ if code[i] != "$":
10
+ i += 1
11
+ continue
12
+ j = i + 1
13
+ while j < length and (code[j].isalnum() or code[j] == "_"):
14
+ j += 1
15
+ if j == i + 1:
16
+ i += 1
17
+ continue
18
+ fn_name = code[i + 1:j]
19
+ if j < length and code[j] == "[":
20
+ depth = 1
21
+ k = j + 1
22
+ while k < length and depth > 0:
23
+ if code[k] == "[":
24
+ depth += 1
25
+ elif code[k] == "]":
26
+ depth -= 1
27
+ k += 1
28
+ if depth == 0:
29
+ raw_args = code[j + 1:k - 1]
30
+ results.append((i, k, fn_name, raw_args))
31
+ i = k
32
+ continue
33
+ results.append((i, j, fn_name, None))
34
+ i = j
35
+ return results
36
+
37
+
38
+ def split_args(raw: str) -> list[str]:
39
+ args = []
40
+ depth = 0
41
+ current = []
42
+ for ch in raw:
43
+ if ch == "[":
44
+ depth += 1
45
+ current.append(ch)
46
+ elif ch == "]":
47
+ depth -= 1
48
+ current.append(ch)
49
+ elif ch == ";" and depth == 0:
50
+ args.append("".join(current))
51
+ current = []
52
+ else:
53
+ current.append(ch)
54
+ args.append("".join(current))
55
+ return args