cordscript 0.1.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.
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: cordscript
3
+ Version: 0.1.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
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: requires-dist
16
+ Dynamic: requires-python
@@ -0,0 +1,3 @@
1
+ # cordscript
2
+
3
+ **CordScript is a library to make developing Discord bots easier**!
@@ -0,0 +1,5 @@
1
+ from .client import Client
2
+ from .parser import Parser
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["Client", "Parser"]
@@ -0,0 +1,306 @@
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
+
12
+ logging.getLogger("discord").setLevel(logging.WARNING)
13
+ logging.getLogger("discord.http").setLevel(logging.WARNING)
14
+ logging.getLogger("discord.gateway").setLevel(logging.WARNING)
15
+ logging.getLogger("discord.client").setLevel(logging.WARNING)
16
+
17
+ _CV2_FLAG = 1 << 15
18
+
19
+
20
+ def _build_view(components: list[dict], client: "Client") -> ui.View | None:
21
+ view = ui.View(timeout=None)
22
+ added = False
23
+
24
+ for comp in components:
25
+ if comp.get("type") == 1:
26
+ for item in comp.get("components", []):
27
+ _add_item_to_view(view, item, client)
28
+ added = True
29
+ elif comp.get("type") in (2, 3):
30
+ _add_item_to_view(view, comp, client)
31
+ added = True
32
+
33
+ return view if added else None
34
+
35
+
36
+ def _add_item_to_view(view: ui.View, item: dict, client: "Client"):
37
+ if item.get("type") == 2:
38
+ styles = {
39
+ 1: discord.ButtonStyle.primary,
40
+ 2: discord.ButtonStyle.secondary,
41
+ 3: discord.ButtonStyle.success,
42
+ 4: discord.ButtonStyle.danger,
43
+ }
44
+ style = styles.get(item.get("style", 1), discord.ButtonStyle.primary)
45
+ button = ui.Button(
46
+ label=item.get("label", "Button"),
47
+ custom_id=item.get("custom_id"),
48
+ style=style,
49
+ )
50
+ async def button_callback(interaction: discord.Interaction, cid=item.get("custom_id")):
51
+ await client._dispatch_interaction(interaction, cid)
52
+ button.callback = button_callback
53
+ view.add_item(button)
54
+
55
+ elif item.get("type") == 3:
56
+ options = [
57
+ discord.SelectOption(label=o["label"], value=o["value"])
58
+ for o in item.get("options", [])
59
+ ]
60
+ select = ui.Select(
61
+ custom_id=item.get("custom_id"),
62
+ placeholder=item.get("placeholder", "Selecione..."),
63
+ options=options,
64
+ )
65
+ async def select_callback(interaction: discord.Interaction, cid=item.get("custom_id")):
66
+ await client._dispatch_interaction(interaction, cid)
67
+ select.callback = select_callback
68
+ view.add_item(select)
69
+
70
+
71
+ class Client(discord.Client):
72
+ def __init__(
73
+ self,
74
+ *,
75
+ prefix: str | list[str],
76
+ token: str,
77
+ insensitive: bool = False,
78
+ ignoreBots: bool = True,
79
+ intents: discord.Intents | None = None,
80
+ **kwargs,
81
+ ):
82
+ resolved_intents = intents or discord.Intents.default()
83
+ resolved_intents.message_content = True
84
+ super().__init__(intents=resolved_intents, **kwargs)
85
+
86
+ self._prefixes: list[str] = [prefix] if isinstance(prefix, str) else list(prefix)
87
+ self._token = token
88
+ self._insensitive = insensitive
89
+ self._ignore_bots = ignoreBots
90
+ self._commands: dict[str, dict] = {}
91
+ self._static_commands: set[str] = set()
92
+ self._static_entries: dict[str, tuple] = {}
93
+ self._interactions: list[dict] = []
94
+ self._ready_command: dict | None = None
95
+ self._commands_path: str | None = None
96
+ self._main_file: str | None = None
97
+
98
+ def command(self, *, name: str, code: str, aliases: list[str] | None = None):
99
+ key = name.lower() if self._insensitive else name
100
+ entry = {"name": name, "code": code}
101
+ self._commands[key] = entry
102
+ self._static_commands.add(key)
103
+ self._static_entries[key] = (name, code, aliases)
104
+ for alias in (aliases or []):
105
+ alias_key = alias.lower() if self._insensitive else alias
106
+ self._commands[alias_key] = entry
107
+ self._static_commands.add(alias_key)
108
+
109
+ def interaction(self, *, code: str, customId: str | None = None):
110
+ self._interactions.append({"customId": customId, "code": code})
111
+
112
+ def readyCommand(self, *, code: str):
113
+ self._ready_command = {"code": code}
114
+
115
+ def loadCommands(self, path: str):
116
+ self._commands_path = os.path.abspath(path)
117
+ self._load_from_path(self._commands_path)
118
+
119
+ def reloadCommands(self, path: str | None = None):
120
+ if self._commands_path:
121
+ for k in list(self._commands.keys()):
122
+ if k not in self._static_commands:
123
+ del self._commands[k]
124
+ target = os.path.abspath(path) if path else self._commands_path
125
+ self._load_from_path(target)
126
+ else:
127
+ if not self._main_file:
128
+ return
129
+ self._commands.clear()
130
+ self._static_commands.clear()
131
+ self._static_entries.clear()
132
+ spec = importlib.util.spec_from_file_location("__main_reload__", self._main_file)
133
+ module = importlib.util.module_from_spec(spec)
134
+ module.bot = self
135
+ try:
136
+ spec.loader.exec_module(module)
137
+ print(f"[cordscript] Reloaded main file")
138
+ except Exception as e:
139
+ print(f"[cordscript] Failed to reload: {e}")
140
+
141
+ def _load_from_path(self, path: str):
142
+ loaded = 0
143
+ for root, _, files in os.walk(path):
144
+ for filename in files:
145
+ if not filename.endswith(".py") or filename.startswith("_"):
146
+ continue
147
+ filepath = os.path.join(root, filename)
148
+ spec = importlib.util.spec_from_file_location(filename[:-3], filepath)
149
+ module = importlib.util.module_from_spec(spec)
150
+ module.bot = self
151
+ try:
152
+ spec.loader.exec_module(module)
153
+ loaded += 1
154
+ except Exception as e:
155
+ print(f"[cordscript] Failed to load {filepath}: {e}")
156
+ print(f"[cordscript] Loaded {loaded} command file(s) from '{path}'")
157
+
158
+ def run(self, token: str | None = None):
159
+ self._main_file = os.path.abspath(inspect.stack()[1].filename)
160
+ super().run(token or self._token)
161
+
162
+ async def on_ready(self):
163
+ prefixes = ", ".join(f'"{p}"' for p in self._prefixes)
164
+ print(f"[cordscript] Logged in as {self.user} (ID: {self.user.id})")
165
+ print(f"[cordscript] Prefixes: {prefixes} | insensitive={self._insensitive} | ignoreBots={self._ignore_bots}")
166
+ if self._ready_command:
167
+ await self._execute_ready(self._ready_command["code"])
168
+
169
+ async def on_message(self, message: discord.Message):
170
+ if self._ignore_bots and message.author.bot:
171
+ return
172
+
173
+ content = message.content
174
+ matched_prefix: str | None = None
175
+
176
+ for prefix in self._prefixes:
177
+ check_content = content.lower() if self._insensitive else content
178
+ check_prefix = prefix.lower() if self._insensitive else prefix
179
+ if check_content.startswith(check_prefix):
180
+ matched_prefix = prefix
181
+ break
182
+
183
+ if matched_prefix is None:
184
+ return
185
+
186
+ raw = content[len(matched_prefix):]
187
+ parts = raw.split(None, 1)
188
+ if not parts:
189
+ return
190
+
191
+ cmd_name = parts[0].lower() if self._insensitive else parts[0]
192
+ command = self._commands.get(cmd_name)
193
+ if not command:
194
+ return
195
+
196
+ await self._execute(message, command["code"])
197
+
198
+ async def _dispatch_interaction(self, interaction: discord.Interaction, custom_id: str | None):
199
+ for entry in self._interactions:
200
+ if entry["customId"] is None or entry["customId"] == custom_id:
201
+ await self._execute_interaction(interaction, entry["code"])
202
+ return
203
+
204
+ async def _send_cv2(self, channel_id: int, components: list[dict], content: str | None = None):
205
+ url = f"https://discord.com/api/v10/channels/{channel_id}/messages"
206
+ headers = {
207
+ "Authorization": f"Bot {self._token}",
208
+ "Content-Type": "application/json",
209
+ }
210
+ payload: dict = {"flags": _CV2_FLAG, "components": components}
211
+ if content:
212
+ payload["content"] = content
213
+ async with aiohttp.ClientSession() as session:
214
+ resp = await session.post(url, headers=headers, data=json.dumps(payload))
215
+ if not resp.ok:
216
+ text = await resp.text()
217
+ print(f"[cordscript cv2] Error {resp.status}: {text}")
218
+
219
+ async def _edit_cv2(self, interaction: discord.Interaction, components: list[dict], content: str | None = None):
220
+ url = f"https://discord.com/api/v10/webhooks/{self.application_id}/{interaction.token}/messages/@original"
221
+ headers = {"Content-Type": "application/json"}
222
+ payload: dict = {"flags": _CV2_FLAG, "components": components}
223
+ if content:
224
+ payload["content"] = content
225
+ async with aiohttp.ClientSession() as session:
226
+ resp = await session.patch(url, headers=headers, data=json.dumps(payload))
227
+ if not resp.ok:
228
+ text = await resp.text()
229
+ print(f"[cordscript cv2] Error {resp.status}: {text}")
230
+
231
+ async def _execute(self, ctx: discord.Message, code: str):
232
+ parser = Parser(ctx, self)
233
+ text, embeds, extra_messages = await parser.evaluate(code)
234
+
235
+ if parser._cv2_pending:
236
+ await self._send_cv2(ctx.channel.id, parser._cv2_pending, text or None)
237
+ else:
238
+ kwargs: dict = {}
239
+ if text:
240
+ kwargs["content"] = text
241
+ if embeds:
242
+ kwargs["embeds"] = [discord.Embed.from_dict(e) for e in embeds]
243
+
244
+ view = _build_view(parser._components, self)
245
+ if view:
246
+ kwargs["view"] = view
247
+
248
+ if kwargs:
249
+ delete_after = parser._delete_after
250
+ if parser._reply_content:
251
+ kwargs["content"] = parser._reply_content
252
+ await ctx.reply(**kwargs, delete_after=delete_after)
253
+ else:
254
+ await ctx.channel.send(**kwargs, delete_after=delete_after)
255
+
256
+ for msg in extra_messages:
257
+ channel = self.get_channel(msg["channel_id"])
258
+ if not channel:
259
+ try:
260
+ channel = await self.fetch_channel(msg["channel_id"])
261
+ except Exception:
262
+ continue
263
+
264
+ send_kwargs: dict = {}
265
+ if msg["content"]:
266
+ send_kwargs["content"] = msg["content"]
267
+ if msg["embeds"]:
268
+ send_kwargs["embeds"] = [discord.Embed.from_dict(e) for e in msg["embeds"]]
269
+
270
+ if send_kwargs:
271
+ await channel.send(**send_kwargs, delete_after=msg.get("delete_after"))
272
+
273
+ async def _execute_interaction(self, interaction: discord.Interaction, code: str):
274
+ parser = Parser(interaction, self)
275
+ text, embeds, _ = await parser.evaluate(code)
276
+
277
+ if parser._cv2_pending:
278
+ if not interaction.response.is_done():
279
+ await interaction.response.defer()
280
+ await self._edit_cv2(interaction, parser._cv2_pending, text or None)
281
+ else:
282
+ kwargs: dict = {}
283
+ if text:
284
+ kwargs["content"] = text
285
+ if embeds:
286
+ kwargs["embeds"] = [discord.Embed.from_dict(e) for e in embeds]
287
+
288
+ view = _build_view(parser._components, self)
289
+ if view:
290
+ kwargs["view"] = view
291
+
292
+ if not interaction.response.is_done() and kwargs:
293
+ await interaction.response.send_message(**kwargs, ephemeral=parser._ephemeral)
294
+
295
+ async def _execute_ready(self, code: str):
296
+ class FakeCtx:
297
+ class channel:
298
+ id = 0
299
+ class author:
300
+ id = 0
301
+ name = "System"
302
+
303
+ parser = Parser(FakeCtx(), self)
304
+ text, embeds, _ = await parser.evaluate(code)
305
+ if text:
306
+ print(f"[cordscript ready] {text}")
@@ -0,0 +1,19 @@
1
+ from .utility import handle_utility
2
+ from .user import handle_user
3
+ from .embed import handle_embed
4
+ from .message import handle_message
5
+ from .components import handle_components
6
+ from .componentsv2 import handle_componentsv2
7
+ from .interaction import handle_interaction
8
+ from .logic import handle_logic
9
+
10
+ __all__ = [
11
+ "handle_utility",
12
+ "handle_user",
13
+ "handle_embed",
14
+ "handle_message",
15
+ "handle_components",
16
+ "handle_componentsv2",
17
+ "handle_interaction",
18
+ "handle_logic",
19
+ ]
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ async def handle_components(fn: str, args: list[str], raw: str, parser) -> str | None:
5
+ match fn:
6
+ case "addbutton":
7
+ label = args[0] if len(args) > 0 else "Button"
8
+ custom_id = args[1] if len(args) > 1 else "button"
9
+ style_raw = args[2].strip().lower() if len(args) > 2 else "primary"
10
+ styles = {"primary": 1, "secondary": 2, "success": 3, "danger": 4, "link": 5}
11
+ style = styles.get(style_raw, 1)
12
+ comp: dict = {
13
+ "type": 2,
14
+ "label": label,
15
+ "style": style,
16
+ }
17
+ if style == 5:
18
+ comp["url"] = custom_id
19
+ else:
20
+ comp["custom_id"] = custom_id
21
+ parser._components.append(comp)
22
+ return ""
23
+
24
+ case "selectmenu":
25
+ custom_id = args[0] if len(args) > 0 else "select"
26
+ placeholder = args[1] if len(args) > 1 else "Selecione..."
27
+ options = []
28
+ for opt in args[2:]:
29
+ opt = opt.strip()
30
+ if opt:
31
+ options.append({"label": opt, "value": opt.lower().replace(" ", "_")})
32
+ parser._components.append({
33
+ "type": 3,
34
+ "custom_id": custom_id,
35
+ "placeholder": placeholder,
36
+ "options": options,
37
+ })
38
+ return ""
39
+
40
+ case _:
41
+ return None
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+ import json
3
+ import discord
4
+
5
+ _CV2_TOKEN = "\x00CV2:"
6
+ _CV2_END = "\x00"
7
+
8
+
9
+ def _parse_color(value: str) -> int | None:
10
+ if not value:
11
+ return None
12
+ value = value.strip().lstrip("#")
13
+ try:
14
+ return int(value, 16)
15
+ except ValueError:
16
+ named = {
17
+ "red": 0xFF0000, "green": 0x00FF00, "blue": 0x0000FF,
18
+ "yellow": 0xFFFF00, "orange": 0xFF8000, "purple": 0x800080,
19
+ "pink": 0xFFC0CB, "white": 0xFFFFFF, "black": 0x000000,
20
+ "cyan": 0x00FFFF, "blurple": 0x5865F2, "gray": 0x808080,
21
+ }
22
+ return named.get(value.lower())
23
+
24
+
25
+ def _token(data: dict) -> str:
26
+ return f"{_CV2_TOKEN}{json.dumps(data)}{_CV2_END}"
27
+
28
+
29
+ def _extract_tokens(value: str) -> list[dict]:
30
+ items = []
31
+ parts = value.split(_CV2_TOKEN)
32
+ for part in parts[1:]:
33
+ end = part.find(_CV2_END)
34
+ if end != -1:
35
+ try:
36
+ items.append(json.loads(part[:end]))
37
+ except Exception:
38
+ pass
39
+ return items
40
+
41
+
42
+ def _is_token(value: str) -> bool:
43
+ return _CV2_TOKEN in value
44
+
45
+
46
+ async def handle_componentsv2(fn: str, args: list[str], raw: str, parser) -> str | None:
47
+ in_container = getattr(parser, "_cv2_in_container", False)
48
+
49
+ match fn:
50
+ case "addtextdisplay":
51
+ content = args[0] if args else raw
52
+ data = {"type": 10, "content": content}
53
+ if in_container:
54
+ return _token(data)
55
+ parser._cv2_pending.append(data)
56
+ return ""
57
+
58
+ case "addseparator":
59
+ visible = (args[0].strip().lower() if args else "true") in ("true", "yes", "1")
60
+ spacing_raw = args[1].strip().lower() if len(args) > 1 else "small"
61
+ spacing = 2 if spacing_raw == "large" else 1
62
+ data = {"type": 14, "divider": visible, "spacing": spacing}
63
+ if in_container:
64
+ return _token(data)
65
+ parser._cv2_pending.append(data)
66
+ return ""
67
+
68
+ case "addthumbnail":
69
+ url = args[0].strip() if args else ""
70
+ desc = args[1] if len(args) > 1 else ""
71
+ data = {"type": 11, "media": {"url": url}}
72
+ if desc:
73
+ data["description"] = desc
74
+ if in_container:
75
+ return _token(data)
76
+ parser._cv2_pending.append(data)
77
+ return ""
78
+
79
+ case "addmediagallery":
80
+ items = []
81
+ i = 0
82
+ while i < len(args):
83
+ url = args[i].strip()
84
+ desc = args[i + 1] if i + 1 < len(args) else ""
85
+ if url:
86
+ item = {"media": {"url": url}}
87
+ if desc:
88
+ item["description"] = desc
89
+ items.append(item)
90
+ i += 2
91
+ data = {"type": 12, "items": items}
92
+ if in_container:
93
+ return _token(data)
94
+ parser._cv2_pending.append(data)
95
+ return ""
96
+
97
+ case "addsection":
98
+ text = args[0] if len(args) > 0 else ""
99
+ accessory_type = args[1].strip().lower() if len(args) > 1 else ""
100
+ accessory_value = args[2] if len(args) > 2 else ""
101
+ data = {
102
+ "type": 9,
103
+ "components": [{"type": 10, "content": text}],
104
+ }
105
+ if accessory_type == "thumbnail":
106
+ data["accessory"] = {"type": 11, "media": {"url": accessory_value}}
107
+ elif accessory_type == "button":
108
+ parts = accessory_value.split(";")
109
+ label = parts[0] if parts else "Button"
110
+ custom_id = parts[1] if len(parts) > 1 else "btn"
111
+ style_raw = parts[2].strip().lower() if len(parts) > 2 else "secondary"
112
+ styles = {"primary": 1, "secondary": 2, "success": 3, "danger": 4}
113
+ data["accessory"] = {
114
+ "type": 2,
115
+ "label": label,
116
+ "custom_id": custom_id,
117
+ "style": styles.get(style_raw, 2),
118
+ }
119
+ if in_container:
120
+ return _token(data)
121
+ parser._cv2_pending.append(data)
122
+ return ""
123
+
124
+ case "addcontainer":
125
+ parser._cv2_in_container = True
126
+ inner_items = []
127
+ color_raw = ""
128
+
129
+ for arg in args:
130
+ if _is_token(arg):
131
+ inner_items.extend(_extract_tokens(arg))
132
+ else:
133
+ color_raw = arg.strip()
134
+
135
+ parser._cv2_in_container = False
136
+ data: dict = {"type": 17, "components": inner_items}
137
+ color = _parse_color(color_raw)
138
+ if color is not None:
139
+ data["accent_color"] = color
140
+ parser._cv2_pending.append(data)
141
+ return ""
142
+
143
+ case _:
144
+ return None
@@ -0,0 +1,59 @@
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
+ match fn:
7
+ case "title":
8
+ parser._current_embed()._title = args[0] if args else raw
9
+ return ""
10
+
11
+ case "description":
12
+ parser._current_embed()._description = args[0] if args else raw
13
+ return ""
14
+
15
+ case "color" | "colour":
16
+ raw_color = args[0] if args else raw
17
+ parser._current_embed()._color = parser._parse_color(raw_color)
18
+ return ""
19
+
20
+ case "footer":
21
+ parser._current_embed()._footer_text = args[0] if args else raw
22
+ return ""
23
+
24
+ case "footericon":
25
+ parser._current_embed()._footer_icon = args[0] if args else raw
26
+ return ""
27
+
28
+ case "image":
29
+ parser._current_embed()._image = args[0] if args else raw
30
+ return ""
31
+
32
+ case "thumbnail":
33
+ parser._current_embed()._thumbnail = args[0] if args else raw
34
+ return ""
35
+
36
+ case "author":
37
+ eb = parser._current_embed()
38
+ eb._author_text = args[0] if len(args) > 0 else ""
39
+ eb._author_icon = args[1] if len(args) > 1 else None
40
+ return ""
41
+
42
+ case "addfield":
43
+ name = args[0] if len(args) > 0 else "\u200b"
44
+ value = args[1] if len(args) > 1 else "\u200b"
45
+ inline_raw = args[2].strip().lower() if len(args) > 2 else "false"
46
+ inline = inline_raw in ("true", "1", "yes")
47
+ parser._current_embed()._fields.append({
48
+ "name": name,
49
+ "value": value,
50
+ "inline": inline,
51
+ })
52
+ return ""
53
+
54
+ case "addtimestamp":
55
+ parser._current_embed()._timestamp = True
56
+ return ""
57
+
58
+ case _:
59
+ return None
@@ -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,34 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def _evaluate_condition(condition: str) -> bool:
5
+ for op in (">=", "<=", "!=", "==", ">", "<"):
6
+ if op in condition:
7
+ left, right = condition.split(op, 1)
8
+ left, right = left.strip(), right.strip()
9
+ try:
10
+ l, r = float(left), float(right)
11
+ except ValueError:
12
+ l, r = left, right
13
+ match op:
14
+ case "==": return l == r
15
+ case "!=": return l != r
16
+ case ">": return l > r
17
+ case "<": return l < r
18
+ case ">=": return l >= r
19
+ case "<=": return l <= r
20
+ return False
21
+
22
+
23
+ async def handle_logic(fn: str, args: list[str], raw: str, parser) -> str | None:
24
+ match fn:
25
+ case "if":
26
+ if not args:
27
+ return ""
28
+ condition = args[0].strip()
29
+ true_val = args[1] if len(args) > 1 else ""
30
+ false_val = args[2] if len(args) > 2 else ""
31
+ return true_val if _evaluate_condition(condition) else false_val
32
+
33
+ case _:
34
+ return None
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ async def handle_message(fn: str, args: list[str], raw: str, parser) -> str | None:
5
+ match fn:
6
+ case "sendmessage":
7
+ channel_id_raw = args[0].strip() if args else ""
8
+ content = args[1] if len(args) > 1 else ""
9
+ if channel_id_raw:
10
+ try:
11
+ channel_id = int(channel_id_raw)
12
+ from ..parser import Parser
13
+ sub_parser = Parser(parser.ctx, parser.client)
14
+ sub_parser._start_time = parser._start_time
15
+ parsed_content, parsed_embeds, _ = await sub_parser.evaluate(content)
16
+ parser._extra_messages.append({
17
+ "channel_id": channel_id,
18
+ "content": parsed_content,
19
+ "embeds": parsed_embeds,
20
+ "delete_after": None,
21
+ })
22
+ except (ValueError, Exception):
23
+ pass
24
+ return ""
25
+
26
+ case "reply":
27
+ parser._reply = True
28
+ if args:
29
+ parser._reply_content = args[0]
30
+ return ""
31
+
32
+ case "deletein":
33
+ try:
34
+ parser._delete_after = float(args[0].strip()) if args else None
35
+ except ValueError:
36
+ pass
37
+ return ""
38
+
39
+ case "updatecommands":
40
+ path = args[0].strip() if args and args[0].strip() else None
41
+ parser.client.reloadCommands(path)
42
+ return ""
43
+
44
+ case _:
45
+ return None
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+ import random
3
+ import time
4
+ import ast
5
+ import operator
6
+ from datetime import datetime, timezone
7
+
8
+
9
+ _OPERATORS = {
10
+ ast.Add: operator.add,
11
+ ast.Sub: operator.sub,
12
+ ast.Mult: operator.mul,
13
+ ast.Div: operator.truediv,
14
+ ast.FloorDiv: operator.floordiv,
15
+ ast.Mod: operator.mod,
16
+ ast.Pow: operator.pow,
17
+ ast.USub: operator.neg,
18
+ }
19
+
20
+
21
+ def _eval_expr(node):
22
+ if isinstance(node, ast.Expression):
23
+ return _eval_expr(node.body)
24
+ elif isinstance(node, ast.Constant):
25
+ return node.value
26
+ elif isinstance(node, ast.BinOp):
27
+ op = _OPERATORS.get(type(node.op))
28
+ if op is None:
29
+ raise ValueError("Operador não suportado")
30
+ return op(_eval_expr(node.left), _eval_expr(node.right))
31
+ elif isinstance(node, ast.UnaryOp):
32
+ op = _OPERATORS.get(type(node.op))
33
+ if op is None:
34
+ raise ValueError("Operador não suportado")
35
+ return op(_eval_expr(node.operand))
36
+ raise ValueError("Expressão inválida")
37
+
38
+
39
+ async def handle_utility(fn: str, args: list[str], raw: str, parser) -> str | None:
40
+ match fn:
41
+ case "ping":
42
+ latency = round(parser.client.latency * 1000)
43
+ return f"{latency}ms"
44
+
45
+ case "executiontime":
46
+ elapsed = round((time.monotonic() - parser._start_time) * 1000)
47
+ return f"{elapsed}ms"
48
+
49
+ case "channelid":
50
+ return str(parser.ctx.channel.id)
51
+
52
+ case "messageid":
53
+ return str(parser.ctx.id)
54
+
55
+ case "random":
56
+ try:
57
+ min_val = int(args[0].strip()) if len(args) > 0 and args[0].strip() else 1
58
+ max_val = int(args[1].strip()) if len(args) > 1 and args[1].strip() else 100
59
+ return str(random.randint(min_val, max_val))
60
+ except ValueError:
61
+ return "0"
62
+
63
+ case "upper":
64
+ return args[0].upper() if args else raw.upper()
65
+
66
+ case "lower":
67
+ return args[0].lower() if args else raw.lower()
68
+
69
+ case "length":
70
+ return str(len(args[0]) if args else len(raw))
71
+
72
+ case "trim":
73
+ return args[0].strip() if args else raw.strip()
74
+
75
+ case "replace":
76
+ text = args[0] if len(args) > 0 else ""
77
+ from_ = args[1] if len(args) > 1 else ""
78
+ to_ = args[2] if len(args) > 2 else ""
79
+ return text.replace(from_, to_)
80
+
81
+ case "includes":
82
+ text = args[0] if len(args) > 0 else ""
83
+ search = args[1] if len(args) > 1 else ""
84
+ return "true" if search in text else "false"
85
+
86
+ case "repeat":
87
+ try:
88
+ times = int(args[0].strip()) if args else 1
89
+ except ValueError:
90
+ times = 1
91
+ text = args[1] if len(args) > 1 else ""
92
+ return text * times
93
+
94
+ case "time":
95
+ now = datetime.now()
96
+ return now.strftime("%H:%M:%S")
97
+
98
+ case "date":
99
+ now = datetime.now()
100
+ return now.strftime("%d/%m/%Y")
101
+
102
+ case "var":
103
+ name = args[0].strip() if args else ""
104
+ if not name:
105
+ return ""
106
+ if len(args) > 1:
107
+ parser._vars[name] = args[1]
108
+ return ""
109
+ return parser._vars.get(name, "")
110
+
111
+ case "textsplit":
112
+ text = args[0] if len(args) > 0 else ""
113
+ sep = args[1] if len(args) > 1 else ","
114
+ parser._split_cache = text.split(sep)
115
+ return ""
116
+
117
+ case "splittext":
118
+ cache = getattr(parser, "_split_cache", [])
119
+ try:
120
+ index = int(args[0].strip()) - 1 if args and args[0].strip() else 0
121
+ return cache[index] if 0 <= index < len(cache) else ""
122
+ except (ValueError, IndexError):
123
+ return ""
124
+
125
+ case "eval":
126
+ expr = args[0] if args else raw
127
+ if "$" in expr:
128
+ return await parser._eval(expr)
129
+ try:
130
+ tree = ast.parse(expr, mode="eval")
131
+ result = _eval_expr(tree)
132
+ if isinstance(result, float) and result.is_integer():
133
+ return str(int(result))
134
+ return str(result)
135
+ except Exception:
136
+ return expr
137
+
138
+ case "dpyeval":
139
+ code = args[0] if args else raw
140
+ try:
141
+ local_vars = {"ctx": parser.ctx, "bot": parser.client, "result": None}
142
+ wrapped = f"async def __fn():\n" + "\n".join(f" {line}" for line in code.splitlines())
143
+ exec(wrapped, local_vars)
144
+ ret = await local_vars["__fn"]()
145
+ return str(ret) if ret is not None else ""
146
+ except Exception as e:
147
+ return f"Error: {e}"
148
+
149
+ case "message":
150
+ parts = parser.ctx.content.split(None, 1)
151
+ content = parts[1] if len(parts) > 1 else ""
152
+ if not args or not args[0].strip():
153
+ return content
154
+ try:
155
+ index = int(args[0].strip()) - 1
156
+ words = content.split()
157
+ return words[index] if 0 <= index < len(words) else ""
158
+ except (ValueError, IndexError):
159
+ return ""
160
+
161
+ case _:
162
+ return None
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ async def handle_user(fn: str, args: list[str], raw: str, parser) -> str | None:
5
+ msg = parser.ctx
6
+
7
+ match fn:
8
+ case "authorid":
9
+ return str(msg.author.id)
10
+
11
+ case "username":
12
+ uid = args[0].strip() if args and args[0].strip() else None
13
+ if uid:
14
+ try:
15
+ user = await parser.client.fetch_user(int(uid))
16
+ return user.name
17
+ except Exception:
18
+ return "Unknown"
19
+ return msg.author.name
20
+
21
+ case "nickname":
22
+ global_name = getattr(msg.author, "global_name", None)
23
+ return global_name or msg.author.name
24
+
25
+ case "mentioned":
26
+ mentions = msg.mentions
27
+ index_raw = args[0].strip() if args and args[0].strip() else "1"
28
+ return_author_raw = args[1].strip().lower() if len(args) > 1 else "false"
29
+ return_author = return_author_raw in ("true", "yes")
30
+ try:
31
+ index = int(index_raw) - 1
32
+ except ValueError:
33
+ index = 0
34
+ if not mentions:
35
+ return str(msg.author.id) if return_author else ""
36
+ if index < 0 or index >= len(mentions):
37
+ return str(msg.author.id) if return_author else ""
38
+ return str(mentions[index].id)
39
+
40
+ case "useravatar":
41
+ uid = args[0].strip() if args and args[0].strip() else None
42
+ if uid:
43
+ try:
44
+ user = await parser.client.fetch_user(int(uid))
45
+ return str(user.display_avatar.url) if user.display_avatar else ""
46
+ except Exception:
47
+ return ""
48
+ avatar = msg.author.display_avatar
49
+ return str(avatar.url) if avatar else ""
50
+
51
+ case "servername":
52
+ return msg.guild.name if msg.guild else ""
53
+
54
+ case "serverid":
55
+ return str(msg.guild.id) if msg.guild else ""
56
+
57
+ case "membercount":
58
+ return str(msg.guild.member_count) if msg.guild else "0"
59
+
60
+ case _:
61
+ return None
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+ import time
3
+ from datetime import datetime, timezone
4
+ from .functions import (
5
+ handle_utility, handle_user, handle_embed, handle_message,
6
+ handle_components, handle_componentsv2, handle_interaction, handle_logic
7
+ )
8
+
9
+
10
+ def _find_functions(code: str) -> list[tuple[int, int, str, str | None]]:
11
+ results = []
12
+ i = 0
13
+ length = len(code)
14
+
15
+ while i < length:
16
+ if code[i] != "$":
17
+ i += 1
18
+ continue
19
+
20
+ j = i + 1
21
+ while j < length and (code[j].isalnum() or code[j] == "_"):
22
+ j += 1
23
+
24
+ if j == i + 1:
25
+ i += 1
26
+ continue
27
+
28
+ fn_name = code[i + 1:j]
29
+
30
+ if j < length and code[j] == "[":
31
+ depth = 1
32
+ k = j + 1
33
+ while k < length and depth > 0:
34
+ if code[k] == "[":
35
+ depth += 1
36
+ elif code[k] == "]":
37
+ depth -= 1
38
+ k += 1
39
+
40
+ if depth == 0:
41
+ raw_args = code[j + 1:k - 1]
42
+ results.append((i, k, fn_name, raw_args))
43
+ i = k
44
+ continue
45
+
46
+ results.append((i, j, fn_name, None))
47
+ i = j
48
+
49
+ return results
50
+
51
+
52
+ def _split_args(raw: str) -> list[str]:
53
+ args = []
54
+ depth = 0
55
+ current = []
56
+
57
+ for ch in raw:
58
+ if ch == "[":
59
+ depth += 1
60
+ current.append(ch)
61
+ elif ch == "]":
62
+ depth -= 1
63
+ current.append(ch)
64
+ elif ch == ";" and depth == 0:
65
+ args.append("".join(current))
66
+ current = []
67
+ else:
68
+ current.append(ch)
69
+
70
+ args.append("".join(current))
71
+ return args
72
+
73
+
74
+ class EmbedBuilder:
75
+ def __init__(self):
76
+ self.reset()
77
+
78
+ def reset(self):
79
+ self._title: str | None = None
80
+ self._description: str | None = None
81
+ self._color: int | None = None
82
+ self._footer_text: str | None = None
83
+ self._footer_icon: str | None = None
84
+ self._image: str | None = None
85
+ self._thumbnail: str | None = None
86
+ self._author_text: str | None = None
87
+ self._author_icon: str | None = None
88
+ self._fields: list[dict] = []
89
+ self._timestamp: bool = False
90
+
91
+ def has_data(self) -> bool:
92
+ return any([
93
+ self._title, self._description, self._color is not None,
94
+ self._footer_text, self._image, self._thumbnail,
95
+ self._author_text, self._fields, self._timestamp,
96
+ ])
97
+
98
+ def to_dict(self) -> dict:
99
+ embed: dict = {"type": "rich"}
100
+ if self._title:
101
+ embed["title"] = self._title
102
+ if self._description:
103
+ embed["description"] = self._description
104
+ if self._color is not None:
105
+ embed["color"] = self._color
106
+ if self._footer_text:
107
+ embed["footer"] = {"text": self._footer_text}
108
+ if self._footer_icon:
109
+ embed["footer"]["icon_url"] = self._footer_icon
110
+ if self._image:
111
+ embed["image"] = {"url": self._image}
112
+ if self._thumbnail:
113
+ embed["thumbnail"] = {"url": self._thumbnail}
114
+ if self._author_text:
115
+ embed["author"] = {"name": self._author_text}
116
+ if self._author_icon:
117
+ embed["author"]["icon_url"] = self._author_icon
118
+ if self._fields:
119
+ embed["fields"] = self._fields
120
+ if self._timestamp:
121
+ embed["timestamp"] = datetime.now(timezone.utc).isoformat()
122
+ return embed
123
+
124
+
125
+ class Parser:
126
+ def __init__(self, ctx, client):
127
+ self.ctx = ctx
128
+ self.client = client
129
+ self._start_time = time.monotonic()
130
+ self._embeds: list[EmbedBuilder] = [EmbedBuilder()]
131
+ self._extra_messages: list[dict] = []
132
+ self._reply: bool = False
133
+ self._reply_content: str | None = None
134
+ self._delete_after: float | None = None
135
+ self._components: list[dict] = []
136
+ self._cv2_pending: list = []
137
+ self._cv2_in_container: bool = False
138
+ self._split_cache: list[str] = []
139
+ self._ephemeral: bool = False
140
+ self._vars: dict[str, str] = {}
141
+
142
+ def _current_embed(self) -> EmbedBuilder:
143
+ return self._embeds[-1]
144
+
145
+ def _parse_color(self, value: str) -> int:
146
+ value = value.strip().lstrip("#")
147
+ try:
148
+ return int(value, 16)
149
+ except ValueError:
150
+ named = {
151
+ "red": 0xFF0000, "green": 0x00FF00, "blue": 0x0000FF,
152
+ "yellow": 0xFFFF00, "orange": 0xFF8000, "purple": 0x800080,
153
+ "pink": 0xFFC0CB, "white": 0xFFFFFF, "black": 0x000000,
154
+ "cyan": 0x00FFFF, "blurple": 0x5865F2, "gray": 0x808080,
155
+ }
156
+ return named.get(value.lower(), 0x2B2D31)
157
+
158
+ async def evaluate(self, code: str) -> tuple[str, list[dict], list[dict]]:
159
+ result = await self._eval(code)
160
+ embeds = [eb.to_dict() for eb in self._embeds if eb.has_data()]
161
+ return result.strip(), embeds, self._extra_messages
162
+
163
+ async def _eval(self, code: str) -> str:
164
+ functions = _find_functions(code)
165
+ if not functions:
166
+ return code
167
+
168
+ result = code
169
+ for start, end, fn_name, raw_args in reversed(functions):
170
+ if raw_args is not None:
171
+ if fn_name.lower() == "addcontainer":
172
+ self._cv2_in_container = True
173
+ evaluated_args_str = await self._eval_container_args(raw_args)
174
+ self._cv2_in_container = False
175
+ else:
176
+ evaluated_args_str = await self._eval(raw_args)
177
+ args = _split_args(evaluated_args_str)
178
+ else:
179
+ args = []
180
+
181
+ replacement = await self._resolve(fn_name.lower(), args, raw_args or "")
182
+ result = result[:start] + replacement + result[end:]
183
+
184
+ return result
185
+
186
+ async def _eval_container_args(self, code: str) -> str:
187
+ functions = _find_functions(code)
188
+ if not functions:
189
+ return code
190
+
191
+ result = code
192
+ for start, end, fn_name, raw_args in reversed(functions):
193
+ if raw_args is not None:
194
+ evaluated_args_str = await self._eval(raw_args)
195
+ args = _split_args(evaluated_args_str)
196
+ else:
197
+ args = []
198
+
199
+ replacement = await self._resolve(fn_name.lower(), args, raw_args or "")
200
+ result = result[:start] + replacement + result[end:]
201
+
202
+ return result
203
+
204
+ async def _resolve(self, fn: str, args: list[str], raw: str) -> str:
205
+ for handler in [
206
+ handle_utility, handle_user, handle_embed, handle_message,
207
+ handle_components, handle_componentsv2, handle_interaction, handle_logic
208
+ ]:
209
+ result = await handler(fn, args, raw, self)
210
+ if result is not None:
211
+ return result
212
+
213
+ if raw:
214
+ return f"${fn}[{raw}]"
215
+ return f"${fn}"
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: cordscript
3
+ Version: 0.1.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
+ Dynamic: author
13
+ Dynamic: author-email
14
+ Dynamic: classifier
15
+ Dynamic: requires-dist
16
+ Dynamic: requires-python
@@ -0,0 +1,19 @@
1
+ README.md
2
+ setup.py
3
+ cordscript/__init__.py
4
+ cordscript/client.py
5
+ cordscript/parser.py
6
+ cordscript.egg-info/PKG-INFO
7
+ cordscript.egg-info/SOURCES.txt
8
+ cordscript.egg-info/dependency_links.txt
9
+ cordscript.egg-info/requires.txt
10
+ cordscript.egg-info/top_level.txt
11
+ cordscript/functions/__init__.py
12
+ cordscript/functions/components.py
13
+ cordscript/functions/componentsv2.py
14
+ cordscript/functions/embed.py
15
+ cordscript/functions/interaction.py
16
+ cordscript/functions/lógico.py
17
+ cordscript/functions/message.py
18
+ cordscript/functions/ultility.py
19
+ cordscript/functions/user.py
@@ -0,0 +1,2 @@
1
+ discord.py
2
+ aiohttp
@@ -0,0 +1 @@
1
+ cordscript
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="cordscript",
5
+ version="0.1.0",
6
+ description="",
7
+ author="Arimo",
8
+ author_email="alysonk078@gmail.com",
9
+ url="",
10
+ python_requires=">=3.10",
11
+ packages=find_packages(),
12
+ install_requires=[
13
+ "discord.py",
14
+ "aiohttp",
15
+ ],
16
+ classifiers=[
17
+ "Programming Language :: Python :: 3.10",
18
+ "License :: OSI Approved :: MIT License",
19
+ ],
20
+ )