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,155 @@
1
+ __all__ = [
2
+ "utils_len",
3
+ "utils_random",
4
+ "utils_get_name",
5
+ "utils_get_id",
6
+ "utils_get_roles",
7
+ ]
8
+
9
+ from discord import Message, Role, TextChannel, VoiceChannel, CategoryChannel, ForumChannel, Thread, Member, Guild
10
+ from discord.utils import get
11
+ from typing import Union, Optional
12
+ from enum import StrEnum
13
+ from random import random, choice
14
+
15
+
16
+ class DiscordType(StrEnum):
17
+ MEMBER = "member"
18
+ ROLE = "role"
19
+ TEXT_CHANNEL = "text_channel"
20
+ VOICE_CHANNEL = "voice_channel"
21
+ CATEGORY_CHANNEL = "category_channel"
22
+ FORUM_CHANNEL = "forum_channel"
23
+ THREAD = "thread"
24
+ UNKNOWN = "unknown"
25
+
26
+ def utils_what_discord_type_is(ctx: Union[Message, Guild], value: int) -> tuple[str, Union[Member, Role, TextChannel, VoiceChannel, CategoryChannel, ForumChannel, Thread, None]]:
27
+ """
28
+ Return an enum of what the value is (str, int, list, Member, Role, Channel, etc.)
29
+ :param ctx:
30
+ :param value:
31
+ :return:
32
+ """
33
+ guild = ctx if isinstance(ctx, Guild) else ctx.guild
34
+
35
+ if member := guild.get_member(value):
36
+ return DiscordType.MEMBER, member
37
+
38
+ elif role := guild.get_role(value):
39
+ return DiscordType.ROLE, role
40
+
41
+ elif (channel := guild.get_channel(value)) and isinstance(channel, TextChannel):
42
+ return DiscordType.TEXT_CHANNEL, channel
43
+
44
+ elif (channel := guild.get_channel(value)) and isinstance(channel, VoiceChannel):
45
+ return DiscordType.VOICE_CHANNEL, channel
46
+
47
+ elif (channel := guild.get_channel(value)) and isinstance(channel, CategoryChannel):
48
+ return DiscordType.CATEGORY_CHANNEL, channel
49
+
50
+ elif (channel := guild.get_channel(value)) and isinstance(channel, ForumChannel):
51
+ return DiscordType.FORUM_CHANNEL, channel
52
+
53
+ elif (channel := guild.get_channel(value)) and isinstance(channel, Thread):
54
+ return DiscordType.THREAD, channel
55
+ else:
56
+ return DiscordType.UNKNOWN, None
57
+
58
+ async def utils_len(ctx: Message, value):
59
+ """
60
+ Return the length of a list, or a string
61
+ :param value:
62
+ :return:
63
+ """
64
+ from ..._DshellParser.ast_nodes import ListNode
65
+ if not isinstance(value, (str, ListNode)):
66
+ raise TypeError(f"value must be a list or a string in len command, not {type(value)}")
67
+
68
+ return len(value)
69
+
70
+ async def utils_random(ctx: Message, value: Optional["ListNode"] = None):
71
+ """
72
+ Return a random element from a list, or a random integer between 0 and value
73
+ :param value:
74
+ :return:
75
+ """
76
+ from ..._DshellParser.ast_nodes import ListNode
77
+ if value is not None and not isinstance(value, ListNode):
78
+ raise TypeError(f"value must be a list in random command, not {type(value)}")
79
+
80
+ if value is None:
81
+ return random()
82
+ return choice(value)
83
+
84
+ async def utils_get_name(ctx : Message, value: int) -> Union[str, None]:
85
+ """
86
+ Return the name of a role, member, or channel.
87
+ If not found, return None.
88
+ :param value:
89
+ :return:
90
+ """
91
+
92
+ if not isinstance(value, int):
93
+ raise TypeError(f"value must be an int in name command, not {type(value)}")
94
+
95
+ guild = ctx.guild
96
+ result = None
97
+
98
+ if role := guild.get_role(value):
99
+ result = role.name
100
+
101
+ elif member := guild.get_member(value):
102
+ result = member.name
103
+
104
+ if channel := guild.get_channel(value) :
105
+ result = channel.name
106
+
107
+ return result
108
+
109
+ async def utils_get_id(ctx : Message, value: str) -> Union[int, None]:
110
+ """
111
+ Return the id of a role, member, or channel.
112
+ If not found, return None.
113
+ :param value:
114
+ :return:
115
+ """
116
+
117
+ if not isinstance(value, str):
118
+ raise TypeError(f"value must be a str in id command, not {type(value)}")
119
+
120
+ guild = ctx.guild
121
+ result = None
122
+
123
+ if role := get(guild.roles, name=value):
124
+ result = role.id
125
+
126
+ elif member := get(guild.members, name=value):
127
+ result = member.id
128
+
129
+ if channel := get(guild.channels, name=value) :
130
+ result = channel.id
131
+
132
+ return result
133
+
134
+ async def utils_get_roles(ctx: Message, value: int):
135
+ """
136
+ Return the roles of a member.
137
+ :param value:
138
+ :return:
139
+ """
140
+
141
+ if not isinstance(value, int):
142
+ raise TypeError(f"value must be an int in roles command, not {type(value)}")
143
+
144
+ guild = ctx.guild
145
+
146
+ what_is, member = utils_what_discord_type_is(ctx, value)
147
+
148
+ if what_is == DiscordType.UNKNOWN:
149
+ raise ValueError(f"{value} member not found in guild {guild.name}")
150
+
151
+ if what_is != DiscordType.MEMBER:
152
+ raise TypeError(f"value must be a member id in roles command, not {what_is}")
153
+
154
+ from ..._DshellParser.ast_nodes import ListNode
155
+ return ListNode([i.id for i in member.roles])
@@ -0,0 +1,109 @@
1
+ __all__ = [
2
+ "utils_list_add",
3
+ "utils_list_remove",
4
+ "utils_list_clear",
5
+ "utils_list_pop",
6
+ "utils_list_sort",
7
+ "utils_list_reverse",
8
+ "utils_list_get_value",
9
+ ]
10
+
11
+ async def utils_list_add(ctx, value: "ListNode", *elements):
12
+ """
13
+ Add an element to a list
14
+ :param value:
15
+ :param elements:
16
+ :return:
17
+ """
18
+ from ..._DshellParser.ast_nodes import ListNode
19
+ if not isinstance(value, ListNode):
20
+ raise TypeError("value must be a list in add command")
21
+
22
+ for elem in elements:
23
+ if isinstance(elem, ListNode):
24
+ value.extend(elem)
25
+ else:
26
+ value.add(elem)
27
+ return value
28
+
29
+ async def utils_list_remove(ctx, value: "ListNode", element, count: int = 1):
30
+ """
31
+ Remove an element from a list
32
+ :param value:
33
+ :param element:
34
+ :param count:
35
+ :return:
36
+ """
37
+ from ..._DshellParser.ast_nodes import ListNode
38
+ if not isinstance(value, ListNode):
39
+ raise TypeError("value must be a list in remove command")
40
+
41
+ value.remove(element, count)
42
+ return value
43
+
44
+ async def utils_list_clear(ctx, value: "ListNode"):
45
+ """
46
+ Clear a list
47
+ :param value:
48
+ :return:
49
+ """
50
+ from ..._DshellParser.ast_nodes import ListNode
51
+ if not isinstance(value, ListNode):
52
+ raise TypeError("value must be a list in clear command")
53
+ value.clear()
54
+ return value
55
+
56
+ async def utils_list_pop(ctx, value: "ListNode", index: int = -1):
57
+ """
58
+ Pop an element from a list
59
+ :param value:
60
+ :param index:
61
+ :return:
62
+ """
63
+ from ..._DshellParser.ast_nodes import ListNode
64
+ if not isinstance(value, ListNode):
65
+ raise TypeError("value must be a list in pop command")
66
+ if not isinstance(index, int):
67
+ raise TypeError("index must be an integer in pop command")
68
+ return value.pop(index)
69
+
70
+ async def utils_list_sort(ctx, value: "ListNode", reverse: bool = False):
71
+ """
72
+ Sort a list
73
+ :param value:
74
+ :param reverse:
75
+ :return:
76
+ """
77
+ from ..._DshellParser.ast_nodes import ListNode
78
+ if not isinstance(value, ListNode):
79
+ raise TypeError("value must be a list in sort command")
80
+ if not isinstance(reverse, bool):
81
+ raise TypeError("reverse must be a boolean in sort command")
82
+ value.sort(reverse=reverse)
83
+ return value
84
+
85
+ async def utils_list_reverse(ctx, value: "ListNode"):
86
+ """
87
+ Reverse a list
88
+ :param value:
89
+ :return:
90
+ """
91
+ from ..._DshellParser.ast_nodes import ListNode
92
+ if not isinstance(value, ListNode):
93
+ raise TypeError("value must be a list in reverse command")
94
+ value.reverse()
95
+ return value
96
+
97
+ async def utils_list_get_value(ctx, value: "ListNode", index: int = 0):
98
+ """
99
+ Get a value from a list
100
+ :param value:
101
+ :param index:
102
+ :return:
103
+ """
104
+ from ..._DshellParser.ast_nodes import ListNode
105
+ if not isinstance(value, ListNode):
106
+ raise TypeError("value must be a list in get command")
107
+ if not isinstance(index, int):
108
+ raise TypeError("index must be an integer in get command")
109
+ return value[index]
@@ -0,0 +1,30 @@
1
+ __all__ = [
2
+ "utils_has_permissions",
3
+ ]
4
+ from discord import Message, PermissionOverwrite
5
+
6
+ from .utils_global import utils_what_discord_type_is, DiscordType
7
+
8
+ async def utils_has_permissions(ctx: Message, member: int, permission: dict[None, PermissionOverwrite]) -> bool:
9
+ """
10
+ Return True if the member has the specified permissions.
11
+ :param member:
12
+ :param permission:
13
+ :return:
14
+ """
15
+
16
+ if not isinstance(member, int):
17
+ raise TypeError(f"member must be an int in has_perms command, not {type(member)}")
18
+
19
+ if not isinstance(permission, dict):
20
+ raise TypeError(f"permissions must be a permission bloc in has_perms command, not {type(permission)}")
21
+
22
+ if None not in permission:
23
+ raise ValueError(f"permissions must have simple 'allow' permission in has_perms command, not {permission.keys()}")
24
+
25
+ discord_type, member = utils_what_discord_type_is(ctx, member)
26
+
27
+ if discord_type != DiscordType.MEMBER:
28
+ raise ValueError(f"No member found with ID {member} in has_perms command.")
29
+
30
+ return (member.guild_permissions & permission[None].pair()[0]) == permission[None].pair()[0]
@@ -0,0 +1,78 @@
1
+ from discord import Message, PartialMessage, AllowedMentions
2
+ from typing import Union
3
+ from re import search
4
+
5
+ from ..._DshellInterpreteur.cached_messages import dshell_cached_messages
6
+
7
+ def utils_get_message(ctx: Message, message: Union[int, str]) -> Union[PartialMessage, Message]:
8
+ """
9
+ Returns the message object of the specified message ID or link.
10
+ Message is only available in the same server as the command and in the same channel.
11
+ If the message is a link, it must be in the format: https://discord.com/channels/{guild_id}/{channel_id}/{message_id}
12
+ """
13
+ cached_messages = dshell_cached_messages.get()
14
+
15
+ if isinstance(message, int):
16
+ if message in cached_messages:
17
+ return cached_messages[message]
18
+
19
+ cached_messages[message] = ctx.channel.get_partial_message(message)
20
+ dshell_cached_messages.set(cached_messages)
21
+ return cached_messages[message]
22
+
23
+ elif isinstance(message, str):
24
+ match = search(r'https://discord\.com/channels/(\d+)/(\d+)/(\d+)', message)
25
+ if not match:
26
+ raise Exception("Invalid message link format. Use a valid Discord message link.")
27
+ guild_id = int(match.group(1))
28
+ channel_id = int(match.group(2))
29
+ message_id = int(match.group(3))
30
+
31
+ if guild_id != ctx.guild.id:
32
+ raise Exception("The message must be from the same server as the command !")
33
+
34
+ cached_messages[message_id] = ctx.guild.get_channel(channel_id).get_partial_message(message_id)
35
+ dshell_cached_messages.set(cached_messages)
36
+ return cached_messages[message_id]
37
+
38
+ raise Exception(f"Message must be an integer or a string, not {type(message)} !")
39
+
40
+
41
+ def utils_autorised_mentions(global_mentions: bool = None,
42
+ everyone_mention: bool = True,
43
+ roles_mentions: bool = True,
44
+ users_mentions: bool = True,
45
+ reply_mention: bool = False) -> Union[bool, 'AllowedMentions']:
46
+ """
47
+ Returns the AllowedMentions object based on the provided parameters.
48
+ If global_mentions is set to True or False, it overrides all other parameters.
49
+ """
50
+
51
+ from discord import AllowedMentions
52
+
53
+ if global_mentions is not None and not isinstance(global_mentions, bool):
54
+ raise Exception(f'Mention parameter must be a boolean or None, not {type(global_mentions)} !')
55
+
56
+ if not isinstance(everyone_mention, bool):
57
+ raise Exception(f'Everyone mention parameter must be a boolean, not {type(everyone_mention)} !')
58
+
59
+ if not isinstance(roles_mentions, bool):
60
+ raise Exception(f'Roles mention parameter must be a boolean, not {type(roles_mentions)} !')
61
+
62
+ if not isinstance(users_mentions, bool):
63
+ raise Exception(f'Users mention parameter must be a boolean, not {type(users_mentions)} !')
64
+
65
+ if not isinstance(reply_mention, bool):
66
+ raise Exception(f'Reply mention parameter must be a boolean, not {type(reply_mention)} !')
67
+
68
+ if global_mentions is True:
69
+ return AllowedMentions.all()
70
+
71
+ elif global_mentions is False:
72
+ return AllowedMentions.none()
73
+
74
+ else:
75
+ return AllowedMentions(everyone=everyone_mention,
76
+ roles=roles_mentions,
77
+ users=users_mentions,
78
+ replied_user=reply_mention)
@@ -0,0 +1,94 @@
1
+ from typing import Union
2
+ from discord import Guild, Member, Role, Permissions, PermissionOverwrite, Message
3
+
4
+ from .utils_global import utils_what_discord_type_is, DiscordType
5
+
6
+ async def utils_update_permissions(ctx: Message,
7
+ permission1: dict[Union[Member, Role, None], PermissionOverwrite],
8
+ permission2: dict[Union[Member, Role, None], PermissionOverwrite]) -> dict:
9
+
10
+ if not isinstance(permission1, dict):
11
+ raise ValueError(f"permission1 must be a permission block, not {type(permission1).__name__}")
12
+
13
+ if not isinstance(permission2, dict):
14
+ raise ValueError(f"permission2 must be a permission block, not {type(permission2).__name__}")
15
+
16
+ permission1.update(permission2)
17
+
18
+ return permission1
19
+
20
+
21
+ class DshellPermissions:
22
+ def __init__(self, target: dict[str, list[int]]):
23
+ """
24
+ Creates a Dshell permissions object.
25
+ :param target: A dictionary containing parameters and their values.
26
+ Expected parameters: “allow”, “deny”, ‘members’, “roles”.
27
+ For “members” and “roles”, values must be ID ListNodes.
28
+ """
29
+ self.target: dict[str, Union["ListNode", int]] = target
30
+
31
+ @staticmethod
32
+ def get_member(guild, member_id: int) -> Member:
33
+ """
34
+ Return a Member object from a member ID.
35
+ :param member_id:
36
+ :return:
37
+ """
38
+ discord_type, instance = utils_what_discord_type_is(guild, member_id)
39
+ if discord_type == DiscordType.MEMBER:
40
+ return instance
41
+ raise ValueError(f"No member found with ID {member_id} in perm command.")
42
+
43
+ @staticmethod
44
+ def get_role(guild, role_id: int) -> Role:
45
+ """
46
+ Return a Role object from a role ID.
47
+ :param role_id:
48
+ :return:
49
+ """
50
+ discord_type, instance = utils_what_discord_type_is(guild, role_id)
51
+ if discord_type == DiscordType.ROLE:
52
+ return instance
53
+ raise ValueError(f"No role found with ID {role_id} in perm command.")
54
+
55
+ def get_permission_overwrite(self, guild: Guild) -> dict[Union[Member, Role, None], PermissionOverwrite]:
56
+ """
57
+ Returns a PermissionOverwrite object with member and role permissions.
58
+ If no members or roles are specified, it returns a PermissionOverwrite with None key.
59
+ :param guild: The Discord server
60
+ :return: A dictionary of PermissionOverwrite objects with members and roles as keys
61
+ """
62
+ from ..._DshellParser.ast_nodes import ListNode
63
+ permissions: dict[Union[Member, Role, None], PermissionOverwrite] = {}
64
+ target_keys = self.target.keys()
65
+
66
+ if 'members' in target_keys:
67
+ for member_id in (
68
+ self.target['members'] if isinstance(self.target['members'], ListNode) else [
69
+ self.target['members']]): # allow a single ID
70
+ member = self.get_member(guild, member_id)
71
+ permissions[member] = PermissionOverwrite.from_pair(
72
+ allow=Permissions(permissions=self.target.get('allow', 0)),
73
+ deny=Permissions(permissions=self.target.get('deny', 0))
74
+ )
75
+
76
+ elif 'roles' in target_keys:
77
+ for role_id in (
78
+ self.target['roles'] if isinstance(self.target['roles'], ListNode) else [
79
+ self.target['roles']]): # allow a single ID
80
+ if role_id == guild.id: # @everyone role
81
+ role = guild.default_role
82
+ else:
83
+ role = self.get_role(guild, role_id)
84
+ permissions[role] = PermissionOverwrite.from_pair(
85
+ allow=Permissions(permissions=self.target.get('allow', 0)),
86
+ deny=Permissions(permissions=self.target.get('deny', 0))
87
+ )
88
+ else:
89
+ permissions[None] = PermissionOverwrite.from_pair(
90
+ allow=Permissions(permissions=self.target.get('allow', 0)),
91
+ deny=Permissions(permissions=self.target.get('deny', 0))
92
+ )
93
+
94
+ return permissions
@@ -0,0 +1,157 @@
1
+ __all__ = [
2
+ "utils_split_string",
3
+ "utils_upper_string",
4
+ "utils_lower_string",
5
+ "utils_title_string",
6
+ "utils_strip_string",
7
+ "utils_replace_string",
8
+ "utils_regex_findall",
9
+ "utils_regex_sub",
10
+ "utils_regex_search"
11
+ ]
12
+
13
+ from discord import Message
14
+ from re import search, findall, sub
15
+
16
+ async def utils_split_string(ctx: Message, value: str, separator: str = ' ') -> "ListNode":
17
+ """
18
+ Split a string into a list of strings using the specified separator.
19
+ :param value:
20
+ :param separator:
21
+ :return:
22
+ """
23
+
24
+ if not isinstance(value, str):
25
+ raise TypeError(f"value must be a str in split command, not {type(value)}")
26
+
27
+ if not isinstance(separator, str):
28
+ raise TypeError(f"separator must be a str in split command, not {type(separator)}")
29
+
30
+ from ..._DshellParser.ast_nodes import ListNode
31
+
32
+ return ListNode(value.split(separator))
33
+
34
+ async def utils_upper_string(ctx: Message, value: str) -> str:
35
+ """
36
+ Convert a string to uppercase.
37
+ :param value:
38
+ :return:
39
+ """
40
+
41
+ if not isinstance(value, str):
42
+ raise TypeError(f"value must be a str in upper command, not {type(value)}")
43
+
44
+ return value.upper()
45
+
46
+ async def utils_lower_string(ctx: Message, value: str) -> str:
47
+ """
48
+ Convert a string to lowercase.
49
+ :param value:
50
+ :return:
51
+ """
52
+
53
+ if not isinstance(value, str):
54
+ raise TypeError(f"value must be a str in lower command, not {type(value)}")
55
+
56
+ return value.lower()
57
+
58
+ async def utils_title_string(ctx: Message, value: str) -> str:
59
+ """
60
+ Convert a string to title case.
61
+ :param value:
62
+ :return:
63
+ """
64
+
65
+ if not isinstance(value, str):
66
+ raise TypeError(f"value must be a str in title command, not {type(value)}")
67
+
68
+ return value.title()
69
+
70
+ async def utils_strip_string(ctx: Message, value: str) -> str:
71
+ """
72
+ Strip whitespace from the beginning and end of a string.
73
+ :param value:
74
+ :return:
75
+ """
76
+
77
+ if not isinstance(value, str):
78
+ raise TypeError(f"value must be a str in strip command, not {type(value)}")
79
+
80
+ return value.strip()
81
+
82
+ async def utils_replace_string(ctx: Message, value: str, old: str, new: str) -> str:
83
+ """
84
+ Replace all occurrences of old with new in a string.
85
+ :param value:
86
+ :param old:
87
+ :param new:
88
+ :return:
89
+ """
90
+
91
+ if not isinstance(value, str):
92
+ raise TypeError(f"value must be a str in replace command, not {type(value)}")
93
+
94
+ if not isinstance(old, str):
95
+ raise TypeError(f"old must be a str in replace command, not {type(old)}")
96
+
97
+ if not isinstance(new, str):
98
+ raise TypeError(f"new must be a str in replace command, not {type(new)}")
99
+
100
+ return value.replace(old, new)
101
+
102
+ async def utils_regex_findall(ctx: Message, regex: str, content: str = None) -> "ListNode":
103
+ """
104
+ Find all occurrences of a regex in a string.
105
+ :param regex:
106
+ :param content:
107
+ :return:
108
+ """
109
+
110
+ if not isinstance(regex, str):
111
+ raise Exception(f"Regex must be a string, not {type(regex)}!")
112
+
113
+ if content is not None and not isinstance(content, str):
114
+ raise Exception(f"Content must be a string, not {type(content)}!")
115
+
116
+ from ..._DshellParser.ast_nodes import ListNode
117
+
118
+ return ListNode(findall(regex, content if content is not None else ctx.content))
119
+
120
+
121
+ async def utils_regex_sub(ctx: Message, regex: str, replace: str, content: str = None) -> str:
122
+ """
123
+ Replace all occurrences of a regex in a string with a replacement string.
124
+ :param regex:
125
+ :param replace:
126
+ :param content:
127
+ :return:
128
+ """
129
+
130
+ if not isinstance(regex, str):
131
+ raise Exception(f"Regex must be a string, not {type(regex)}!")
132
+
133
+ if not isinstance(replace, str):
134
+ raise Exception(f"Replacement must be a string, not {type(replace)}!")
135
+
136
+ if content is not None and not isinstance(content, str):
137
+ raise Exception(f"Content must be a string, not {type(content)}!")
138
+
139
+ return sub(regex, replace, content if content is not None else ctx.content)
140
+
141
+ async def utils_regex_search(ctx: Message, regex: str, content: str = None) -> str:
142
+ """
143
+ Search for a regex in a string.
144
+ :param regex:
145
+ :param content:
146
+ :return:
147
+ """
148
+
149
+ if not isinstance(regex, str):
150
+ raise Exception(f"Regex must be a string, not {type(regex)}!")
151
+
152
+ if content is not None and not isinstance(content, str):
153
+ raise Exception(f"Content must be a string, not {type(content)}!")
154
+
155
+ result = search(regex, content if content is not None else ctx.content)
156
+
157
+ return result.group() if result else ''
@@ -0,0 +1,35 @@
1
+ from discord import Message, Thread
2
+ from discord.errors import NotFound
3
+
4
+ from typing import Union
5
+ from re import search
6
+
7
+
8
+ async def utils_get_thread(ctx: Message, thread: Union[int, str]) -> Thread:
9
+ """
10
+ Returns the thread object of the specified thread ID or link.
11
+ Thread is only available in the same server as the command and in the same channel.
12
+ If the thread is a link, it must be in the format: https://discord.com/channels/{guild_id}/{channel_id}/{message_id}
13
+ """
14
+
15
+ if isinstance(thread, int):
16
+ return ctx.channel.get_thread(thread)
17
+
18
+ elif isinstance(thread, str):
19
+ match = search(r'https://discord\.com/channels/(\d+)/(\d+)(/\d+)?', thread)
20
+ if not match:
21
+ raise Exception("Invalid thread link format. Use a valid Discord thread link.")
22
+ guild_id = int(match.group(1))
23
+ message_id = int(match.group(2))
24
+ channel_id = ctx.channel.id if len(match.groups()) == 3 else ctx.channel.id
25
+
26
+ if guild_id != ctx.guild.id:
27
+ raise Exception("The thread must be from the same server as the command !")
28
+
29
+ try:
30
+ c = await ctx.guild.get_channel(channel_id).fetch_message(message_id)
31
+ return c.thread
32
+ except NotFound:
33
+ raise Exception(f"Thread with ID {message_id} not found in channel {channel_id} !")
34
+
35
+ raise Exception(f"Thread must be an integer or a string, not {type(thread)} !")
@@ -0,0 +1,4 @@
1
+ from .dshell_interpreter import *
2
+ from .errors import *
3
+ from .cached_messages import dshell_cached_messages
4
+ from .dshell_arguments import DshellArguments
@@ -0,0 +1,4 @@
1
+ from contextvars import ContextVar
2
+
3
+ # used to save all viewed messages in the current scoop
4
+ dshell_cached_messages = ContextVar('dshell_cached_messages')