bTagScript 5.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bTagScript/__init__.py +66 -0
- bTagScript/adapter/__init__.py +16 -0
- bTagScript/adapter/discord_adapters.py +275 -0
- bTagScript/adapter/function_adapter.py +33 -0
- bTagScript/adapter/int_adapter.py +30 -0
- bTagScript/adapter/object_adapter.py +41 -0
- bTagScript/adapter/string_adapter.py +69 -0
- bTagScript/block/__init__.py +67 -0
- bTagScript/block/break_block.py +41 -0
- bTagScript/block/case_block.py +62 -0
- bTagScript/block/comment_block.py +33 -0
- bTagScript/block/control_block.py +161 -0
- bTagScript/block/counting_blocks.py +88 -0
- bTagScript/block/digitshorthand_block.py +38 -0
- bTagScript/block/discord_blocks/__init__.py +20 -0
- bTagScript/block/discord_blocks/command_block.py +52 -0
- bTagScript/block/discord_blocks/cooldown_block.py +101 -0
- bTagScript/block/discord_blocks/delete_block.py +47 -0
- bTagScript/block/discord_blocks/embed_block.py +248 -0
- bTagScript/block/discord_blocks/override_block.py +61 -0
- bTagScript/block/discord_blocks/react_block.py +49 -0
- bTagScript/block/discord_blocks/redirect_block.py +42 -0
- bTagScript/block/discord_blocks/requirement_blocks.py +83 -0
- bTagScript/block/helpers.py +121 -0
- bTagScript/block/math_blocks.py +257 -0
- bTagScript/block/random_block.py +65 -0
- bTagScript/block/range_block.py +54 -0
- bTagScript/block/replace_block.py +110 -0
- bTagScript/block/stop_block.py +38 -0
- bTagScript/block/strf_block.py +70 -0
- bTagScript/block/url_blocks.py +76 -0
- bTagScript/block/util_blocks/__init__.py +3 -0
- bTagScript/block/util_blocks/debug_block.py +107 -0
- bTagScript/block/var_block.py +49 -0
- bTagScript/block/vargetter_blocks.py +91 -0
- bTagScript/exceptions.py +139 -0
- bTagScript/interface/__init__.py +4 -0
- bTagScript/interface/adapter.py +43 -0
- bTagScript/interface/block.py +136 -0
- bTagScript/interpreter.py +614 -0
- bTagScript/utils.py +65 -0
- bTagScript/verb.py +129 -0
- btagscript-5.0.0.dist-info/METADATA +109 -0
- btagscript-5.0.0.dist-info/RECORD +47 -0
- btagscript-5.0.0.dist-info/WHEEL +5 -0
- btagscript-5.0.0.dist-info/licenses/LICENSE +1 -0
- btagscript-5.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from inspect import ismethod
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
from discord import Colour, Embed
|
|
6
|
+
|
|
7
|
+
from ...exceptions import BadColourArgument, EmbedParseError
|
|
8
|
+
from ...interface import Block
|
|
9
|
+
from ...interpreter import Context
|
|
10
|
+
from ..helpers import helper_split, implicit_bool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def string_to_color(argument: str) -> Colour:
|
|
14
|
+
"""
|
|
15
|
+
Converts a string to a discord.Colour object
|
|
16
|
+
"""
|
|
17
|
+
arg = argument.replace("0x", "").lower()
|
|
18
|
+
|
|
19
|
+
if arg[0] == "#":
|
|
20
|
+
arg = arg[1:]
|
|
21
|
+
try:
|
|
22
|
+
value = int(arg, base=16)
|
|
23
|
+
if not (0 <= value <= 0xFFFFFF):
|
|
24
|
+
raise BadColourArgument(arg)
|
|
25
|
+
return Colour(value=value)
|
|
26
|
+
except ValueError:
|
|
27
|
+
arg = arg.replace(" ", "_")
|
|
28
|
+
method = getattr(Colour, arg, None)
|
|
29
|
+
if arg.startswith("from_") or method is None or not ismethod(method):
|
|
30
|
+
raise BadColourArgument(arg) # pylint: disable=raise-missing-from
|
|
31
|
+
return method()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_color(embed: Embed, attribute: str, value: str) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Sets the colour of the embed given
|
|
37
|
+
"""
|
|
38
|
+
value = string_to_color(value)
|
|
39
|
+
setattr(embed, attribute, value)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def set_dynamic_url(embed: Embed, attribute: str, value: str) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Dynamically sets the url of the embed
|
|
45
|
+
"""
|
|
46
|
+
method = getattr(embed, f"set_{attribute}")
|
|
47
|
+
method(url=value)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def add_field(embed: Embed, _: str, payload: str) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Adds a field to the embed
|
|
53
|
+
"""
|
|
54
|
+
parts = helper_split(payload, False)
|
|
55
|
+
if parts is None or len(parts) not in (2, 3):
|
|
56
|
+
raise EmbedParseError("`add_field` payload was not split by |")
|
|
57
|
+
name, value = parts[0], parts[1]
|
|
58
|
+
inline = False
|
|
59
|
+
if len(parts) == 3:
|
|
60
|
+
inline = implicit_bool(parts[2])
|
|
61
|
+
if inline is None:
|
|
62
|
+
raise EmbedParseError(
|
|
63
|
+
f"`inline` argument for `add_field` is not a boolean value ({parts[2]})"
|
|
64
|
+
)
|
|
65
|
+
embed.add_field(name=name, value=value, inline=inline)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EmbedBlock(Block):
|
|
69
|
+
"""
|
|
70
|
+
An embed block will send an embed in the tag response.
|
|
71
|
+
There are two ways to use the embed block, either by using properly
|
|
72
|
+
formatted embed JSON from an embed generator or manually inputting
|
|
73
|
+
the accepted embed attributes.
|
|
74
|
+
|
|
75
|
+
**JSON**
|
|
76
|
+
|
|
77
|
+
Using JSON to create an embed offers complete embed customization.
|
|
78
|
+
Multiple embed generators are available online to visualize and generate
|
|
79
|
+
embed JSON.
|
|
80
|
+
|
|
81
|
+
**Usage:** ``{embed(<json>)}``
|
|
82
|
+
|
|
83
|
+
**Payload:** ``None``
|
|
84
|
+
|
|
85
|
+
**Parameter:** ``json``
|
|
86
|
+
|
|
87
|
+
.. tagscript::
|
|
88
|
+
|
|
89
|
+
{embed({"title":"Hello!", "description":"This is a test embed."})}
|
|
90
|
+
{embed({
|
|
91
|
+
"title":"Here's a random duck!",
|
|
92
|
+
"image":{"url":"https://random-d.uk/api/randomimg"},
|
|
93
|
+
"color":15194415
|
|
94
|
+
})}
|
|
95
|
+
|
|
96
|
+
**Manual**
|
|
97
|
+
|
|
98
|
+
The following embed attributes can be set manually:
|
|
99
|
+
|
|
100
|
+
* ``title``
|
|
101
|
+
* ``description``
|
|
102
|
+
* ``color``
|
|
103
|
+
* ``url``
|
|
104
|
+
* ``thumbnail``
|
|
105
|
+
* ``image``
|
|
106
|
+
* ``field`` - (See below)
|
|
107
|
+
|
|
108
|
+
Adding a field to an embed requires the payload to be split by ``|``, into
|
|
109
|
+
either 2 or 3 parts. The first part is the name of the field, the second is
|
|
110
|
+
the text of the field, and the third optionally specifies whether the field
|
|
111
|
+
should be inline.
|
|
112
|
+
|
|
113
|
+
**Usage:** ``{embed(<attribute>):<value>}``
|
|
114
|
+
|
|
115
|
+
**Payload:** ``value``
|
|
116
|
+
|
|
117
|
+
**Parameter:** ``attribute``
|
|
118
|
+
|
|
119
|
+
.. tagscript::
|
|
120
|
+
{embed(color):#37b2cb}
|
|
121
|
+
{embed(title):Rules}
|
|
122
|
+
{embed(description):Follow these rules to ensure a good experience in our server!}
|
|
123
|
+
{embed(field):Rule 1|Respect everyone you speak to.|false}
|
|
124
|
+
|
|
125
|
+
Both methods can be combined to create an embed in a tag.
|
|
126
|
+
The following tagscript uses JSON to create an embed with fields and later
|
|
127
|
+
set the embed title.
|
|
128
|
+
|
|
129
|
+
:: tagscript::
|
|
130
|
+
|
|
131
|
+
{embed({{"fields":[{"name":"Field 1","value":"field description","inline":false}]})}
|
|
132
|
+
{embed(title):my embed title}
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
ACCEPTED_NAMES = ("embed",)
|
|
136
|
+
|
|
137
|
+
ATTRIBUTE_HANDLERS = {
|
|
138
|
+
"description": setattr,
|
|
139
|
+
"title": setattr,
|
|
140
|
+
"color": set_color,
|
|
141
|
+
"colour": set_color,
|
|
142
|
+
"url": setattr,
|
|
143
|
+
"thumbnail": set_dynamic_url,
|
|
144
|
+
"image": set_dynamic_url,
|
|
145
|
+
"field": add_field,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def get_embed(ctx: Context) -> Embed:
|
|
150
|
+
"""
|
|
151
|
+
Gets the embed object
|
|
152
|
+
"""
|
|
153
|
+
return ctx.response.actions.get("embed", Embed())
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def value_to_color(value: Optional[Union[int, str]]) -> Colour:
|
|
157
|
+
"""
|
|
158
|
+
Converts a value to a discord.Colour object
|
|
159
|
+
"""
|
|
160
|
+
if value is None or isinstance(value, Colour):
|
|
161
|
+
return value
|
|
162
|
+
if isinstance(value, int):
|
|
163
|
+
return Colour(value)
|
|
164
|
+
elif isinstance(value, str):
|
|
165
|
+
return string_to_color(value)
|
|
166
|
+
else:
|
|
167
|
+
raise EmbedParseError("Received invalid type for color key (expected string or int)")
|
|
168
|
+
|
|
169
|
+
def text_to_embed(self, text: str) -> Embed:
|
|
170
|
+
"""
|
|
171
|
+
Converts json to an embed
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
data = json.loads(text)
|
|
175
|
+
except json.decoder.JSONDecodeError as error:
|
|
176
|
+
raise EmbedParseError(error) from error
|
|
177
|
+
|
|
178
|
+
if data.get("embed"):
|
|
179
|
+
data = data["embed"]
|
|
180
|
+
if data.get("timestamp"):
|
|
181
|
+
data["timestamp"] = data["timestamp"].strip("Z")
|
|
182
|
+
|
|
183
|
+
color = data.pop("color", data.pop("colour", None))
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
embed = Embed.from_dict(data)
|
|
187
|
+
except Exception as error:
|
|
188
|
+
raise EmbedParseError(error) from error
|
|
189
|
+
else:
|
|
190
|
+
if color := self.value_to_color(color):
|
|
191
|
+
embed.color = color
|
|
192
|
+
return embed
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def update_embed(cls, embed: Embed, attribute: str, value: str) -> Embed:
|
|
196
|
+
"""
|
|
197
|
+
Update the embed with all attributes
|
|
198
|
+
"""
|
|
199
|
+
handler = cls.ATTRIBUTE_HANDLERS[attribute]
|
|
200
|
+
try:
|
|
201
|
+
handler(embed, attribute, value)
|
|
202
|
+
except Exception as error:
|
|
203
|
+
raise EmbedParseError(error) from error
|
|
204
|
+
return embed
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def return_error(error: Exception) -> str:
|
|
208
|
+
"""
|
|
209
|
+
Return an error message
|
|
210
|
+
"""
|
|
211
|
+
return f"Embed Parse Error: {error}"
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def return_embed(ctx: Context, embed: Embed) -> str:
|
|
215
|
+
"""
|
|
216
|
+
Returns the embed
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
length = len(embed)
|
|
220
|
+
except Exception as error: # pylint: disable=broad-except
|
|
221
|
+
return str(error)
|
|
222
|
+
if length > 6000:
|
|
223
|
+
return f"`MAX EMBED LENGTH REACHED ({length}/6000)`"
|
|
224
|
+
ctx.response.actions["embed"] = embed
|
|
225
|
+
return ""
|
|
226
|
+
|
|
227
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
228
|
+
"""
|
|
229
|
+
Process the block
|
|
230
|
+
"""
|
|
231
|
+
if not ctx.verb.parameter:
|
|
232
|
+
return self.return_embed(ctx, self.get_embed(ctx))
|
|
233
|
+
|
|
234
|
+
lowered = ctx.verb.parameter.lower()
|
|
235
|
+
try:
|
|
236
|
+
if ctx.verb.parameter.strip().startswith("{") and ctx.verb.parameter.strip().endswith(
|
|
237
|
+
"}"
|
|
238
|
+
):
|
|
239
|
+
embed = self.text_to_embed(ctx.verb.parameter)
|
|
240
|
+
elif lowered in self.ATTRIBUTE_HANDLERS and ctx.verb.payload:
|
|
241
|
+
embed = self.get_embed(ctx)
|
|
242
|
+
embed = self.update_embed(embed, lowered, ctx.verb.payload)
|
|
243
|
+
else:
|
|
244
|
+
return
|
|
245
|
+
except EmbedParseError as error:
|
|
246
|
+
return self.return_error(error)
|
|
247
|
+
|
|
248
|
+
return self.return_embed(ctx, embed)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...interface import Block
|
|
4
|
+
from ...interpreter import Context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OverrideBlock(Block):
|
|
8
|
+
"""
|
|
9
|
+
Override a command's permission requirements. This can override
|
|
10
|
+
mod, admin, or general user permission requirements when running commands
|
|
11
|
+
with the :ref:`Command Block`. Passing no parameter will default to overriding
|
|
12
|
+
all permissions.
|
|
13
|
+
|
|
14
|
+
In order to add a tag with the override block, the tag author must have ``Manage
|
|
15
|
+
Server`` permissions.
|
|
16
|
+
|
|
17
|
+
This will not override bot owner commands or command checks.
|
|
18
|
+
|
|
19
|
+
**Usage:** ``{override(["admin"|"mod"|"permissions"]):[command]}``
|
|
20
|
+
|
|
21
|
+
**Aliases:** ``bypass``
|
|
22
|
+
|
|
23
|
+
**Payload:** ``command``
|
|
24
|
+
|
|
25
|
+
**Parameter:** ``"admin", "mod", "permissions"``
|
|
26
|
+
|
|
27
|
+
**Examples:**
|
|
28
|
+
|
|
29
|
+
.. tagscript::
|
|
30
|
+
|
|
31
|
+
{override}
|
|
32
|
+
overrides all commands and permissions
|
|
33
|
+
|
|
34
|
+
{override(admin)}
|
|
35
|
+
overrides commands that require the admin role
|
|
36
|
+
|
|
37
|
+
{bypass(permissions)}
|
|
38
|
+
{bypass(mod)}
|
|
39
|
+
overrides commands that require the mod role or have user permission requirements
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
ACCEPTED_NAMES = ("override", "bypass")
|
|
43
|
+
|
|
44
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
45
|
+
"""
|
|
46
|
+
Process the block and update response.actions with correct overrides
|
|
47
|
+
"""
|
|
48
|
+
param = ctx.verb.parameter
|
|
49
|
+
if not param:
|
|
50
|
+
ctx.response.actions["overrides"] = {"admin": True, "mod": True, "permissions": True}
|
|
51
|
+
return ""
|
|
52
|
+
|
|
53
|
+
param = param.strip().lower()
|
|
54
|
+
if param not in ("admin", "mod", "permissions"):
|
|
55
|
+
return None
|
|
56
|
+
overrides = ctx.response.actions.get(
|
|
57
|
+
"overrides", {"admin": False, "mod": False, "permissions": False}
|
|
58
|
+
)
|
|
59
|
+
overrides[param] = True
|
|
60
|
+
ctx.response.actions["overrides"] = overrides
|
|
61
|
+
return ""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...interface import verb_required_block
|
|
4
|
+
from ...interpreter import Context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ReactBlock(verb_required_block(True, payload=True)):
|
|
8
|
+
"""
|
|
9
|
+
The react block will set the actions "react" key to a list of reactions.
|
|
10
|
+
|
|
11
|
+
.. note::
|
|
12
|
+
|
|
13
|
+
You must set the behaviour manually.
|
|
14
|
+
|
|
15
|
+
**Usage:** ``{react:<emojis>}``
|
|
16
|
+
|
|
17
|
+
**Aliases:** ``None``
|
|
18
|
+
|
|
19
|
+
**Payload:** ``emojis``
|
|
20
|
+
|
|
21
|
+
**Parameter:** ``None``
|
|
22
|
+
|
|
23
|
+
.. tagscript::
|
|
24
|
+
|
|
25
|
+
{react:💩}
|
|
26
|
+
{react:💩,:)}
|
|
27
|
+
{react:💩~:)~:D}
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
ACCEPTED_NAMES = ("react",)
|
|
31
|
+
|
|
32
|
+
def __init__(self, limit: int = 5) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Initialize the block
|
|
35
|
+
|
|
36
|
+
Limit is the maximum number of reactions the block will add
|
|
37
|
+
"""
|
|
38
|
+
self.limit = limit
|
|
39
|
+
super().__init__()
|
|
40
|
+
|
|
41
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
42
|
+
"""
|
|
43
|
+
Process the reactions
|
|
44
|
+
"""
|
|
45
|
+
reactions = ctx.verb.payload.strip().split("~" if "~" in ctx.verb.payload else ",")
|
|
46
|
+
if len(reactions) > self.limit:
|
|
47
|
+
return f"`Reaction Limit Reached ({self.limit})`"
|
|
48
|
+
ctx.response.actions["reactions"] = reactions
|
|
49
|
+
return ""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...interface import verb_required_block
|
|
4
|
+
from ...interpreter import Context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RedirectBlock(verb_required_block(True, parameter=True)):
|
|
8
|
+
"""
|
|
9
|
+
Redirects the tag response to either the given channel, the author's DMs,
|
|
10
|
+
or uses a reply based on what is passed to the parameter.
|
|
11
|
+
|
|
12
|
+
**Usage:** ``{redirect(<"dm"|"reply"|channel>)}``
|
|
13
|
+
|
|
14
|
+
**Payload:** ``None``
|
|
15
|
+
|
|
16
|
+
**Parameter:** ``"dm", "reply", "channel"``
|
|
17
|
+
|
|
18
|
+
**Examples:**
|
|
19
|
+
|
|
20
|
+
.. tagscript::
|
|
21
|
+
|
|
22
|
+
{redirect(dm)}
|
|
23
|
+
{redirect(reply)}
|
|
24
|
+
{redirect(#general)}
|
|
25
|
+
{redirect(626861902521434160)}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
ACCEPTED_NAMES = ("redirect",)
|
|
29
|
+
|
|
30
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
31
|
+
"""
|
|
32
|
+
Process the redirect block and params
|
|
33
|
+
"""
|
|
34
|
+
param = ctx.verb.parameter.strip()
|
|
35
|
+
if param.lower() == "dm":
|
|
36
|
+
target = "dm"
|
|
37
|
+
elif param.lower() == "reply":
|
|
38
|
+
target = "reply"
|
|
39
|
+
else:
|
|
40
|
+
target = param
|
|
41
|
+
ctx.response.actions["target"] = target
|
|
42
|
+
return ""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from ...interface import verb_required_block
|
|
4
|
+
from ...interpreter import Context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RequireBlock(verb_required_block(True, parameter=True)):
|
|
8
|
+
"""
|
|
9
|
+
The require block will attempt to convert the given parameter into a channel
|
|
10
|
+
role or member, using name or ID. If the user running the tag is not in the
|
|
11
|
+
targeted channel or doesn't have the targeted role, the tag will stop processing
|
|
12
|
+
and it will send the response if one is given. Multiple role or channel
|
|
13
|
+
requirements can be given, and should be split by a ",".
|
|
14
|
+
|
|
15
|
+
**Usage:** ``{require(<role, channel, member>):[response]}``
|
|
16
|
+
|
|
17
|
+
**Aliases:** ``whitelist``
|
|
18
|
+
|
|
19
|
+
**Payload:** ``response``
|
|
20
|
+
|
|
21
|
+
**Parameter:** ``role, channel, member``
|
|
22
|
+
|
|
23
|
+
**Examples:**
|
|
24
|
+
|
|
25
|
+
.. tagscript::
|
|
26
|
+
|
|
27
|
+
{require(Moderator)}
|
|
28
|
+
{require(#general, #bot-cmds):This tag can only be run in #general and #bot-cmds.}
|
|
29
|
+
{require(757425366209134764, 668713062186090506, 737961895356792882):You aren't allowed to use this tag.}
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
ACCEPTED_NAMES = ("require", "whitelist")
|
|
33
|
+
|
|
34
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
35
|
+
"""
|
|
36
|
+
Process the requirements
|
|
37
|
+
"""
|
|
38
|
+
actions = ctx.response.actions.get("requires")
|
|
39
|
+
if actions:
|
|
40
|
+
return None
|
|
41
|
+
ctx.response.actions["requires"] = {
|
|
42
|
+
"items": [i.strip() for i in ctx.verb.parameter.split(",")],
|
|
43
|
+
"response": ctx.verb.payload,
|
|
44
|
+
}
|
|
45
|
+
return ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BlacklistBlock(verb_required_block(True, parameter=True)):
|
|
49
|
+
"""
|
|
50
|
+
The blacklist block will attempt to convert the given parameter into a channel
|
|
51
|
+
or role, using name or ID. If the user running the tag is in the targeted
|
|
52
|
+
channel or has the targeted role, the tag will stop processing and
|
|
53
|
+
it will send the response if one is given. Multiple role or channel
|
|
54
|
+
requirements can be given, and should be split by a ",".
|
|
55
|
+
|
|
56
|
+
**Usage:** ``{blacklist(<role,channel>):[response]}``
|
|
57
|
+
|
|
58
|
+
**Payload:** ``response``
|
|
59
|
+
|
|
60
|
+
**Parameter:** ``role, channel``
|
|
61
|
+
|
|
62
|
+
**Examples:**
|
|
63
|
+
|
|
64
|
+
.. tagscript::
|
|
65
|
+
{blacklist(Muted)}
|
|
66
|
+
{blacklist(#support):This tag is not allowed in #support.}
|
|
67
|
+
{blacklist(Tag Blacklist, 668713062186090506):You are blacklisted from using tags.}
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
ACCEPTED_NAMES = ("blacklist",)
|
|
71
|
+
|
|
72
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
73
|
+
"""
|
|
74
|
+
Process the blacklists
|
|
75
|
+
"""
|
|
76
|
+
actions = ctx.response.actions.get("blacklist")
|
|
77
|
+
if actions:
|
|
78
|
+
return None
|
|
79
|
+
ctx.response.actions["blacklist"] = {
|
|
80
|
+
"items": [i.strip() for i in ctx.verb.parameter.split(",")],
|
|
81
|
+
"response": ctx.verb.payload,
|
|
82
|
+
}
|
|
83
|
+
return ""
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helpers for blocks, mainly for parsing expressions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
__all__ = ("implicit_bool", "helper_parse_if", "helper_split", "helper_parse_list_if")
|
|
9
|
+
|
|
10
|
+
SPLIT_REGEX = re.compile(r"(?<!\\)\|")
|
|
11
|
+
BOOL_LOOKUP = {"true": True, "false": False} # potentially add more bool values
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def implicit_bool(string: str) -> Optional[bool]:
|
|
15
|
+
"""
|
|
16
|
+
Parse a string to a boolean.
|
|
17
|
+
|
|
18
|
+
>>> implicit_bool("true")
|
|
19
|
+
True
|
|
20
|
+
>>> implicit_bool("FALSE")
|
|
21
|
+
False
|
|
22
|
+
>>> implicit_bool("abc")
|
|
23
|
+
None
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
string: str
|
|
28
|
+
The string to convert.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
bool
|
|
33
|
+
The boolean value of the string.
|
|
34
|
+
None
|
|
35
|
+
The string failed to parse.
|
|
36
|
+
"""
|
|
37
|
+
return BOOL_LOOKUP.get(string.lower())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def helper_parse_if(string: str) -> Optional[bool]:
|
|
41
|
+
"""
|
|
42
|
+
Parse an expression string to a boolean.
|
|
43
|
+
|
|
44
|
+
>>> helper_parse_if("this == this")
|
|
45
|
+
True
|
|
46
|
+
>>> helper_parse_if("2>3")
|
|
47
|
+
False
|
|
48
|
+
>>> helper_parse_if("40 >= 40")
|
|
49
|
+
True
|
|
50
|
+
>>> helper_parse_if("False")
|
|
51
|
+
False
|
|
52
|
+
>>> helper_parse_if("1")
|
|
53
|
+
None
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
string: str
|
|
58
|
+
The string to convert.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
bool
|
|
63
|
+
The boolean value of the expression.
|
|
64
|
+
None
|
|
65
|
+
The expression failed to parse.
|
|
66
|
+
"""
|
|
67
|
+
value = implicit_bool(string)
|
|
68
|
+
if value is not None:
|
|
69
|
+
return value
|
|
70
|
+
try:
|
|
71
|
+
if "!=" in string:
|
|
72
|
+
spl = string.split("!=")
|
|
73
|
+
return spl[0].strip() != spl[1].strip()
|
|
74
|
+
if "==" in string:
|
|
75
|
+
spl = string.split("==")
|
|
76
|
+
return spl[0].strip() == spl[1].strip()
|
|
77
|
+
if ">=" in string:
|
|
78
|
+
spl = string.split(">=")
|
|
79
|
+
return float(spl[0].strip()) >= float(spl[1].strip())
|
|
80
|
+
if "<=" in string:
|
|
81
|
+
spl = string.split("<=")
|
|
82
|
+
return float(spl[0].strip()) <= float(spl[1].strip())
|
|
83
|
+
if ">" in string:
|
|
84
|
+
spl = string.split(">")
|
|
85
|
+
return float(spl[0].strip()) > float(spl[1].strip())
|
|
86
|
+
if "<" in string:
|
|
87
|
+
spl = string.split("<")
|
|
88
|
+
return float(spl[0].strip()) < float(spl[1].strip())
|
|
89
|
+
except: # pylint: disable=bare-except
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def helper_split(
|
|
94
|
+
split_string: str, easy: bool = True, *, maxsplit: int = None
|
|
95
|
+
) -> Optional[List[str]]:
|
|
96
|
+
"""
|
|
97
|
+
A helper method to universalize the splitting logic used in multiple
|
|
98
|
+
blocks and adapters. Please use this wherever a verb needs content to
|
|
99
|
+
be chopped at | , or ~!
|
|
100
|
+
|
|
101
|
+
>>> helper_split("this, should|work")
|
|
102
|
+
["this, should", "work"]
|
|
103
|
+
"""
|
|
104
|
+
args = (maxsplit,) if maxsplit is not None else ()
|
|
105
|
+
if "|" in split_string:
|
|
106
|
+
return SPLIT_REGEX.split(split_string, *args)
|
|
107
|
+
if easy and "~" in split_string:
|
|
108
|
+
return split_string.split("~", *args)
|
|
109
|
+
if easy and "," in split_string:
|
|
110
|
+
return split_string.split(",", *args)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def helper_parse_list_if(if_string: str) -> Optional[List[str]]:
|
|
115
|
+
"""
|
|
116
|
+
Returns a list of bool strings from a string.
|
|
117
|
+
"""
|
|
118
|
+
split = helper_split(if_string, False)
|
|
119
|
+
if split is None:
|
|
120
|
+
return [helper_parse_if(if_string)]
|
|
121
|
+
return [helper_parse_if(item) for item in split]
|