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
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import threading
4
+ from tinydb import TinyDB, Query
5
+
6
+
7
+ class Database:
8
+ def __init__(self, path: str = "database/data.json"):
9
+ self.path = path
10
+ directory = os.path.dirname(path)
11
+ if directory:
12
+ os.makedirs(directory, exist_ok=True)
13
+ self._lock = threading.Lock()
14
+ self._db = TinyDB(path)
15
+ self._tables = {
16
+ "user": self._db.table("users"),
17
+ "channel": self._db.table("channels"),
18
+ "guild": self._db.table("guilds"),
19
+ }
20
+ self._q = Query()
21
+
22
+ def _table(self, scope: str):
23
+ table = self._tables.get(scope)
24
+ if table is None:
25
+ raise ValueError(f"Escopo de database inválido: {scope}")
26
+ return table
27
+
28
+ def get(self, scope: str, scope_id: str, key: str, default: str = "") -> str:
29
+ scope_id = str(scope_id)
30
+ table = self._table(scope)
31
+ with self._lock:
32
+ doc = table.get(self._q.id == scope_id)
33
+ if not doc:
34
+ return default
35
+ return str(doc.get("vars", {}).get(key, default))
36
+
37
+ def set(self, scope: str, scope_id: str, key: str, value: str) -> None:
38
+ scope_id = str(scope_id)
39
+ table = self._table(scope)
40
+ with self._lock:
41
+ doc = table.get(self._q.id == scope_id)
42
+ if doc:
43
+ variables = dict(doc.get("vars", {}))
44
+ variables[key] = value
45
+ table.update({"vars": variables}, self._q.id == scope_id)
46
+ else:
47
+ table.insert({"id": scope_id, "vars": {key: value}})
48
+
49
+ def all(self, scope: str, scope_id: str) -> dict:
50
+ scope_id = str(scope_id)
51
+ table = self._table(scope)
52
+ with self._lock:
53
+ doc = table.get(self._q.id == scope_id)
54
+ return dict(doc.get("vars", {})) if doc else {}
55
+
56
+ def get_all(self) -> dict:
57
+ result = {}
58
+ with self._lock:
59
+ for scope_name, table in self._tables.items():
60
+ result[scope_name] = {}
61
+ for doc in table.all():
62
+ scope_id = doc.get("id", "unknown")
63
+ result[scope_name][scope_id] = doc.get("vars", {})
64
+ return result
65
+
66
+ def wipe(self) -> None:
67
+ with self._lock:
68
+ for table in self._tables.values():
69
+ table.truncate()
@@ -0,0 +1,15 @@
1
+ from .message import handle_message
2
+ from .interaction import handle_interaction
3
+ from .embed import handle_embed
4
+ from .components import handle_components, _flush_row, _flush_select
5
+ from .componentsv2 import handle_componentsv2
6
+ from .logic import handle_logic
7
+ from .moderation import handle_moderation
8
+ from .status import handle_status
9
+
10
+ __all__ = [
11
+ "handle_message", "handle_interaction", "handle_embed",
12
+ "handle_components", "handle_componentsv2", "handle_logic",
13
+ "handle_moderation", "handle_status",
14
+ "_flush_row", "_flush_select",
15
+ ]
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ _STYLE_MAP = {
4
+ "primary": 1,
5
+ "secondary": 2,
6
+ "success": 3,
7
+ "danger": 4,
8
+ "link": 5,
9
+ }
10
+
11
+
12
+ def _target_list(parser) -> list:
13
+ """Action rows go into the current CV2 container if we're inside one,
14
+ otherwise into the top-level (legacy) components list."""
15
+ if parser._cv2_stack:
16
+ return parser._cv2_target()
17
+ return parser._components
18
+
19
+
20
+ async def handle_components(fn: str, args: list[str], raw: str, parser) -> str | None:
21
+ try:
22
+ match fn:
23
+ case "addactionrow":
24
+ items = list(parser._pending_row or [])
25
+ if parser._pending_select:
26
+ items.append(parser._pending_select)
27
+ if items:
28
+ _target_list(parser).append({"type": 1, "components": items})
29
+ parser._pending_row = []
30
+ parser._pending_select = None
31
+ return ""
32
+
33
+ case "addbutton":
34
+ if parser._pending_row is None:
35
+ return "[Error in $addButton: must be inside $addActionRow]"
36
+
37
+ custom_id = args[0].strip() if len(args) > 0 else ""
38
+ label = args[1] if len(args) > 1 else ""
39
+ style_raw = args[2].strip().lower() if len(args) > 2 and args[2].strip() else "secondary"
40
+ emoji = args[3].strip() if len(args) > 3 and args[3].strip() else ""
41
+ disabled = (args[4].strip().lower() if len(args) > 4 else "") in ("true", "yes", "1")
42
+ url = args[5].strip() if len(args) > 5 and args[5].strip() else ""
43
+
44
+ if not custom_id and not url:
45
+ return "[Error in $addButton: custom_id or url required]"
46
+
47
+ data: dict = {
48
+ "type": 2,
49
+ "style": _STYLE_MAP.get(style_raw, 2),
50
+ "label": label,
51
+ "disabled": disabled,
52
+ }
53
+ if url:
54
+ data["url"] = url
55
+ data["style"] = 5
56
+ else:
57
+ data["custom_id"] = custom_id
58
+ if emoji:
59
+ data["emoji"] = {"name": emoji}
60
+
61
+ parser._pending_row.append(data)
62
+ if len(parser._pending_row) >= 5:
63
+ _target_list(parser).append({"type": 1, "components": parser._pending_row})
64
+ parser._pending_row = []
65
+ return ""
66
+
67
+ case "addselectmenu":
68
+ if parser._pending_row is None:
69
+ return "[Error in $addSelectMenu: must be inside $addActionRow]"
70
+
71
+ custom_id = args[0].strip() if len(args) > 0 else ""
72
+ placeholder = args[1] if len(args) > 1 else ""
73
+ try:
74
+ min_values = int(args[2].strip()) if len(args) > 2 and args[2].strip() else 1
75
+ except ValueError:
76
+ min_values = 1
77
+ try:
78
+ max_values = int(args[3].strip()) if len(args) > 3 and args[3].strip() else 1
79
+ except ValueError:
80
+ max_values = 1
81
+ disabled = (args[4].strip().lower() if len(args) > 4 else "") in ("true", "yes", "1")
82
+
83
+ if not custom_id:
84
+ return "[Error in $addSelectMenu: custom_id required]"
85
+
86
+ parser._pending_select = {
87
+ "type": 3,
88
+ "custom_id": custom_id,
89
+ "placeholder": placeholder,
90
+ "min_values": min_values,
91
+ "max_values": max_values,
92
+ "disabled": disabled,
93
+ "options": [],
94
+ }
95
+ return ""
96
+
97
+ case "addoption":
98
+ if parser._pending_select is None:
99
+ return "[Error in $addOption: no $addSelectMenu started]"
100
+ label = args[0] if len(args) > 0 else ""
101
+ value = args[1] if len(args) > 1 else label
102
+ description = args[2] if len(args) > 2 else ""
103
+ emoji = args[3].strip() if len(args) > 3 and args[3].strip() else ""
104
+ default = (args[4].strip().lower() if len(args) > 4 else "") in ("true", "yes", "1")
105
+
106
+ option: dict = {"label": label, "value": value, "default": default}
107
+ if description:
108
+ option["description"] = description
109
+ if emoji:
110
+ option["emoji"] = {"name": emoji}
111
+ parser._pending_select["options"].append(option)
112
+ return ""
113
+
114
+ case _:
115
+ return None
116
+
117
+ except Exception as e:
118
+ return f"[Unexpected error in ${fn}: {e}]"
119
+
120
+
121
+ def _flush_row(parser) -> None:
122
+ """Called at the end of evaluate() to catch a row left open without
123
+ an explicit $addActionRow close."""
124
+ if parser._pending_row:
125
+ _target_list(parser).append({"type": 1, "components": parser._pending_row})
126
+ parser._pending_row = []
127
+
128
+
129
+ def _flush_select(parser) -> None:
130
+ """Called at the end of evaluate() to catch a select menu left open
131
+ without an explicit $addActionRow close."""
132
+ if parser._pending_select:
133
+ _target_list(parser).append({"type": 1, "components": [parser._pending_select]})
134
+ parser._pending_select = None
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ async def handle_componentsv2(fn: str, args: list[str], raw: str, parser) -> str | None:
5
+ try:
6
+ match fn:
7
+ case "addcontainer":
8
+ color = args[1] if len(args) > 1 else None
9
+
10
+ # If an $addActionRow was opened inside this container and never
11
+ # explicitly closed, flush it into the container now (it's still
12
+ # the current target) before we pop the container's children off
13
+ # the stack.
14
+ if parser._pending_row or parser._pending_select:
15
+ items = list(parser._pending_row or [])
16
+ if parser._pending_select:
17
+ items.append(parser._pending_select)
18
+ if items:
19
+ parser._cv2_target().append({"type": 1, "components": items})
20
+ parser._pending_row = []
21
+ parser._pending_select = None
22
+
23
+ # Pop the children collected while this container's content was
24
+ # being evaluated (pushed onto the stack in parser._eval).
25
+ children = parser._cv2_stack.pop() if parser._cv2_stack else []
26
+
27
+ container: dict = {"type": 17, "components": children}
28
+ if color:
29
+ try:
30
+ container["accent_color"] = parser._parse_color(color)
31
+ except Exception:
32
+ pass
33
+
34
+ parser._cv2_target().append(container)
35
+ return ""
36
+
37
+ case "addtextdisplay":
38
+ title = args[0] if len(args) > 0 else ""
39
+ value = args[1] if len(args) > 1 else ""
40
+ if title and value:
41
+ content = f"**{title}**\n{value}"
42
+ else:
43
+ content = title or value
44
+ if not content:
45
+ return ""
46
+
47
+ parser._cv2_target().append({"type": 10, "content": content})
48
+ return ""
49
+
50
+ case "addseparator":
51
+ parser._cv2_target().append({"type": 14, "divider": True, "spacing": 1})
52
+ return ""
53
+
54
+ case "addthumbnail":
55
+ url = args[0] if len(args) > 0 else ""
56
+ if not url:
57
+ return ""
58
+
59
+ target = parser._cv2_target()
60
+ # If the previous item is a section without an accessory yet,
61
+ # attach the thumbnail there instead of adding a stray component.
62
+ if target and target[-1].get("type") == 9 and "accessory" not in target[-1]:
63
+ target[-1]["accessory"] = {"type": 11, "media": {"url": url}}
64
+ else:
65
+ target.append({"type": 11, "media": {"url": url}})
66
+ return ""
67
+
68
+ case "addmediagallery":
69
+ urls = args[0] if len(args) > 0 else ""
70
+ items = [{"media": {"url": u.strip()}} for u in urls.split(";") if u.strip()]
71
+ if not items:
72
+ return ""
73
+
74
+ parser._cv2_target().append({"type": 12, "items": items})
75
+ return ""
76
+
77
+ case "addsection":
78
+ title = args[0] if len(args) > 0 else ""
79
+ if not title:
80
+ return ""
81
+
82
+ parser._cv2_target().append({
83
+ "type": 9,
84
+ "components": [{"type": 10, "content": title}],
85
+ })
86
+ return ""
87
+
88
+ case _:
89
+ return None
90
+
91
+ except Exception as e:
92
+ return f"[Unexpected error in ${fn}: {e}]"
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+ from datetime import datetime, timezone
3
+
4
+
5
+ async def handle_embed(fn: str, args: list[str], raw: str, parser) -> str | None:
6
+ try:
7
+ match fn:
8
+ case "title":
9
+ parser._current_embed()._title = args[0] if args else raw
10
+ return ""
11
+
12
+ case "description":
13
+ parser._current_embed()._description = args[0] if args else raw
14
+ return ""
15
+
16
+ case "color" | "colour":
17
+ raw_color = args[0] if args else raw
18
+ parser._current_embed()._color = parser._parse_color(raw_color)
19
+ return ""
20
+
21
+ case "footer":
22
+ parser._current_embed()._footer_text = args[0] if args else raw
23
+ return ""
24
+
25
+ case "footericon":
26
+ parser._current_embed()._footer_icon = args[0] if args else raw
27
+ return ""
28
+
29
+ case "image":
30
+ parser._current_embed()._image = args[0] if args else raw
31
+ return ""
32
+
33
+ case "thumbnail":
34
+ parser._current_embed()._thumbnail = args[0] if args else raw
35
+ return ""
36
+
37
+ case "author":
38
+ eb = parser._current_embed()
39
+ eb._author_text = args[0] if len(args) > 0 else ""
40
+ eb._author_icon = args[1] if len(args) > 1 else None
41
+ return ""
42
+
43
+ case "addfield":
44
+ name = args[0] if len(args) > 0 else "\u200b"
45
+ value = args[1] if len(args) > 1 else "\u200b"
46
+ inline_raw = args[2].strip().lower() if len(args) > 2 else "false"
47
+ inline = inline_raw in ("true", "1", "yes")
48
+ parser._current_embed()._fields.append({
49
+ "name": name,
50
+ "value": value,
51
+ "inline": inline,
52
+ })
53
+ return ""
54
+
55
+ case "addtimestamp":
56
+ parser._current_embed()._timestamp = True
57
+ return ""
58
+
59
+ case _:
60
+ return None
61
+
62
+ except Exception as e:
63
+ return f"[Unexpected error in ${fn}: {e}]"
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+ import discord
3
+
4
+
5
+ async def handle_interaction(fn: str, args: list[str], raw: str, parser) -> str | None:
6
+ ctx = parser.ctx
7
+ is_interaction = isinstance(ctx, discord.Interaction)
8
+
9
+ match fn:
10
+ case "customid":
11
+ if is_interaction and ctx.data:
12
+ return ctx.data.get("custom_id", "")
13
+ return ""
14
+
15
+ case "defer":
16
+ if is_interaction and not ctx.response.is_done():
17
+ await ctx.response.defer()
18
+ return ""
19
+
20
+ case "deferephemeral":
21
+ if is_interaction and not ctx.response.is_done():
22
+ await ctx.response.defer(ephemeral=True)
23
+ return ""
24
+
25
+ case "interactionupdate":
26
+ content = args[0] if args else raw
27
+ if is_interaction and not ctx.response.is_done():
28
+ await ctx.response.edit_message(content=content)
29
+ elif is_interaction:
30
+ await ctx.edit_original_response(content=content)
31
+ return ""
32
+
33
+ case "ephemeral":
34
+ parser._ephemeral = True
35
+ return ""
36
+
37
+ case "interactionuser":
38
+ if is_interaction:
39
+ return str(ctx.user.id)
40
+ return str(ctx.author.id)
41
+
42
+ case "interactionchannel":
43
+ if is_interaction:
44
+ return str(ctx.channel_id)
45
+ return str(ctx.channel.id)
46
+
47
+ case _:
48
+ return None
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+ from ...exceptions import StopExecution
3
+
4
+
5
+ def _evaluate_condition(condition: str) -> bool:
6
+ condition = condition.strip()
7
+ for op in (">=", "<=", "!=", "==", ">", "<"):
8
+ if op in condition:
9
+ left, right = condition.split(op, 1)
10
+ left, right = left.strip(), right.strip()
11
+ try:
12
+ l_val: float | str = float(left)
13
+ r_val: float | str = float(right)
14
+ except ValueError:
15
+ l_val, r_val = left, right
16
+ match op:
17
+ case "==": return l_val == r_val
18
+ case "!=": return l_val != r_val
19
+ case ">": return l_val > r_val
20
+ case "<": return l_val < r_val
21
+ case ">=": return l_val >= r_val
22
+ case "<=": return l_val <= r_val
23
+ return bool(condition) and condition.lower() not in ("false", "0", "no")
24
+
25
+
26
+ async def handle_logic(fn: str, args: list[str], raw: str, parser) -> str | None:
27
+ try:
28
+ match fn:
29
+ case "if":
30
+ if not args:
31
+ return f"[Error in $if: condition required]"
32
+ condition = args[0].strip()
33
+ true_val = args[1] if len(args) > 1 else ""
34
+ false_val = args[2] if len(args) > 2 else ""
35
+ return true_val if _evaluate_condition(condition) else false_val
36
+
37
+ case "and":
38
+ if not args:
39
+ return f"[Error in $and: at least one condition required]"
40
+ return "true" if all(_evaluate_condition(a.strip()) for a in args) else "false"
41
+
42
+ case "or":
43
+ if not args:
44
+ return f"[Error in $or: at least one condition required]"
45
+ return "true" if any(_evaluate_condition(a.strip()) for a in args) else "false"
46
+
47
+ case "not":
48
+ val = args[0].strip() if args else ""
49
+ return "false" if _evaluate_condition(val) else "true"
50
+
51
+ case "switch":
52
+ if not args:
53
+ return f"[Error in $switch: value required]"
54
+ value = args[0].strip()
55
+ i = 1
56
+ while i + 1 < len(args):
57
+ if value == args[i].strip():
58
+ return args[i + 1]
59
+ i += 2
60
+ return args[i] if i < len(args) else ""
61
+
62
+ case "onlyif":
63
+ if not args:
64
+ return "[Error in $onlyIf: condition required]"
65
+ condition = args[0].strip()
66
+ error_msg = args[1] if len(args) > 1 else ""
67
+ if not _evaluate_condition(condition):
68
+ raise StopExecution(error_msg)
69
+ return ""
70
+
71
+ case "try":
72
+ code = args[0] if len(args) > 0 else ""
73
+ fallback = args[1] if len(args) > 1 else ""
74
+ try:
75
+ return await parser._eval(code)
76
+ except StopExecution:
77
+ raise
78
+ except Exception:
79
+ return fallback
80
+
81
+ case _:
82
+ return None
83
+
84
+ except StopExecution:
85
+ raise
86
+ except Exception as e:
87
+ return f"[Unexpected error in ${fn}: {e}]"
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ async def handle_message(fn: str, args: list[str], raw: str, parser) -> str | None:
5
+ try:
6
+ match fn:
7
+ case "reply":
8
+ parser._reply = True
9
+ content = args[0] if args else (raw if raw else None)
10
+ if content:
11
+ parser._reply_content = content
12
+ return ""
13
+
14
+ case "deleteafter":
15
+ seconds = args[0].strip() if args else raw.strip()
16
+ try:
17
+ parser._delete_after = float(seconds)
18
+ except ValueError:
19
+ return "[Error in $deleteAfter: invalid duration]"
20
+ return ""
21
+
22
+ case "sendmessage":
23
+ channel_id = args[0].strip() if len(args) > 0 else ""
24
+ content = args[1] if len(args) > 1 else ""
25
+ return_var = args[2].strip() if len(args) > 2 and args[2].strip() else None
26
+ if not channel_id:
27
+ return "[Error in $sendMessage: channel id required]"
28
+ try:
29
+ cid = int(channel_id)
30
+ except ValueError:
31
+ return "[Error in $sendMessage: invalid channel id]"
32
+ msg = {
33
+ "channel_id": cid,
34
+ "content": content,
35
+ "embeds": [],
36
+ "components": [],
37
+ "cv2": [],
38
+ "delete_after": None,
39
+ }
40
+ if return_var:
41
+ msg["return_id"] = True
42
+ msg["return_var"] = return_var
43
+ msg["_parser"] = parser
44
+ parser._extra_messages.append(msg)
45
+ return ""
46
+
47
+ case _:
48
+ return None
49
+
50
+ except Exception as e:
51
+ return f"[Unexpected error in ${fn}: {e}]"