cordscript 0.1.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.
- cordscript/__init__.py +5 -0
- cordscript/client.py +306 -0
- cordscript/functions/__init__.py +19 -0
- cordscript/functions/components.py +41 -0
- cordscript/functions/componentsv2.py +144 -0
- cordscript/functions/embed.py +59 -0
- cordscript/functions/interaction.py +48 -0
- cordscript/functions/l/303/263gico.py +34 -0
- cordscript/functions/message.py +45 -0
- cordscript/functions/ultility.py +162 -0
- cordscript/functions/user.py +61 -0
- cordscript/parser.py +215 -0
- cordscript-0.1.0.dist-info/METADATA +16 -0
- cordscript-0.1.0.dist-info/RECORD +16 -0
- cordscript-0.1.0.dist-info/WHEEL +5 -0
- cordscript-0.1.0.dist-info/top_level.txt +1 -0
cordscript/__init__.py
ADDED
cordscript/client.py
ADDED
|
@@ -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
|
cordscript/parser.py
ADDED
|
@@ -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,16 @@
|
|
|
1
|
+
cordscript/__init__.py,sha256=j0CRkRbGgmAVjDX2MMX3uYxnndD27rXb8L7n0RZjQ4k,108
|
|
2
|
+
cordscript/client.py,sha256=Z7KWnldTntkvyxyGrRQ3Ecy4uxQdQ4kReqbxf2oP8Ek,11926
|
|
3
|
+
cordscript/parser.py,sha256=BhHJnmcNzXswrfnbwRczdtlLUODNq8FQL9naz31DfDE,6955
|
|
4
|
+
cordscript/functions/__init__.py,sha256=BoiR31KqV6bvXks-AVOwGBuaVO5tNv3SYhL5w0G-V84,494
|
|
5
|
+
cordscript/functions/components.py,sha256=qQX5F4y7_odzgdYC3J_CvP9qcXncO2bBOMQTsXgu5Rg,1464
|
|
6
|
+
cordscript/functions/componentsv2.py,sha256=2f9EmjJDGYTo4EYkpcFKPeTWldJjXR3PRRJdaWxBaa8,4875
|
|
7
|
+
cordscript/functions/embed.py,sha256=a11I785o_Rx6vQBUxrnZegBHMYXGuKEY39bVezEY_YU,1907
|
|
8
|
+
cordscript/functions/interaction.py,sha256=kAqmSZENmttOqyV-m3hcDVWdJzkbwEqmBnVr1pl6fao,1449
|
|
9
|
+
cordscript/functions/lógico.py,sha256=-0eGlA8r40nX_4EviXbEtXRIwriF5m-CKijharBBgsA,1121
|
|
10
|
+
cordscript/functions/message.py,sha256=V3uowZZwUve2RFLskCpfaRHBSZaK0vxRDqCFyLfGnxs,1578
|
|
11
|
+
cordscript/functions/ultility.py,sha256=mGa3Gz2dz5Bd1e3E97VPG9ordHyBomXoOUYknxKBak4,5309
|
|
12
|
+
cordscript/functions/user.py,sha256=jXziCray2SRqqZsapORf_CPqzn4xSrC1HgoOoCJQN9c,2142
|
|
13
|
+
cordscript-0.1.0.dist-info/METADATA,sha256=u3czKTMDi0tyQ5fwphzNflQ4gXqWDKoGW0EjgsbeHas,396
|
|
14
|
+
cordscript-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
15
|
+
cordscript-0.1.0.dist-info/top_level.txt,sha256=OVTk5gwO8_wFSHGhXAEiEI6AwUhGf6xWGya5YcuemlM,11
|
|
16
|
+
cordscript-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cordscript
|