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.
Files changed (47) hide show
  1. bTagScript/__init__.py +66 -0
  2. bTagScript/adapter/__init__.py +16 -0
  3. bTagScript/adapter/discord_adapters.py +275 -0
  4. bTagScript/adapter/function_adapter.py +33 -0
  5. bTagScript/adapter/int_adapter.py +30 -0
  6. bTagScript/adapter/object_adapter.py +41 -0
  7. bTagScript/adapter/string_adapter.py +69 -0
  8. bTagScript/block/__init__.py +67 -0
  9. bTagScript/block/break_block.py +41 -0
  10. bTagScript/block/case_block.py +62 -0
  11. bTagScript/block/comment_block.py +33 -0
  12. bTagScript/block/control_block.py +161 -0
  13. bTagScript/block/counting_blocks.py +88 -0
  14. bTagScript/block/digitshorthand_block.py +38 -0
  15. bTagScript/block/discord_blocks/__init__.py +20 -0
  16. bTagScript/block/discord_blocks/command_block.py +52 -0
  17. bTagScript/block/discord_blocks/cooldown_block.py +101 -0
  18. bTagScript/block/discord_blocks/delete_block.py +47 -0
  19. bTagScript/block/discord_blocks/embed_block.py +248 -0
  20. bTagScript/block/discord_blocks/override_block.py +61 -0
  21. bTagScript/block/discord_blocks/react_block.py +49 -0
  22. bTagScript/block/discord_blocks/redirect_block.py +42 -0
  23. bTagScript/block/discord_blocks/requirement_blocks.py +83 -0
  24. bTagScript/block/helpers.py +121 -0
  25. bTagScript/block/math_blocks.py +257 -0
  26. bTagScript/block/random_block.py +65 -0
  27. bTagScript/block/range_block.py +54 -0
  28. bTagScript/block/replace_block.py +110 -0
  29. bTagScript/block/stop_block.py +38 -0
  30. bTagScript/block/strf_block.py +70 -0
  31. bTagScript/block/url_blocks.py +76 -0
  32. bTagScript/block/util_blocks/__init__.py +3 -0
  33. bTagScript/block/util_blocks/debug_block.py +107 -0
  34. bTagScript/block/var_block.py +49 -0
  35. bTagScript/block/vargetter_blocks.py +91 -0
  36. bTagScript/exceptions.py +139 -0
  37. bTagScript/interface/__init__.py +4 -0
  38. bTagScript/interface/adapter.py +43 -0
  39. bTagScript/interface/block.py +136 -0
  40. bTagScript/interpreter.py +614 -0
  41. bTagScript/utils.py +65 -0
  42. bTagScript/verb.py +129 -0
  43. btagscript-5.0.0.dist-info/METADATA +109 -0
  44. btagscript-5.0.0.dist-info/RECORD +47 -0
  45. btagscript-5.0.0.dist-info/WHEEL +5 -0
  46. btagscript-5.0.0.dist-info/licenses/LICENSE +1 -0
  47. btagscript-5.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,33 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import Block
