dshellInterpreter 0.2.21.7__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 (34) hide show
  1. Dshell/DISCORD_COMMANDS/__init__.py +7 -0
  2. Dshell/DISCORD_COMMANDS/dshell_channel.py +612 -0
  3. Dshell/DISCORD_COMMANDS/dshell_interaction.py +89 -0
  4. Dshell/DISCORD_COMMANDS/dshell_member.py +274 -0
  5. Dshell/DISCORD_COMMANDS/dshell_message.py +444 -0
  6. Dshell/DISCORD_COMMANDS/dshell_pastbin.py +28 -0
  7. Dshell/DISCORD_COMMANDS/dshell_role.py +128 -0
  8. Dshell/DISCORD_COMMANDS/utils/__init__.py +8 -0
  9. Dshell/DISCORD_COMMANDS/utils/utils_global.py +155 -0
  10. Dshell/DISCORD_COMMANDS/utils/utils_list.py +109 -0
  11. Dshell/DISCORD_COMMANDS/utils/utils_member.py +30 -0
  12. Dshell/DISCORD_COMMANDS/utils/utils_message.py +78 -0
  13. Dshell/DISCORD_COMMANDS/utils/utils_permissions.py +94 -0
  14. Dshell/DISCORD_COMMANDS/utils/utils_string.py +157 -0
  15. Dshell/DISCORD_COMMANDS/utils/utils_thread.py +35 -0
  16. Dshell/_DshellInterpreteur/__init__.py +4 -0
  17. Dshell/_DshellInterpreteur/cached_messages.py +4 -0
  18. Dshell/_DshellInterpreteur/dshell_arguments.py +74 -0
  19. Dshell/_DshellInterpreteur/dshell_interpreter.py +671 -0
  20. Dshell/_DshellInterpreteur/errors.py +8 -0
  21. Dshell/_DshellParser/__init__.py +2 -0
  22. Dshell/_DshellParser/ast_nodes.py +675 -0
  23. Dshell/_DshellParser/dshell_parser.py +408 -0
  24. Dshell/_DshellTokenizer/__init__.py +4 -0
  25. Dshell/_DshellTokenizer/dshell_keywords.py +193 -0
  26. Dshell/_DshellTokenizer/dshell_token_type.py +58 -0
  27. Dshell/_DshellTokenizer/dshell_tokenizer.py +146 -0
  28. Dshell/__init__.py +3 -0
  29. Dshell/_utils.py +1 -0
  30. dshellinterpreter-0.2.21.7.dist-info/METADATA +37 -0
  31. dshellinterpreter-0.2.21.7.dist-info/RECORD +34 -0
  32. dshellinterpreter-0.2.21.7.dist-info/WHEEL +5 -0
  33. dshellinterpreter-0.2.21.7.dist-info/licenses/LICENSE +21 -0
  34. dshellinterpreter-0.2.21.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,671 @@