4
+ from ..interpreter import Context
5
+
6
+
7
+ class CommentBlock(Block):
8
+ """
9
+ The comment block is literally just for comments, it will not be
10
+ parsed, however it will be removed from your codes output.
11
+
12
+ **Usage:** ``{comment([other]):[text]}``
13
+
14
+ **Aliases:** /, Comment, comment, //
15
+
16
+ **Payload:** ``text``
17
+
18
+ **Parameter:** ``other``
19
+
20
+ .. tagscript::
21
+
22
+ {//:Comment!}
23
+
24
+ {Comment(Something):Comment!}
25
+ """
26
+
27
+ ACCEPTED_NAMES = ("/", "comment", "//")
28
+
29
+ def process(self, ctx: Context) -> Optional[str]:
30
+ """
31
+ Remove the block
32
+ """
33
+ return ""
@@ -0,0 +1,161 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import verb_required_block
4
+ from ..interpreter import Context
5
+ from . import helper_parse_if, helper_parse_list_if, helper_split
6
+
7
+
8
+ def parse_into_output(payload: str, result: Optional[bool]) -> Optional[str]:
9
+ """
10
+ Parse a payload into an output.
11
+ """
12
+ if result is None:
13
+ return
14
+ try:
15
+ output = helper_split(payload, False)
16
+ if output is not None and len(output) == 2:
17
+ if result:
18
+ return output[0]
19
+ return output[1]
20
+ elif result:
21
+ return payload
22
+ else:
23
+ return ""
24
+ except: # pylint: disable=bare-except
25
+ return
26
+
27
+
28
+ ImplicitPPRBlock = verb_required_block(True, payload=True, parameter=True)
29
+
30
+
31
+ class AnyBlock(ImplicitPPRBlock):
32
+ """
33
+ The any block checks that any of the passed expressions are true.
34
+ Multiple expressions can be passed to the parameter by splitting them with ``|``.
35
+
36
+ The payload is a required message that must be split by ``|``.
37
+ If the expression evaluates true, then the message before the ``|`` is returned, else the message after is returned.
38
+
39
+ **Usage:** ``{any(<expression|expression|...>):<message>}``
40
+
41
+ **Aliases:** ``or``
42
+
43
+ **Payload:** ``message``
44
+
45
+ **Parameter:** ``expression``
46
+
47
+ **Examples:**
48
+
49
+ .. tagscript::
50
+
51
+ {any(hi=={args}|hello=={args}|heyy=={args}):Hello {user}!|How rude.}
52
+ If {args} is hi
53
+ Hello _Leg3ndary#0001!
54
+
55
+ If {args} is what's up!
56
+ How rude.
57
+ """
58
+
59
+ ACCEPTED_NAMES = ("any", "or")
60
+
61
+ def process(self, ctx: Context) -> Optional[str]:
62
+ """
63
+ Process the any block
64
+ """
65
+ result = any(helper_parse_list_if(ctx.verb.parameter) or [])
66
+ return parse_into_output(ctx.verb.payload, result)
67
+
68
+
69
+ class AllBlock(ImplicitPPRBlock):
70
+ """
71
+ The all block checks that all of the passed expressions are true.
72
+ Multiple expressions can be passed to the parameter by splitting them with ``|``.
73
+
74
+ The payload is a required message that must be split by ``|``.
75
+ If the expression evaluates true, then the message before the ``|`` is returned, else the message after is returned.
76
+
77
+ **Usage:** ``{all(<expression|expression|...>):<message>}``
78
+
79
+ **Aliases:** ``and``
80
+
81
+ **Payload:** ``message``
82
+
83
+ **Parameter:** ``expression``
84
+
85
+ **Examples:**
86
+
87
+ .. tagscript::
88
+
89
+ {all({args}>=100|{args}<=1000):You picked {args}.|You must provide a number between 100 and 1000.}
90
+ # if {args} is 52
91
+ You must provide a number between 100 and 1000.
92
+
93
+ # if {args} is 282
94
+ You picked 282.
95
+ """
96
+
97
+ ACCEPTED_NAMES = ("all", "and")
98
+
99
+ def process(self, ctx: Context) -> Optional[str]:
100
+ """
101
+ Process all the expressions
102
+ """
103
+ result = all(helper_parse_list_if(ctx.verb.parameter) or [])
104
+ return parse_into_output(ctx.verb.payload, result)
105
+
106
+
107
+ class IfBlock(ImplicitPPRBlock):
108
+ """
109
+ The if block returns a message based on the passed expression to the parameter.
110
+ An expression is represented by two values compared with an operator.
111
+
112
+ The payload is a required message that must be split by ``|``.
113
+ If the expression evaluates true, then the message before the ``|`` is returned, else the message after is returned.
114
+
115
+ **Expression Operators:**
116
+
117
+ +----------+--------------------------+---------+---------------------------------------------+
118
+ | Operator | Check | Example | Description |
119
+ +==========+==========================+=========+=============================================+
120
+ | ``==`` | equality | a==a | value 1 is equal to value 2 |
121
+ +----------+--------------------------+---------+---------------------------------------------+
122
+ | ``!=`` | inequality | a!=b | value 1 is not equal to value 2 |
123
+ +----------+--------------------------+---------+---------------------------------------------+
124
+ | ``>`` | greater than | 5>3 | value 1 is greater than value 2 |
125
+ +----------+--------------------------+---------+---------------------------------------------+
126
+ | ``<`` | less than | 4<8 | value 1 is less than value 2 |
127
+ +----------+--------------------------+---------+---------------------------------------------+
128
+ | ``>=`` | greater than or equality | 10>=10 | value 1 is greater than or equal to value 2 |
129
+ +----------+--------------------------+---------+---------------------------------------------+
130
+ | ``<=`` | less than or equality | 5<=6 | value 1 is less than or equal to value 2 |
131
+ +----------+--------------------------+---------+---------------------------------------------+
132
+
133
+ **Usage:** ``{if(<expression>):<message>]}``
134
+
135
+ **Payload:** ``message``
136
+
137
+ **Parameter:** ``expression``
138
+
139
+ **Examples:**
140
+
141
+ .. tagscript::
142
+
143
+ {if(63=={args}):You guessed it! The number I was thinking of was 63!|Too {if({args}<63):low|high}, try again.}
144
+ If args is 63
145
+ You guessed it! The number I was thinking of was 63!
146
+
147
+ If args is 73
148
+ Too low, try again.
149
+
150
+ If args is 14
151
+ Too high, try again.
152
+ """
153
+
154
+ ACCEPTED_NAMES = ("if",)
155
+
156
+ def process(self, ctx: Context) -> Optional[str]:
157
+ """
158
+ Process the if block
159
+ """
160
+ result = helper_parse_if(ctx.verb.parameter)
161
+ return parse_into_output(ctx.verb.payload, result)
@@ -0,0 +1,88 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import verb_required_block
4
+ from ..interpreter import Context
5
+
6
+
7
+ class CountBlock(verb_required_block(True, payload=True)):
8
+ """
9
+ The count block will count how much of text is in message.
10
+ This is case sensitive and will include substrings, if you
11
+ don't provide a parameter, it will count the spaces in the
12
+ message.
13
+
14
+
15
+ **Usage:** ``{count([text]):<message>}``
16
+
17
+ **Aliases:** ``None``
18
+
19
+ **Payload:** ``message``
20
+
21
+ **Parameter:** text
22
+
23
+ .. tagscript::
24
+
25
+ {count(Tag):TagScript}
26
+ 1
27
+
28
+ {count(Tag):Tag Script TagScript}
29
+ 2
30
+
31
+ {count(t):Hello World, Tag, Script}
32
+ 1 as there's only one lowercase t in the entire string
33
+ """
34
+
35
+ ACCEPTED_NAMES = ("count",)
36
+
37
+ def process(self, ctx: Context) -> Optional[str]:
38
+ """
39
+ Check the count of a string
40
+ """
41
+ if ctx.verb.parameter:
42
+ return str(ctx.verb.payload.count(ctx.verb.parameter))
43
+ return str(ctx.verb.payload.count(" "))
44
+
45
+
46
+ class LengthBlock(verb_required_block(True, payload=True)):
47
+ """
48
+ The length block will check the length of the given String.
49
+ If a parameter is passed in, the block will check the length
50
+ based on what you passed in, w for word, s for spaces.
51
+ If you provide an invalid parameter, the block will return -1.
52
+
53
+ **Usage:** ``{length(["w", "s"]):<text>}``
54
+
55
+ **Aliases:** ``len``
56
+
57
+ **Payload:** ``text``
58
+
59
+ **Parameter:** ``"w", "s"``
60
+
61
+ .. tagscript::
62
+
63
+ {length:TagScript}
64
+ 9
65
+
66
+ {len(w):Tag Script}
67
+ 2
68
+
69
+ {len(s):Hello World, Tag, Script}
70
+ 3
71
+
72
+ {len(space):Hello World, Tag, Script}
73
+ -1
74
+ """
75
+
76
+ ACCEPTED_NAMES = ("length", "len")
77
+
78
+ def process(self, ctx: Context) -> Optional[str]:
79
+ """
80
+ Check the length of a string
81
+ """
82
+ if ctx.verb.parameter:
83
+ if ctx.verb.parameter in ("w", "words", "word"):
84
+ return str(len(ctx.verb.payload.split(" ")))
85
+ if ctx.verb.parameter in ("s", "spaces", "space"):
86
+ return str(len(ctx.verb.payload.split(" ")) - 1)
87
+ return "-1"
88
+ return str(len(ctx.verb.payload))
@@ -0,0 +1,38 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import Block
4
+ from ..interpreter import Context
5
+ from ..verb import Verb
6
+
7
+
8
+ class DigitShorthandBlock(Block):
9
+ """
10
+ DigitShorthand Blocks are used to provide a shorthand for variables.
11
+ This is usually used for arguments, so you can set {1} == {args(1)}
12
+ {2} == {args(2)} etc.
13
+
14
+ .. tagscript::
15
+
16
+ Check the description.
17
+ """
18
+
19
+ def __init__(self, var_name: str) -> None:
20
+ """
21
+ Initialize the DigitShorthandBlock with the block you want to be digitted. If thats a word.
22
+ """
23
+ self.redirect_name = var_name
24
+
25
+ def will_accept(self, ctx: Context) -> bool: # pylint: disable=arguments-differ
26
+ """
27
+ Check if the declaration is a digit
28
+ """
29
+ return ctx.verb.declaration.isdigit()
30
+
31
+ def process(self, ctx: Context) -> Optional[str]:
32
+ """
33
+ Process the digital shorthand
34
+ """
35
+ blank = Verb()
36
+ blank.declaration = self.redirect_name
37
+ blank.parameter = ctx.verb.declaration
38
+ ctx.verb = blank
@@ -0,0 +1,20 @@
1
+ from .command_block import CommandBlock
2
+ from .cooldown_block import CooldownBlock
3
+ from .delete_block import DeleteBlock
4
+ from .embed_block import EmbedBlock
5
+ from .override_block import OverrideBlock
6
+ from .react_block import ReactBlock
7
+ from .redirect_block import RedirectBlock
8
+ from .requirement_blocks import BlacklistBlock, RequireBlock
9
+
10
+ __all__ = (
11
+ "CommandBlock",
12
+ "OverrideBlock",
13
+ "CooldownBlock",
14
+ "DeleteBlock",
15
+ "EmbedBlock",
16
+ "ReactBlock",
17
+ "RedirectBlock",
18
+ "BlacklistBlock",
19
+ "RequireBlock",
20
+ )
@@ -0,0 +1,52 @@
1
+ from typing import Optional
2
+
3
+ from ...interface import verb_required_block
4
+ from ...interpreter import Context
5
+
6
+
7
+ class CommandBlock(verb_required_block(True, payload=True)):
8
+ """
9
+ Run a command as if the tag invoker had ran it. Only 3 command
10
+ blocks can be used in a tag.
11
+
12
+ **Usage:** ``{command:<command>}``
13
+
14
+ **Aliases:** ``c, com, command``
15
+
16
+ **Payload:** command
17
+
18
+ **Parameter:** None
19
+
20
+ **Examples:**
21
+
22
+ .. tagscript::
23
+
24
+ {c:ping}
25
+ # Invokes ping command
26
+
27
+ {c:ban {target(id)} Chatflood/spam}
28
+ # Invokes ban command on the pinged user with the reason as "Chatflood/spam"
29
+ """
30
+
31
+ ACCEPTED_NAMES = ("c", "com", "command")
32
+
33
+ def __init__(self, limit: int = 3) -> None:
34
+ """
35
+ Construct the command block.
36
+ """
37
+ self.limit = limit
38
+ super().__init__()
39
+
40
+ def process(self, ctx: Context) -> Optional[str]:
41
+ """
42
+ Process the block and update response.actions
43
+ """
44
+ command = ctx.verb.payload.strip()
45
+ actions = ctx.response.actions.get("commands")
46
+ if actions:
47
+ if len(actions) >= self.limit:
48
+ return f"<{ctx.verb.declaration} error: limit reached ({self.limit})>"
49
+ else:
50
+ ctx.response.actions["commands"] = []
51
+ ctx.response.actions["commands"].append(command)
52
+ return ""
@@ -0,0 +1,101 @@
1
+ import time
2
+ from typing import Any, Dict, Optional
3
+
4
+ from discord.ext.commands import CooldownMapping
5
+
6
+ from ...exceptions import CooldownExceeded
7
+ from ...interface import verb_required_block
8
+ from ...interpreter import Context
9
+ from ..helpers import helper_split
10
+
11
+ __all__ = ("CooldownBlock",)
12
+
13
+
14
+ class CooldownBlock(verb_required_block(True, payload=True, parameter=True)):
15
+ """
16
+ The cooldown block implements cooldowns when running a tag.
17
+ The parameter requires 2 values to be passed: ``rate`` and ``per`` integers.
18
+ The ``rate`` is the number of times the tag can be used every ``per`` seconds.
19
+
20
+ The payload requires a ``key`` value, which is the key used to store the cooldown.
21
+ A key should be any string that is unique. If a channel's ID is passed as a key,
22
+ the tag's cooldown will be enforced on that channel. Running the tag in a separate channel
23
+ would have a different cooldown with the same ``rate`` and ``per`` values.
24
+
25
+ The payload also has an optional ``message`` value, which is the message to be sent when the
26
+ cooldown is exceeded. If no message is passed, the default message will be sent instead.
27
+ The cooldown message supports 2 blocks: ``key`` and ``retry_after``.
28
+
29
+ **Usage:** ``{cooldown(<rate>|<per>):<key>|[message]}``
30
+
31
+ **Payload:** ``key, message``
32
+
33
+ **Parameter:** ``rate, per``
34
+
35
+ **Examples:**
36
+
37
+ .. tagscript::
38
+
39
+ {cooldown(1|10):{author(id)}}
40
+ the tag author used the tag more than once in 10 seconds
41
+ The bucket for 741074175875088424 has reached its cooldown. Retry in 3.25 seconds."
42
+
43
+ {cooldown(3|3):{channel(id)}|Slow down! This tag can only be used 3 times per 3 seconds per channel. Try again in **{retry_after}** seconds."}
44
+ the tag was used more than 3 times in 3 seconds in a channel
45
+ Slow down! This tag can only be used 3 times per 3 seconds per channel. Try again in **0.74** seconds.
46
+ """
47
+
48
+ ACCEPTED_NAMES = ("cooldown",)
49
+ COOLDOWNS: Dict[Any, CooldownMapping] = {}
50
+
51
+ @classmethod
52
+ def create_cooldown(cls, key: Any, rate: int, per: int) -> CooldownMapping:
53
+ """
54
+ Create a new cooldown for the given key.
55
+ """
56
+ cooldown = CooldownMapping.from_cooldown(rate, per, lambda x: x)
57
+ cls.COOLDOWNS[key] = cooldown
58
+ return cooldown
59
+
60
+ def process(self, ctx: Context) -> Optional[str]:
61
+ """
62
+ Process the cooldown block.
63
+ """
64
+ verb = ctx.verb
65
+ try:
66
+ rate, per = helper_split(verb.parameter, maxsplit=1)
67
+ per = int(per)
68
+ rate = float(rate)
69
+ except (ValueError, TypeError):
70
+ return
71
+
72
+ if split := helper_split(verb.payload, False, maxsplit=1):
73
+ key, message = split
74
+ else:
75
+ key = verb.payload
76
+ message = None
77
+
78
+ cooldown_key = ctx.response.extras.get("cooldown_key")
79
+ if cooldown_key is None:
80
+ cooldown_key = ctx.original_message
81
+ try:
82
+ cooldown = self.COOLDOWNS[cooldown_key]
83
+ base = cooldown._cooldown # pylint: disable=protected-access
84
+ if (rate, per) != (base.rate, base.per):
85
+ cooldown = self.create_cooldown(cooldown_key, rate, per)
86
+ except KeyError:
87
+ cooldown = self.create_cooldown(cooldown_key, rate, per)
88
+
89
+ current = time.time()
90
+ bucket = cooldown.get_bucket(key, current)
91
+ retry_after = bucket.update_rate_limit(current)
92
+ if retry_after:
93
+ retry_after = round(retry_after, 2)
94
+ if message:
95
+ message = message.replace("{key}", str(key)).replace(
96
+ "{retry_after}", str(retry_after)
97
+ )
98
+ else:
99
+ message = f"The bucket for {key} has reached its cooldown. Retry in {retry_after} seconds."
100
+ raise CooldownExceeded(message, bucket, key, retry_after)
101
+ return ""
@@ -0,0 +1,47 @@
1
+ from typing import Optional
2
+
3
+ from ...interface import Block
4
+ from ...interpreter import Context
5
+ from .. import helper_parse_if
6
+
7
+
8
+ class DeleteBlock(Block):
9
+ """
10
+ The delete block will delete the message if the condition provided in
11
+ the parameter is met, or if just the block is added, the message will
12
+ be deleted. Only one delete block will be processed, the rest,
13
+ removed, but ignored.
14
+
15
+ .. note::
16
+
17
+ This block will only set the actions "delete" key to True/False.
18
+ You must set the behaviour manually.
19
+
20
+ **Usage:** ``{delete(<expression>)}``
21
+
22
+ **Aliases:** ``del``
23
+
24
+ **Payload:** ``None``
25
+
26
+ **Parameter:** ``expression``
27
+
28
+ .. tagscript::
29
+
30
+ {delete}
31
+ {del(true==true)}
32
+ """
33
+
34
+ ACCEPTED_NAMES = ("delete", "del")
35
+
36
+ def process(self, ctx: Context) -> Optional[str]:
37
+ """
38
+ Process the delete
39
+ """
40
+ if "delete" in ctx.response.actions.keys():
41
+ return ""
42
+ if ctx.verb.parameter is None:
43
+ value = True
44
+ else:
45
+ value = helper_parse_if(ctx.verb.parameter)
46
+ ctx.response.actions["delete"] = value
47
+ return ""