1
+ from asyncio import sleep
2
+ from re import findall, sub, escape
3
+ from typing import TypeVar, Union, Any, Optional, Callable
4
+ from copy import deepcopy
5
+ from pycordViews import EasyModifiedViews
6
+ from pycordViews.views.errors import CustomIDNotFound
7
+
8
+ from discord import AutoShardedBot, Embed, Colour, PermissionOverwrite, Permissions, Guild, Member, Role, Message, Interaction, ButtonStyle
9
+ from discord.ui import Button
10
+ from discord.abc import PrivateChannel
11
+
12
+ from .errors import *
13
+ from .._DshellParser.ast_nodes import *
14
+ from ..DISCORD_COMMANDS.utils.utils_permissions import DshellPermissions
15
+ from .._DshellParser.dshell_parser import parse
16
+ from .._DshellParser.dshell_parser import to_postfix, print_ast
17
+ from .._DshellTokenizer.dshell_keywords import *
18
+ from .._DshellTokenizer.dshell_token_type import DshellTokenType as DTT
19
+ from .._DshellTokenizer.dshell_token_type import Token
20
+ from .._DshellTokenizer.dshell_tokenizer import DshellTokenizer
21
+ from .cached_messages import dshell_cached_messages
22
+ from .dshell_arguments import DshellArguments
23
+
24
+ All_nodes = TypeVar('All_nodes', IfNode, LoopNode, ElseNode, ElifNode, ArgsCommandNode, VarNode)
25
+ context = TypeVar('context', AutoShardedBot, Message, PrivateChannel, Interaction)
26
+ ButtonStyleValues: tuple = tuple(i.name for i in ButtonStyle)
27
+
28
+ class DshellInterpreteur:
29
+ """
30
+ Discord Dshell interpreter.
31
+ Make what you want with Dshell code to interact with Discord !
32
+ """
33
+
34
+ def __init__(self, code: str, ctx: context,
35
+ debug: bool = False,
36
+ vars: Optional[str] = None,
37
+ vars_env: Optional[dict[str, Any]] = None):
38
+ """
39
+ Interpreter Dshell code
40
+ :param code: The code to interpret. Each line must end with a newline character, except SEPARATOR and SUB_SEPARATOR tokens.
41
+ :param ctx: The context in which the code is executed. It can be a Discord bot, a message, or a channel.
42
+ :param debug: If True, prints the AST of the code and put the ctx to None.
43
+ :param vars: Optional dictionary of variables to initialize in the interpreter's environment.
44
+ :param vars_env: Optional dictionary of additional environment variables to add to the interpreter's environment.
45
+
46
+ Note: __message_before__ (message content before edit) can be overwritten by vars_env parameter.
47
+ """
48
+ self.ast: list[ASTNode] = parse(DshellTokenizer(code).start(), StartNode([]))[0]
49
+ message = ctx.message if isinstance(ctx, Interaction) else ctx
50
+ self.env: dict[str, Any] = {
51
+ '__ret__': None, # environment variables, '__ret__' is used to store the return value of commands
52
+
53
+ '__author__': message.author.id,
54
+ '__author_name__': message.author.name,
55
+ '__author_display_name__': message.author.display_name,
56
+ '__author_avatar__': message.author.display_avatar.url if message.author.display_avatar else None,
57
+ '__author_discriminator__': message.author.discriminator,
58
+ '__author_bot__': message.author.bot,
59
+ '__author_nick__': message.author.nick if hasattr(message.author, 'nick') else None,
60
+ '__author_id__': message.author.id,
61
+ '__author_add_reaction__': None, # Can be overwritten by add vars_env parameter to get the author on message add event reaction
62
+ '__author_remove_reaction__': None, # Can be overwritten by add vars_env parameter to get the author on message remove event reaction
63
+
64
+ '__message__': message.content,
65
+ '__message_content__': message.content,
66
+ '__message_id__': message.id,
67
+ '__message_author__': message.author.id,
68
+ '__message_before__': message.content, # same as __message__, but before edit. Can be overwritten by add vars_env parameter
69
+ '__message_created_at__': str(message.created_at),
70
+ '__message_edited_at__': str(message.edited_at),
71
+ '__message_reactions__': ListNode([str(reaction.emoji) for reaction in message.reactions]),
72
+ '__message_add_reaction__': None, # Can be overwritten by add vars_env parameter to get the reaction added on message add event reaction
73
+ '__message_remove_reaction__': None, # Can be overwritten by add vars_env parameter to get the reaction removed on message remove event reaction
74
+ '__message_url__': message.jump_url if hasattr(message, 'jump_url') else None,
75
+ '__last_message__': message.channel.last_message_id,
76
+
77
+ '__channel__': message.channel.id,
78
+ '__channel_name__': message.channel.name,
79
+ '__channel_type__': message.channel.type.name if hasattr(message.channel, 'type') else None,
80
+ '__channel_id__': message.channel.id,
81
+ '__private_channel__': isinstance(message.channel, PrivateChannel),
82
+
83
+ '__guild__': message.channel.guild.id,
84
+ '__guild_name__': message.channel.guild.name,
85
+ '__guild_id__': message.channel.guild.id,
86
+ '__guild_members__': ListNode([member.id for member in message.channel.guild.members]),
87
+ '__guild_member_count__': message.channel.guild.member_count,
88
+ '__guild_icon__': message.channel.guild.icon.url if message.channel.guild.icon else None,
89
+ '__guild_owner_id__': message.channel.guild.owner_id,
90
+ '__guild_description__': message.channel.guild.description,
91
+ '__guild_roles__': ListNode([role.id for role in message.channel.guild.roles]),
92
+ '__guild_roles_count__': len(message.channel.guild.roles),
93
+ '__guild_emojis__': ListNode([emoji.id for emoji in message.channel.guild.emojis]),
94
+ '__guild_emojis_count__': len(message.channel.guild.emojis),
95
+ '__guild_channels__': ListNode([channel.id for channel in message.channel.guild.channels]),
96
+ '__guild_text_channels__': ListNode([channel.id for channel in message.channel.guild.text_channels]),
97
+ '__guild_voice_channels__': ListNode([channel.id for channel in message.channel.guild.voice_channels]),
98
+ '__guild_categories__': ListNode([channel.id for channel in message.channel.guild.categories]),
99
+ '__guild_stage_channels__': ListNode([channel.id for channel in message.channel.guild.stage_channels]),
100
+ '__guild_forum_channels__': ListNode([channel.id for channel in message.channel.guild.forum_channels]),
101
+ '__guild_channels_count__': len(message.channel.guild.channels),
102
+
103
+ } if message is not None and not debug else {'__ret__': None} # {} is used in debug mode, when ctx is None
104
+ if vars_env is not None: # add the variables to the environment
105
+ self.env.update(vars_env)
106
+ self.vars = vars or ''
107
+ self.ctx: context = ctx
108
+ dshell_cached_messages.set(dict()) # save all messages view in the current scoop
109
+ if debug:
110
+ print_ast(self.ast)
111
+
112
+ async def execute(self, ast: Optional[list[All_nodes]] = None):
113
+ """
114
+ Executes the abstract syntax tree (AST) generated from the Dshell code.
115
+
116
+ This asynchronous method traverses and interprets each node in the AST, executing commands,
117
+ handling control flow structures (such as if, elif, else, and loops), managing variables,
118
+ and interacting with Discord through the provided context. It supports command execution,
119
+ variable assignment, sleep operations, and permission handling, among other features.
120
+
121
+ :param ast: Optional list of AST nodes to execute. If None, uses the interpreter's main AST.
122
+ :raises RuntimeError: If an EndNode is encountered, indicating execution should be stopped.
123
+ :raises Exception: If sleep duration is out of allowed bounds.
124
+ """
125
+ if ast is None:
126
+ ast = self.ast
127
+
128
+ for node in ast:
129
+
130
+ if isinstance(node, StartNode):
131
+ await self.execute(node.body)
132
+
133
+ if isinstance(node, CommandNode):
134
+ result = await call_function(dshell_commands[node.name], node.body, self)
135
+ self.env[f'__{node.name}__'] = result # return value of the command
136
+ self.env['__ret__'] = result # global return variable for all commands
137
+
138
+ elif isinstance(node, ParamNode):
139
+ params = await get_params(node, self)
140
+ self.env.update(params) # update the environment
141
+
142
+ elif isinstance(node, IfNode):
143
+ elif_valid = False
144
+ if await eval_expression(node.condition, self):
145
+ await self.execute(node.body)
146
+ continue
147
+ elif node.elif_nodes:
148
+
149
+ for i in node.elif_nodes:
150
+ if await eval_expression(i.condition, self):
151
+ await self.execute(i.body)
152
+ elif_valid = True
153
+ break
154
+
155
+ if not elif_valid and node.else_body is not None:
156
+ await self.execute(node.else_body.body)
157
+
158
+ elif isinstance(node, LoopNode):
159
+ self.env[node.variable.name.value] = 0
160
+ for i in DshellIterator(await eval_expression(node.variable.body, self)):
161
+ self.env[node.variable.name.value] = i
162
+ c = deepcopy(node.body)
163
+ await self.execute(c)
164
+ del c
165
+
166
+ elif isinstance(node, VarNode):
167
+
168
+ first_node = node.body[0]
169
+ if isinstance(first_node, IfNode):
170
+ self.env[node.name.value] = await eval_expression_inline(first_node, self)
171
+
172
+ elif isinstance(first_node, EmbedNode):
173
+ # rebuild the embed if it already exists
174
+ if node.name.value in self.env and isinstance(self.env[node.name.value], Embed):
175
+ self.env[node.name.value] = await rebuild_embed(self.env[node.name.value], first_node.body, first_node.fields, self)
176
+ else:
177
+ self.env[node.name.value] = await build_embed(first_node.body, first_node.fields, self)
178
+
179
+ elif isinstance(first_node, PermissionNode):
180
+ # rebuild the permissions if it already exists
181
+ if node.name.value in self.env and isinstance(self.env[node.name.value], dict):
182
+ self.env[node.name.value].update(await build_permission(first_node.body, self))
183
+ else:
184
+ self.env[node.name.value] = await build_permission(first_node.body, self)
185
+
186
+ elif isinstance(first_node, UiNode):
187
+ # rebuild the UI if it already exists
188
+ if node.name.value in self.env and isinstance(self.env[node.name.value], EasyModifiedViews):
189
+ self.env[node.name.value] = await rebuild_ui(first_node, self.env[node.name.value], self)
190
+ else:
191
+ self.env[node.name.value] = await build_ui(first_node, self)
192
+
193
+ else:
194
+ self.env[node.name.value] = await eval_expression(node.body, self)
195
+
196
+ elif isinstance(node, SleepNode):
197
+ sleep_time = await eval_expression(node.body, self)
198
+ if sleep_time > 3600:
199
+ raise Exception(f"Sleep time is too long! ({sleep_time} seconds) - maximum is 3600 seconds)")
200
+ elif sleep_time < 1:
201
+ raise Exception(f"Sleep time is too short! ({sleep_time} seconds) - minimum is 1 second)")
202
+
203
+ await sleep(sleep_time)
204
+
205
+
206
+ elif isinstance(node, EndNode):
207
+ if await self.eval_data_token(node.error_message):
208
+ raise RuntimeError("Execution stopped - EndNode encountered")
209
+ else:
210
+ raise DshellInterpreterStopExecution()
211
+
212
+ async def eval_data_token(self, token: Token):
213
+ """
214
+ Eval a data token and returns its value in Python.
215
+ :param token: The token to evaluate.
216
+ """
217
+
218
+ if not hasattr(token, 'type'):
219
+ return token
220
+
221
+ if token.type in (DTT.INT, DTT.MENTION):
222
+ return int(token.value)
223
+ elif token.type == DTT.FLOAT:
224
+ return float(token.value)
225
+ elif token.type == DTT.BOOL:
226
+ return token.value.lower() == "true"
227
+ elif token.type == DTT.NONE:
228
+ return None
229
+ elif token.type == DTT.LIST:
230
+ return ListNode(
231
+ [await self.eval_data_token(tok) for tok in token.value]) # token.value contient déjà une liste de Token
232
+ elif token.type == DTT.IDENT:
233
+ if token.value in self.env.keys():
234
+ return self.env[token.value]
235
+ return token.value
236
+ elif token.type == DTT.EVAL_GROUP:
237
+ await self.execute(parse([token.value], StartNode([]))[0]) # obliger de parser car ce il n'est pas dejà un AST
238
+ return self.env['__ret__']
239
+ elif token.type == DTT.STR:
240
+ for match in findall(rf"\$({'|'.join(self.env.keys())})", token.value):
241
+ token.value = token.value.replace('$' + match, str(self.env[match]))
242
+ return token.value
243
+ else:
244
+ return token.value # fallback
245
+
246
+
247
+ async def get_params(node: ParamNode, interpreter: DshellInterpreteur) -> dict[str, Any]:
248
+ """
249
+ Get the parameters from a ParamNode.
250
+ :param node: The ParamNode to get the parameters from.
251
+ :param interpreter: The Dshell interpreter instance.
252
+ :return: A dictionary of parameters.
253
+ """
254
+ def remplacer(match) -> str:
255
+ spacial_char = match.group(1)
256
+ if spacial_char:
257
+ return ''
258
+ return match.group(4)
259
+
260
+ variables = interpreter.vars
261
+ regrouped_parameters: DshellArguments = await regroupe_commandes(node.body, interpreter)
262
+
263
+ _ = DshellTokenizer(variables).start()
264
+ regrouped_variables = await regroupe_commandes(_[0] if _ else tuple(), interpreter)
265
+
266
+ already_modified = set()
267
+ variables_non_specified_parameters = regrouped_variables.parameters.pop('*', None).value # remove non-specified parameters
268
+
269
+ for param_name, param_data in regrouped_variables.parameters.items():
270
+ regrouped_parameters.update_parameter(param_name, param_data)
271
+ variables = sub(rf"--([*']?)({escape(param_name)})\s+(.*)\s*?(.*)$", remplacer, variables, count=1)
272
+ already_modified.add(param_name)
273
+
274
+ index_variable = 0
275
+ for var in regrouped_parameters.parameters.keys():
276
+ if var not in already_modified:
277
+
278
+ parameter_type = regrouped_parameters.get_parameter(var).type
279
+
280
+ if parameter_type == DTT.PARAMETER and index_variable < len(variables_non_specified_parameters):
281
+ regrouped_parameters.set_parameter(var, variables_non_specified_parameters[index_variable], parameter_type) # variables_post_regrouped[index_variable] n'est pas un token donc impossible de l'évaluer ! pose problème dans les commandes qui requière autre chose que des str
282
+ index_variable += 1
283
+
284
+ elif parameter_type == DTT.STR_PARAMETER:
285
+ variables_post_regrouped: list[str] = variables.strip().split(' ') if variables else [] # set uniquement pour les paramètres full str
286
+ regrouped_parameters.set_parameter(var, ' '.join(variables_post_regrouped[index_variable:]), parameter_type)
287
+ break
288
+
289
+ elif parameter_type == DTT.PARAMETERS:
290
+ regrouped_parameters.set_parameter(var, ListNode(variables_non_specified_parameters[index_variable:]), parameter_type)
291
+ break
292
+
293
+ for param_name, param_data in regrouped_parameters.parameters.items():
294
+ if param_data.obligatory and param_data.value == '*':
295
+ raise Exception(f"Parameter '{param_name}' is obligatory but not specified!")
296
+
297
+ x = regrouped_parameters.get_dict_parameters()
298
+ x.pop('*', None)
299
+ print(x)
300
+ return x
301
+
302
+
303
+ async def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) -> Token:
304
+ """
305
+ Eval a conditional expression inline.
306
+ :param if_node: The IfNode to evaluate.
307
+ :param interpreter: The Dshell interpreter instance.
308
+ """
309
+ if await eval_expression(if_node.condition, interpreter):
310
+ return await eval_expression(if_node.body, interpreter)
311
+ else:
312
+ return await eval_expression(if_node.else_body.body, interpreter)
313
+
314
+
315
+ async def eval_expression(tokens: list[Token], interpreter: DshellInterpreteur) -> Any:
316
+ """
317
+ Evaluates an arithmetic and logical expression.
318
+ :param tokens: A list of tokens representing the expression.
319
+ :param interpreter: The Dshell interpreter instance.
320
+ """
321
+ postfix = to_postfix(tokens, interpreter)
322
+ stack = []
323
+
324
+ for token in postfix:
325
+
326
+ if token.type in {DTT.INT, DTT.FLOAT, DTT.BOOL, DTT.STR, DTT.LIST, DTT.IDENT, DTT.EVAL_GROUP}:
327
+ stack.append(await interpreter.eval_data_token(token))
328
+
329
+ elif token.type in (DTT.MATHS_OPERATOR, DTT.LOGIC_OPERATOR, DTT.LOGIC_WORD_OPERATOR):
330
+ op = token.value
331
+
332
+ if op == "not":
333
+ a = stack.pop()
334
+ result = dshell_operators[op][0](a)
335
+
336
+ else:
337
+ b = stack.pop()
338
+ try:
339
+ a = stack.pop()
340
+ except IndexError:
341
+ if op == "-":
342
+ a = 0
343
+ else:
344
+ raise SyntaxError(f"Invalid expression: {op} operator requires two operands, but only one was found.")
345
+
346
+ result = dshell_operators[op][0](a, b)
347
+
348
+ stack.append(result)
349
+
350
+ else:
351
+ raise SyntaxError(f"Unexpected token type: {token.type} - {token.value}")
352
+
353
+ if len(stack) != 1:
354
+ raise SyntaxError("Invalid expression: stack should contain exactly one element after evaluation.")
355
+
356
+ return stack[0]
357
+
358
+
359
+ async def call_function(function: Callable, args: ArgsCommandNode, interpreter: DshellInterpreteur):
360
+ """
361
+ Call the function with the given arguments.
362
+ It can be an async function !
363
+ :param function: The function to call.
364
+ :param args: The arguments to pass to the function.
365
+ :param interpreter: The Dshell interpreter instance.
366
+ """
367
+ reformatted = await regroupe_commandes(args.body, interpreter)
368
+
369
+ args = reformatted.get_non_specified_parameters() # remove non-specified parameters from dict parameters
370
+ kwargs = reformatted.get_dict_parameters()
371
+ kwargs.pop('*', None)
372
+
373
+ args.insert(0, interpreter.ctx) # add the context as first argument
374
+
375
+ return await function(*args, **kwargs)
376
+
377
+
378
+ async def regroupe_commandes(body: list[Token], interpreter: DshellInterpreteur, normalise: bool = False) -> DshellArguments:
379
+ """
380
+ Groups the command arguments in the form of a python dictionary.
381
+ Note that you can specify the parameter you wish to pass via -- followed by the parameter name. But this is not mandatory!
382
+ Non-mandatory parameters will be stored in a list in the form of tokens with the key \`*\`.
383
+ The others, having been specified via a separator, will be in the form of a list of tokens with the IDENT token as key, following the separator for each argument.
384
+ If two parameters have the same name, the last one will overwrite the previous one.
385
+ To accept duplicates, use the SUB_SEPARATOR (~~) to create a sub-dictionary for parameters with the same name (sub-dictionary is added to the list returned).
386
+
387
+ :param body: The list of tokens to group.
388
+ :param interpreter: The Dshell interpreter instance.
389
+ :param normalise: If True, normalises the arguments (make value lowercase).
390
+ """
391
+ # tokens to return
392
+
393
+ instance_dhsell_arguments = DshellArguments()
394
+ index = 0
395
+ n = len(body)
396
+
397
+ while index < n:
398
+
399
+ if normalise and hasattr(body[index], 'value') and isinstance(body[index].value, str):
400
+ body[index].value = body[index].value.lower()
401
+
402
+ # If the current token is the last one and is a parameter marker, add it with empty value
403
+ if index == n - 1 and body[index].type in (DTT.PARAMETER, DTT.STR_PARAMETER, DTT.PARAMETERS):
404
+ if body[index].type == DTT.PARAMETER:
405
+ instance_dhsell_arguments.set_parameter(body[index].value, '', DTT.PARAMETER)
406
+ elif body[index].type == DTT.STR_PARAMETER:
407
+ instance_dhsell_arguments.set_parameter(body[index].value, '', DTT.STR_PARAMETER)
408
+ else: # DTT.PARAMETERS
409
+ instance_dhsell_arguments.set_parameter(body[index].value, ListNode([]), DTT.PARAMETERS)
410
+ index += 1
411
+ continue
412
+
413
+ if body[index].type == DTT.PARAMETER:
414
+
415
+ value = ''
416
+ current_index = index
417
+ while (index + 1) < n and body[index + 1].type not in (DTT.PARAMETER, DTT.STR_PARAMETER, DTT.PARAMETERS):
418
+
419
+ value = await interpreter.eval_data_token(body[index + 1])
420
+ index += 1
421
+ break
422
+
423
+ instance_dhsell_arguments.set_parameter(body[current_index].value, value, DTT.PARAMETER, obligatory=value == '*')
424
+ index += 1
425
+
426
+ elif body[index].type == DTT.STR_PARAMETER:
427
+
428
+ final_argument = ''
429
+ current_index = index
430
+
431
+ while (index + 1) < n and body[index + 1].type not in (DTT.PARAMETER, DTT.STR_PARAMETER, DTT.PARAMETERS):
432
+
433
+ final_argument += body[index + 1].value + ' '
434
+ index += 1
435
+ instance_dhsell_arguments.set_parameter(body[current_index].value, final_argument, type_=DTT.STR_PARAMETER)
436
+
437
+ index += 1
438
+
439
+ elif body[index].type == DTT.PARAMETERS:
440
+
441
+ list_parameters = []
442
+ current_index = index
443
+ while (index + 1) < n and body[index + 1].type not in (DTT.PARAMETER, DTT.STR_PARAMETER, DTT.PARAMETERS):
444
+
445
+ list_parameters.append(await interpreter.eval_data_token(body[index + 1]))
446
+ index += 1
447
+ instance_dhsell_arguments.set_parameter(body[current_index].value, ListNode(list_parameters), type_=DTT.PARAMETERS)
448
+
449
+ index += 1
450
+
451
+ else:
452
+ instance_dhsell_arguments.add_non_specified_parameters(await interpreter.eval_data_token(body[index]))
453
+ index += 1
454
+
455
+ return instance_dhsell_arguments
456
+
457
+
458
+ async def build_embed_args(body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> tuple[dict, list[dict]]:
459
+ """
460
+ Builds the arguments for an embed from the command information.
461
+ """
462
+ regrouped_parameters = await regroupe_commandes(body, interpreter)
463
+ args_main_embed: dict[str, list[Any]] = regrouped_parameters.get_dict_parameters()
464
+ args_main_embed.pop('*') # remove unspecified parameters for the embed
465
+ args_main_embed: dict[str, Token] # specify what it contains from now on
466
+
467
+ args_fields: list[dict[str, Token]] = []
468
+ for field in fields: # do the same for the fields
469
+ y = await regroupe_commandes(field.body, interpreter)
470
+ args_field = y.get_dict_parameters()
471
+ args_field.pop('*')
472
+ args_field: dict[str, Token]
473
+ args_fields.append(args_field)
474
+
475
+ if 'color' in args_main_embed:
476
+ args_main_embed['color'] = build_colour(args_main_embed['color']) # convert color to Colour object or int
477
+
478
+ return args_main_embed, args_fields
479
+
480
+ async def build_embed(body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> Embed:
481
+ """
482
+ Builds an embed from the command information.
483
+ """
484
+
485
+ args_main_embed, args_fields = await build_embed_args(body, fields, interpreter)
486
+ embed = Embed(**args_main_embed) # build the main embed
487
+ for field in args_fields:
488
+ embed.add_field(**field) # add all fields
489
+
490
+ return embed
491
+
492
+ async def rebuild_embed(embed: Embed, body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> Embed:
493
+ """
494
+ Rebuilds an embed from an existing embed and the command information.
495
+ """
496
+ args_main_embed, args_fields = await build_embed_args(body, fields, interpreter)
497
+
498
+ for key, value in args_main_embed.items():
499
+ if key == 'color':
500
+ embed.colour = value
501
+ else:
502
+ setattr(embed, key, value)
503
+
504
+ if args_fields:
505
+ embed.clear_fields()
506
+ for field in args_fields:
507
+ embed.add_field(**field)
508
+
509
+ return embed
510
+
511
+ def build_colour(color: Union[int, ListNode]) -> Union[Colour, int]:
512
+ """
513
+ Builds a Colour object from an integer or a ListNode.
514
+ :param color: The color to build.
515
+ :return: A Colour object.
516
+ """
517
+ if isinstance(color, int):
518
+ return color
519
+ elif isinstance(color, (ListNode, list)):
520
+ if not len(color) == 3:
521
+ raise ValueError(f"Color must be a list of 3 integers, not {len(color)} elements !")
522
+ return Colour.from_rgb(*color)
523
+ else:
524
+ raise TypeError(f"Color must be an integer or a ListNode, not {type(color)} !")
525
+
526
+ async def build_ui_parameters(ui_node: UiNode, interpreter: DshellInterpreteur):
527
+ """
528
+ Builds the parameters for a UI component from the UiNode.
529
+ Can accept buttons and select menus.
530
+ :param ui_node:
531
+ :param interpreter:
532
+ :return:
533
+ """
534
+ for ident_component in range(len(ui_node.buttons)):
535
+ regrouped_parameters = await regroupe_commandes(ui_node.buttons[ident_component].body, interpreter, normalise=True)
536
+ args_button: dict[str, list[Any]] = regrouped_parameters.get_dict_parameters()
537
+
538
+ code = args_button.pop('code', None)
539
+ style = args_button.pop('style', 'primary').lower()
540
+ custom_id = args_button.pop('custom_id', str(ident_component))
541
+
542
+ if not isinstance(custom_id, str):
543
+ raise TypeError(f"Button custom_id must be a string, not {type(custom_id)} !")
544
+
545
+ if style not in ButtonStyleValues:
546
+ raise ValueError(f"Button style must be one of {', '.join(ButtonStyleValues)}, not '{style}' !")
547
+
548
+ args_button['custom_id'] = custom_id
549
+ args_button['style'] = ButtonStyle[style]
550
+ args = args_button.pop('*', ())
551
+ yield args, args_button, code
552
+
553
+ async def build_ui(ui_node: UiNode, interpreter: DshellInterpreteur) -> EasyModifiedViews:
554
+ """
555
+ Builds a UI component from the UiNode.
556
+ Can accept buttons and select menus.
557
+ :param ui_node:
558
+ :param interpreter:
559
+ :return:
560
+ """
561
+ view = EasyModifiedViews()
562
+
563
+ async for args, args_button, code in build_ui_parameters(ui_node, interpreter):
564
+ b = Button(**args_button)
565
+
566
+ view.add_items(b)
567
+ view.set_callable(b.custom_id, _callable=ui_button_callback, data={'code': code})
568
+
569
+ return view
570
+
571
+ async def rebuild_ui(ui_node : UiNode, view: EasyModifiedViews, interpreter: DshellInterpreteur) -> EasyModifiedViews:
572
+ """
573
+ Rebuilds a UI component from an existing EasyModifiedViews.
574
+ :param view:
575
+ :param interpreter:
576
+ :return:
577
+ """
578
+ async for args, args_button, code in build_ui_parameters(ui_node, interpreter):
579
+ try:
580
+ ui = view.get_ui(args_button['custom_id'])
581
+ except CustomIDNotFound:
582
+ raise ValueError(f"Button with custom_id '{args_button['custom_id']}' not found in the view !")
583
+
584
+ ui.label = args_button.get('label', ui.label)
585
+ ui.style = args_button.get('style', ui.style)
586
+ ui.emoji = args_button.get('emoji', ui.emoji)
587
+ ui.disabled = args_button.get('disabled', ui.disabled)
588
+ ui.url = args_button.get('url', ui.url)
589
+ ui.row = args_button.get('row', ui.row)
590
+ new_code = code if code is not None else view.get_callable_data(args_button['custom_id'])['code']
591
+ view.set_callable(args_button['custom_id'], _callable=ui_button_callback, data={'code': args_button.get('code', code)})
592
+
593
+ return view
594
+
595
+
596
+ async def ui_button_callback(button: Button, interaction: Interaction, data: dict[str, Any]):
597
+ """
598
+ Callback for UI buttons.
599
+ Executes the code associated with the button.
600
+ :param button:
601
+ :param interaction:
602
+ :param data:
603
+ :return:
604
+ """
605
+ code = data.pop('code', None)
606
+ if code is not None:
607
+ local_env = {
608
+ '__ret__': None,
609
+ '__guild__': interaction.guild.name if interaction.guild else None,
610
+ '__channel__': interaction.channel.name if interaction.channel else None,
611
+ '__author__': interaction.user.name,
612
+ '__author_display_name__': interaction.user.display_name,
613
+ '__author_avatar__': interaction.user.display_avatar.url if interaction.user.display_avatar else None,
614
+ '__author_discriminator__': interaction.user.discriminator,
615
+ '__author_bot__': interaction.user.bot,
616
+ '__author_nick__': interaction.user.nick if hasattr(interaction.user, 'nick') else None,
617
+ '__author_id__': interaction.user.id,
618
+ '__message__': interaction.message.content if hasattr(interaction.message, 'content') else None,
619
+ '__message_id__': interaction.message.id if hasattr(interaction.message, 'id') else None,
620
+ '__channel_name__': interaction.channel.name if interaction.channel else None,
621
+ '__channel_type__': interaction.channel.type.name if hasattr(interaction.channel, 'type') else None,
622
+ '__channel_id__': interaction.channel.id if interaction.channel else None,
623
+ '__private_channel__': isinstance(interaction.channel, PrivateChannel),
624
+ }
625
+ local_env.update(data)
626
+ x = DshellInterpreteur(code, interaction, debug=False)
627
+ x.env.update(local_env)
628
+ await x.execute()
629
+ else:
630
+ await interaction.response.defer(invisible=True)
631
+
632
+ data.update({'code': code})
633
+
634
+ async def build_permission(body: list[Token], interpreter: DshellInterpreteur) -> dict[
635
+ Union[Member, Role], PermissionOverwrite]:
636
+ """
637
+ Builds a dictionary of PermissionOverwrite objects from the command information.
638
+ """
639
+ args_permissions: DshellArguments = await regroupe_commandes(body, interpreter, normalise=True)
640
+
641
+ print(args_permissions)
642
+
643
+ x = args_permissions.get_dict_parameters()
644
+ x.pop('*', None)
645
+
646
+ return DshellPermissions(x).get_permission_overwrite(interpreter.ctx.channel.guild)
647
+
648
+
649
+
650
+ class DshellIterator:
651
+ """
652
+ Used to transform anything into an iterable
653
+ """
654
+
655
+ def __init__(self, data):
656
+ self.data = data if isinstance(data, (str, list, ListNode)) else range(int(data))
657
+ self.current = 0
658
+
659
+ def __iter__(self):
660
+ return self
661
+
662
+ def __next__(self):
663
+ if self.current >= len(self.data):
664
+ self.current = 0
665
+ raise StopIteration
666
+
667
+ value = self.data[self.current]
668
+ self.current += 1
669
+ return value
670
+
671
+
@@ -0,0 +1,8 @@
1
+
2
+ class DshellInterpreterError(Exception):
3
+ """Base class for exceptions in this module."""
4
+ pass
5
+
6
+ class DshellInterpreterStopExecution(DshellInterpreterError):
7
+ """Exception raised to stop the execution of the interpreter."""
8
+ pass
@@ -0,0 +1,2 @@
1
+ from .ast_nodes import *
2
+ from .dshell_parser import *