ya-tagscript 1.0.0a4__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 (84) hide show
  1. ya_tagscript/__init__.py +6 -0
  2. ya_tagscript/adapters/__init__.py +21 -0
  3. ya_tagscript/adapters/discord_adapters/__init__.py +11 -0
  4. ya_tagscript/adapters/discord_adapters/attribute_adapter.py +62 -0
  5. ya_tagscript/adapters/discord_adapters/channel_adapter.py +54 -0
  6. ya_tagscript/adapters/discord_adapters/guild_adapter.py +63 -0
  7. ya_tagscript/adapters/discord_adapters/member_adapter.py +60 -0
  8. ya_tagscript/adapters/function_adapter.py +24 -0
  9. ya_tagscript/adapters/int_adapter.py +17 -0
  10. ya_tagscript/adapters/object_adapter.py +49 -0
  11. ya_tagscript/adapters/string_adapter.py +93 -0
  12. ya_tagscript/blocks/__init__.py +70 -0
  13. ya_tagscript/blocks/actions/__init__.py +15 -0
  14. ya_tagscript/blocks/actions/command_block.py +66 -0
  15. ya_tagscript/blocks/actions/delete_block.py +68 -0
  16. ya_tagscript/blocks/actions/override_block.py +81 -0
  17. ya_tagscript/blocks/actions/react_block.py +106 -0
  18. ya_tagscript/blocks/actions/redirect_block.py +79 -0
  19. ya_tagscript/blocks/actions/silence_block.py +53 -0
  20. ya_tagscript/blocks/conditional/__init__.py +11 -0
  21. ya_tagscript/blocks/conditional/all_block.py +95 -0
  22. ya_tagscript/blocks/conditional/any_block.py +95 -0
  23. ya_tagscript/blocks/conditional/if_block.py +114 -0
  24. ya_tagscript/blocks/conditional/python_block.py +81 -0
  25. ya_tagscript/blocks/discord/__init__.py +7 -0
  26. ya_tagscript/blocks/discord/cooldown_block.py +122 -0
  27. ya_tagscript/blocks/discord/embed_block.py +358 -0
  28. ya_tagscript/blocks/flow/__init__.py +9 -0
  29. ya_tagscript/blocks/flow/break_block.py +54 -0
  30. ya_tagscript/blocks/flow/shortcutredirect_block.py +64 -0
  31. ya_tagscript/blocks/flow/stop_block.py +56 -0
  32. ya_tagscript/blocks/limiters/__init__.py +7 -0
  33. ya_tagscript/blocks/limiters/blacklist_block.py +84 -0
  34. ya_tagscript/blocks/limiters/require_block.py +86 -0
  35. ya_tagscript/blocks/lists/__init__.py +7 -0
  36. ya_tagscript/blocks/lists/cycle_block.py +65 -0
  37. ya_tagscript/blocks/lists/list_block.py +63 -0
  38. ya_tagscript/blocks/math/__init__.py +7 -0
  39. ya_tagscript/blocks/math/math_block.py +350 -0
  40. ya_tagscript/blocks/math/ordinal_block.py +79 -0
  41. ya_tagscript/blocks/meta/__init__.py +7 -0
  42. ya_tagscript/blocks/meta/comment_block.py +47 -0
  43. ya_tagscript/blocks/meta/debug_block.py +140 -0
  44. ya_tagscript/blocks/rng/__init__.py +9 -0
  45. ya_tagscript/blocks/rng/fiftyfifty_block.py +36 -0
  46. ya_tagscript/blocks/rng/random_block.py +96 -0
  47. ya_tagscript/blocks/rng/range_block.py +95 -0
  48. ya_tagscript/blocks/strings/__init__.py +15 -0
  49. ya_tagscript/blocks/strings/case_block.py +55 -0
  50. ya_tagscript/blocks/strings/join_block.py +47 -0
  51. ya_tagscript/blocks/strings/replace_block.py +64 -0
  52. ya_tagscript/blocks/strings/substring_block.py +78 -0
  53. ya_tagscript/blocks/strings/urldecode_block.py +54 -0
  54. ya_tagscript/blocks/strings/urlencode_block.py +52 -0
  55. ya_tagscript/blocks/time/__init__.py +7 -0
  56. ya_tagscript/blocks/time/strf_block.py +102 -0
  57. ya_tagscript/blocks/time/timedelta_block.py +171 -0
  58. ya_tagscript/blocks/variables/__init__.py +9 -0
  59. ya_tagscript/blocks/variables/assign_block.py +48 -0
  60. ya_tagscript/blocks/variables/loose_variable_getter_block.py +80 -0
  61. ya_tagscript/blocks/variables/strict_variable_getter_block.py +80 -0
  62. ya_tagscript/exceptions/__init__.py +19 -0
  63. ya_tagscript/exceptions/exceptions.py +111 -0
  64. ya_tagscript/interfaces/__init__.py +12 -0
  65. ya_tagscript/interfaces/adapterabc.py +28 -0
  66. ya_tagscript/interfaces/blockabc.py +151 -0
  67. ya_tagscript/interfaces/interpreterabc.py +107 -0
  68. ya_tagscript/interfaces/nodeabc.py +124 -0
  69. ya_tagscript/interpreter/__init__.py +11 -0
  70. ya_tagscript/interpreter/context.py +47 -0
  71. ya_tagscript/interpreter/interpreter.py +196 -0
  72. ya_tagscript/interpreter/node.py +75 -0
  73. ya_tagscript/interpreter/parse_state.py +96 -0
  74. ya_tagscript/interpreter/response.py +75 -0
  75. ya_tagscript/interpreter/ts_parser.py +364 -0
  76. ya_tagscript/py.typed +0 -0
  77. ya_tagscript/util/__init__.py +9 -0
  78. ya_tagscript/util/conditionals.py +83 -0
  79. ya_tagscript/util/escaping.py +13 -0
  80. ya_tagscript/util/splitter.py +40 -0
  81. ya_tagscript-1.0.0a4.dist-info/METADATA +104 -0
  82. ya_tagscript-1.0.0a4.dist-info/RECORD +84 -0
  83. ya_tagscript-1.0.0a4.dist-info/WHEEL +4 -0
  84. ya_tagscript-1.0.0a4.dist-info/licenses/LICENSE.md +1 -0
@@ -0,0 +1,6 @@
1
+ from .interpreter import Response, TagScriptInterpreter
2
+
3
+ __all__ = (
4
+ "Response",
5
+ "TagScriptInterpreter",
6
+ )
@@ -0,0 +1,21 @@
1
+ from .discord_adapters import (
2
+ AttributeAdapter,
3
+ ChannelAdapter,
4
+ GuildAdapter,
5
+ MemberAdapter,
6
+ )
7
+ from .function_adapter import FunctionAdapter
8
+ from .int_adapter import IntAdapter
9
+ from .object_adapter import ObjectAdapter
10
+ from .string_adapter import StringAdapter
11
+
12
+ __all__ = (
13
+ "AttributeAdapter",
14
+ "ChannelAdapter",
15
+ "FunctionAdapter",
16
+ "GuildAdapter",
17
+ "IntAdapter",
18
+ "MemberAdapter",
19
+ "ObjectAdapter",
20
+ "StringAdapter",
21
+ )
@@ -0,0 +1,11 @@
1
+ from .attribute_adapter import AttributeAdapter
2
+ from .channel_adapter import ChannelAdapter
3
+ from .guild_adapter import GuildAdapter
4
+ from .member_adapter import MemberAdapter
5
+
6
+ __all__ = (
7
+ "AttributeAdapter",
8
+ "ChannelAdapter",
9
+ "GuildAdapter",
10
+ "MemberAdapter",
11
+ )
@@ -0,0 +1,62 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from ...interfaces import AdapterABC
5
+ from ...interpreter import Context
6
+ from ...util import escape_content
7
+
8
+
9
+ class AttributeAdapter(AdapterABC):
10
+ """A basic Discord object adapter
11
+
12
+ **Attributes**:
13
+
14
+ - ``id``: :class:`int` — The object's ID
15
+ - ``created_at``: :class:`~datetime.datetime` — Represents the object's creation
16
+ time
17
+ - ``timestamp``: :class:`int` — The seconds-based timestamp of the object's
18
+ ``created_at`` attribute
19
+ - ``name``: :class:`str` — The object's name or the stringified version of the
20
+ object if no name exists
21
+ """
22
+
23
+ __slots__ = ("object", "_attributes", "_methods")
24
+
25
+ def __init__(
26
+ self,
27
+ base: Any, # should be typed by each subclass, not feasible here
28
+ ):
29
+ self.object = base
30
+ self._attributes = {
31
+ "id": self.object.id,
32
+ "created_at": self.object.created_at,
33
+ "timestamp": int(self.object.created_at.timestamp()),
34
+ "name": getattr(self.object, "name", str(self.object)),
35
+ }
36
+ self._methods: dict[str, Callable[[], Any]] = {}
37
+
38
+ def __repr__(self) -> str:
39
+ return f"<{type(self).__qualname__} object={self.object!r}>"
40
+
41
+ def get_value(self, ctx: Context) -> str | None:
42
+ should_escape = False
43
+
44
+ if ((param := ctx.node.parameter) is None) or (
45
+ (parsed_param := ctx.interpret_segment(param)).strip() == ""
46
+ ):
47
+ return_value = str(self.object)
48
+ else:
49
+ try:
50
+ value = self._attributes[parsed_param]
51
+ except KeyError:
52
+ if method := self._methods.get(parsed_param):
53
+ value = method()
54
+ else:
55
+ return None
56
+
57
+ if isinstance(value, tuple):
58
+ value, should_escape = value
59
+
60
+ return_value = str(value) if value is not None else None
61
+
62
+ return escape_content(return_value) if should_escape else return_value
@@ -0,0 +1,54 @@
1
+ from typing import Any
2
+
3
+ import discord
4
+
5
+ from .attribute_adapter import AttributeAdapter
6
+
7
+
8
+ class ChannelAdapter(AttributeAdapter):
9
+ """A :class:`discord.TextChannel` adapter
10
+
11
+ Note:
12
+ Only :class:`discord.TextChannel` instances are fully supported. The
13
+ constructor accepts ``Any`` to avoid type checking issues when creating this
14
+ adapter with the :attr:`discord.ext.commands.Context.channel` attribute, which
15
+ could be any kind of channel. This loose typing allows the construction with
16
+ that attribute but doesn't set any of the :class:`discord.TextChannel`-specific
17
+ attributes.
18
+
19
+ **Attributes**:
20
+
21
+ (from base :class:`AttributeAdapter`)
22
+
23
+ - ``id``: :class:`int` — The channel's ID
24
+ - ``created_at``: :class:`~datetime.datetime` — Represents the channel's creation
25
+ time
26
+ - ``timestamp``: :class:`int` — The seconds-based timestamp of the channel's
27
+ ``created_at`` attribute
28
+ - ``name``: :class:`str` — The channel's name
29
+
30
+ (:class:`discord.TextChannel`-specific)
31
+
32
+ - ``nsfw``: :class:`bool` — Whether this :class:`discord.TextChannel` is marked as
33
+ NSFW and is therefore age-gated
34
+ - ``mention``: :class:`str` — The mention string for this channel
35
+ - ``topic``: :class:`str` | :data:`None` — The channel's topic, if it exists and
36
+ :data:`None` otherwise
37
+ - ``slowmode``: :class:`int` — The slowmode delay of the channel in seconds
38
+ (0 represents a disabled slowmode)
39
+ - ``position``: :class:`int` — The position of the channel in the channel list
40
+ """
41
+
42
+ def __init__(self, channel: Any):
43
+ # hard to type usefully since ctx.channel might not be TextChannel
44
+ super().__init__(base=channel)
45
+ if not isinstance(channel, discord.TextChannel):
46
+ return
47
+ additional_attributes = {
48
+ "nsfw": channel.nsfw,
49
+ "mention": channel.mention,
50
+ "topic": channel.topic,
51
+ "slowmode": channel.slowmode_delay,
52
+ "position": channel.position,
53
+ }
54
+ self._attributes.update(additional_attributes)
@@ -0,0 +1,63 @@
1
+ import random
2
+ from collections.abc import Sequence
3
+
4
+ import discord
5
+
6
+ from .attribute_adapter import AttributeAdapter
7
+
8
+
9
+ class GuildAdapter(AttributeAdapter):
10
+ """A :class:`discord.Guild` adapter
11
+
12
+ **Attributes**:
13
+
14
+ (from base :class:`AttributeAdapter`)
15
+
16
+ - ``id``: :class:`int` — The guild's ID
17
+ - ``created_at``: :class:`~datetime.datetime` — Represents the guild's creation
18
+ time
19
+ - ``timestamp``: :class:`int` — The seconds-based timestamp of the guild's
20
+ ``created_at`` attribute
21
+ - ``name``: :class:`str` — The guild's name
22
+
23
+ (:class:`discord.Guild`-specific)
24
+
25
+ - ``icon``: :class:`tuple[str, Literal[False]]` — The guild's icon. The first tuple
26
+ element contains the icon's URL or is empty. The :data:`False` instructs the
27
+ adapter to not escape the contents of this attribute.
28
+ - ``member_count``: :class:`int` | :data:`None` — The number of members in this
29
+ guild (alias: ``members``) (Can be :data:`None` under some circumstances)
30
+ - ``members``: :class:`int` | :data:`None` — The number of members in this guild
31
+ (alias: ``member_count``) (Can be :class:`None` under some circumstances)
32
+ - ``bots``: :class:`int` — The number of bots in this guild
33
+ - ``humans``: :class:`int` — The number of human users in this guild
34
+ - ``description``: :class:`str` — The guild's description
35
+
36
+ - ``random``: :class:`discord.Member` — Returns a randomly chosen member of the
37
+ guild
38
+ """
39
+
40
+ def __init__(self, guild: discord.Guild):
41
+ super().__init__(base=guild)
42
+ bots = 0
43
+ humans = 0
44
+ for m in guild.members:
45
+ if m.bot:
46
+ bots += 1
47
+ else:
48
+ humans += 1
49
+ additional_attributes = {
50
+ "icon": (getattr(guild.icon, "url", ""), False),
51
+ "member_count": guild.member_count,
52
+ "members": guild.member_count,
53
+ "bots": bots,
54
+ "humans": humans,
55
+ "description": guild.description or "No description.",
56
+ }
57
+ self._attributes.update(additional_attributes)
58
+ additional_methods = {"random": self.random_member}
59
+ self._methods.update(additional_methods)
60
+
61
+ def random_member(self) -> discord.Member:
62
+ members: Sequence[discord.Member] = self.object.members
63
+ return random.choice(members)
@@ -0,0 +1,60 @@
1
+ import discord
2
+
3
+ from .attribute_adapter import AttributeAdapter
4
+
5
+
6
+ class MemberAdapter(AttributeAdapter):
7
+ """A :class:`discord.Member` adapter
8
+
9
+ **Attributes**:
10
+
11
+ (from base :class:`AttributeAdapter`)
12
+
13
+ - ``id``: :class:`int` — The user's ID
14
+ - ``created_at``: :class:`~datetime.datetime` — Represents the user's creation time
15
+ - ``timestamp``: :class:`int` — The seconds-based timestamp of the user's
16
+ ``created_at`` attribute
17
+ - ``name``: :class:`str` — The user's name
18
+
19
+ (:class:`discord.Member`-specific)
20
+
21
+ - ``color`` :class:`discord.Colour` — The colour the user's name is shown in
22
+ (depends on their top role) (alias: ``colour``)
23
+ - ``colour`` :class:`discord.Colour` — The colour the user's name is shown in
24
+ (depends on their top role) (alias: ``color``)
25
+ - ``global_name``: :class:`str` | :data:`None` — The user's global nickname
26
+ - ``nick``: :class:`str` | :data:`None` — The user's guild-specific nickname
27
+ - ``avatar`` :class:`tuple[str, Literal[False]]` — The user's avatar. The first
28
+ tuple element contains the avatar's URL. The False instructs the adapter to not
29
+ escape the contents of this attribute.
30
+ - ``discriminator``: :class:`str` — The user's discriminator
31
+ - ``joined_at``: :class:`~datetime.datetime` — The user's time of joining this
32
+ guild. If the user has left the guild, this falls back to the user's creation
33
+ time.
34
+ - ``joinstamp``: :class:`int` — The seconds-based timestamp of the user's
35
+ joined_at attribute
36
+ - ``mention``: :class:`str` — The mention string for this user
37
+ - ``bot``: :class:`bool` — Whether this user is a bot account
38
+ - ``top_role``: :class:`discord.Role` — The user's topmost role
39
+ - ``roleids``: :class:`str` — A space-separated list of the IDs of each role of
40
+ this user.
41
+ """
42
+
43
+ def __init__(self, member: discord.Member):
44
+ super().__init__(base=member)
45
+ joined_at = member.joined_at or member.created_at
46
+ additional_attributes = {
47
+ "color": member.color,
48
+ "colour": member.colour,
49
+ "global_name": member.global_name,
50
+ "nick": member.nick,
51
+ "avatar": (member.display_avatar.url, False),
52
+ "discriminator": member.discriminator,
53
+ "joined_at": joined_at,
54
+ "joinstamp": int(joined_at.timestamp()),
55
+ "mention": member.mention,
56
+ "bot": member.bot,
57
+ "top_role": member.top_role,
58
+ "roleids": " ".join(str(rid.id) for rid in member.roles),
59
+ }
60
+ self._attributes.update(additional_attributes)
@@ -0,0 +1,24 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from ..interfaces import AdapterABC
5
+ from ..interpreter import Context
6
+
7
+
8
+ class FunctionAdapter(AdapterABC):
9
+ """An adapter for a simple, no-arg function
10
+
11
+ Caution:
12
+ The provided function CANNOT take ANY arguments.
13
+ """
14
+
15
+ __slots__ = ("fn",)
16
+
17
+ def __init__(self, function: Callable[[], Any]):
18
+ self.fn: Callable[[], Any] = function
19
+
20
+ def __repr__(self) -> str:
21
+ return f"<{type(self).__qualname__} fn={self.fn!r}>"
22
+
23
+ def get_value(self, ctx: Context) -> str | None:
24
+ return str(self.fn())
@@ -0,0 +1,17 @@
1
+ from ..interfaces import AdapterABC
2
+ from ..interpreter import Context
3
+
4
+
5
+ class IntAdapter(AdapterABC):
6
+ """An adapter for integers"""
7
+
8
+ __slots__ = ("integer",)
9
+
10
+ def __init__(self, integer: int):
11
+ self.integer: int = int(integer)
12
+
13
+ def __repr__(self) -> str:
14
+ return f"<{type(self).__qualname__} integer={self.integer!r}>"
15
+
16
+ def get_value(self, ctx: Context) -> str | None:
17
+ return str(self.integer)
@@ -0,0 +1,49 @@
1
+ from inspect import ismethod
2
+
3
+ from ..interfaces import AdapterABC
4
+ from ..interpreter import Context
5
+
6
+
7
+ class ObjectAdapter(AdapterABC):
8
+ """An adapter for any sort of Python object
9
+
10
+ Caution:
11
+ The following things are unsupported and will be rejected
12
+
13
+ - Methods
14
+ - Private attributes (names starting with ``_``)
15
+ - Nested attributes (``obj.a.b``)
16
+
17
+ - ``obj.a`` is accepted
18
+ - ``obj.a.b`` is not accepted
19
+
20
+ - Float attributes will be truncated into integer values (``12.97 -> 12``)
21
+ """
22
+
23
+ __slots__ = ("obj",)
24
+
25
+ def __init__(self, base: object):
26
+ self.obj = base
27
+
28
+ def __repr__(self) -> str:
29
+ return f"<{type(self).__qualname__} object={self.obj!r}>"
30
+
31
+ def get_value(self, ctx: Context) -> str | None:
32
+ if ctx.node.parameter is None:
33
+ return str(self.obj)
34
+
35
+ parsed_param = ctx.interpret_segment(ctx.node.parameter)
36
+
37
+ if parsed_param.startswith("_") or "." in parsed_param:
38
+ return None
39
+
40
+ try:
41
+ attribute = getattr(self.obj, parsed_param)
42
+ except AttributeError:
43
+ return None
44
+ if ismethod(attribute):
45
+ return None
46
+ elif isinstance(attribute, float):
47
+ attribute = int(attribute)
48
+
49
+ return str(attribute)
@@ -0,0 +1,93 @@
1
+ from ..interfaces import AdapterABC
2
+ from ..interpreter import Context
3
+ from ..util import escape_content
4
+
5
+
6
+ class StringAdapter(AdapterABC):
7
+ """An adapter for strings
8
+
9
+ .. _partial-substring-retrieval:
10
+
11
+ Retrieving partial substrings with parameters
12
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13
+
14
+ You can optionally use parameters to limit what parts of the string should be
15
+ retrieved.
16
+
17
+ If you provide a single number n as the parameter, the n-th word of the string is
18
+ returned (split by spaces). The first word is at index 1, and so on (1-indexed).
19
+
20
+ **Examples**::
21
+
22
+ # Assume my_string_variable holds "Hello there. General Kenobi."
23
+ {my_string_variable(2)}
24
+ # there.
25
+
26
+ {my_string_variable(3)}
27
+ # General
28
+
29
+ If you provide a single number followed (or preceded) by a plus, all words after
30
+ (or before) are returned (split by spaces).
31
+
32
+ **Examples**::
33
+
34
+ # Assume my_string_variable holds "Hello there. General Kenobi."
35
+ {my_string_variable(3+)}
36
+ # General Kenobi.
37
+
38
+ {my_string_variable(+2)}
39
+ # Hello there.
40
+
41
+ You can define the characters to split the string on by passing a payload. The
42
+ string will then be split at occurrences of those characters.
43
+
44
+ **Examples**::
45
+
46
+ # Assume my_string_variable holds "Hello there. General Kenobi."
47
+ {my_string_variable(2):.}
48
+ # General Kenobi
49
+
50
+ {my_string_variable(3):en}
51
+ # obi.
52
+ """
53
+
54
+ def __init__(self, string: str, *, should_escape: bool = False):
55
+ self.string: str = str(string)
56
+ self.should_escape: bool = should_escape
57
+
58
+ def __repr__(self) -> str:
59
+ return f"<{type(self).__qualname__} string={self.string!r}>"
60
+
61
+ def get_value(self, ctx: Context) -> str | None:
62
+ return self._return_value(self._handle_ctx(ctx))
63
+
64
+ def _handle_ctx(self, ctx: Context) -> str:
65
+ if (param := ctx.node.parameter) is None:
66
+ return self.string
67
+
68
+ parsed_param = ctx.interpret_segment(param)
69
+ parsed_payload = (
70
+ ctx.interpret_segment(ctx.node.payload)
71
+ if ctx.node.payload is not None
72
+ else None
73
+ )
74
+
75
+ try:
76
+ if "+" not in parsed_param:
77
+ index = int(parsed_param) - 1
78
+ splitter = " " if parsed_payload is None else parsed_payload
79
+ return self.string.split(splitter)[index]
80
+ else:
81
+ index = int(parsed_param.replace("+", "")) - 1
82
+ splitter = " " if parsed_payload is None else parsed_payload
83
+ if parsed_param.startswith("+"):
84
+ return splitter.join(self.string.split(splitter)[: index + 1])
85
+ elif parsed_param.endswith("+"):
86
+ return splitter.join(self.string.split(splitter)[index:])
87
+ else:
88
+ return self.string.split(splitter)[index]
89
+ except (ValueError, IndexError):
90
+ return self.string
91
+
92
+ def _return_value(self, string: str) -> str | None:
93
+ return escape_content(string) if self.should_escape else string
@@ -0,0 +1,70 @@
1
+ from .actions import (
2
+ CommandBlock,
3
+ DeleteBlock,
4
+ OverrideBlock,
5
+ ReactBlock,
6
+ RedirectBlock,
7
+ SilenceBlock,
8
+ )
9
+ from .conditional import AllBlock, AnyBlock, IfBlock, PythonBlock
10
+ from .discord import CooldownBlock, EmbedBlock
11
+ from .flow import BreakBlock, ShortcutRedirectBlock, StopBlock
12
+ from .limiters import BlacklistBlock, RequireBlock
13
+ from .lists import CycleBlock, ListBlock
14
+ from .math import MathBlock, OrdinalBlock
15
+ from .meta import CommentBlock, DebugBlock
16
+ from .rng import FiftyFiftyBlock, RandomBlock, RangeBlock
17
+ from .strings import (
18
+ CaseBlock,
19
+ JoinBlock,
20
+ ReplaceBlock,
21
+ SubstringBlock,
22
+ URLDecodeBlock,
23
+ URLEncodeBlock,
24
+ )
25
+ from .time import StrfBlock, TimedeltaBlock
26
+ from .variables import (
27
+ AssignmentBlock,
28
+ LooseVariableGetterBlock,
29
+ StrictVariableGetterBlock,
30
+ )
31
+
32
+ __all__ = (
33
+ "AllBlock",
34
+ "AnyBlock",
35
+ "AssignmentBlock",
36
+ "BlacklistBlock",
37
+ "BreakBlock",
38
+ "CaseBlock",
39
+ "CommandBlock",
40
+ "CommentBlock",
41
+ "CooldownBlock",
42
+ "CycleBlock",
43
+ "DebugBlock",
44
+ "DeleteBlock",
45
+ "EmbedBlock",
46
+ "FiftyFiftyBlock",
47
+ "IfBlock",
48
+ "JoinBlock",
49
+ "ListBlock",
50
+ "LooseVariableGetterBlock",
51
+ "MathBlock",
52
+ "OrdinalBlock",
53
+ "OverrideBlock",
54
+ "PythonBlock",
55
+ "RandomBlock",
56
+ "RangeBlock",
57
+ "ReactBlock",
58
+ "RedirectBlock",
59
+ "ReplaceBlock",
60
+ "RequireBlock",
61
+ "ShortcutRedirectBlock",
62
+ "SilenceBlock",
63
+ "StopBlock",
64
+ "StrfBlock",
65
+ "StrictVariableGetterBlock",
66
+ "SubstringBlock",
67
+ "TimedeltaBlock",
68
+ "URLDecodeBlock",
69
+ "URLEncodeBlock",
70
+ )
@@ -0,0 +1,15 @@
1
+ from .command_block import CommandBlock
2
+ from .delete_block import DeleteBlock
3
+ from .override_block import OverrideBlock
4
+ from .react_block import ReactBlock
5
+ from .redirect_block import RedirectBlock
6
+ from .silence_block import SilenceBlock
7
+
8
+ __all__ = (
9
+ "CommandBlock",
10
+ "DeleteBlock",
11
+ "OverrideBlock",
12
+ "ReactBlock",
13
+ "RedirectBlock",
14
+ "SilenceBlock",
15
+ )
@@ -0,0 +1,66 @@
1
+ from ...interfaces import BlockABC
2
+ from ...interpreter import Context
3
+
4
+
5
+ class CommandBlock(BlockABC):
6
+ """
7
+ Run a command as if the tag invoker had run it.
8
+
9
+ By default, only 3 command blocks can be used in a tag.
10
+
11
+ **Usage**: ``{command:<command text>}``
12
+
13
+ **Aliases**: ``c``, ``com``, ``cmd``, ``command``
14
+
15
+ **Parameter**: ``None``
16
+
17
+ **Payload**: ``command text`` (required)
18
+
19
+ **Examples**::
20
+
21
+ {c:ping}
22
+ # adds "ping" to the "commands" list of the Response's actions attribute
23
+
24
+ {c:ban {target(id)} flooding/spam}
25
+ # (Assuming target is a user with ID 123)
26
+ # adds "ban 123 flooding/spam"
27
+
28
+ **Response Attribute**:
29
+
30
+ This block sets the following attribute on the
31
+ :class:`~ya_tagscript.interpreter.Response` object:
32
+
33
+ - :attr:`~ya_tagscript.interpreter.Response.actions`
34
+ - ``actions["commands"]``: :class:`list[str]` | :data:`None` — A list of
35
+ command strings or :data:`None`
36
+
37
+ Note:
38
+ This block will only add the processed command strings to the ``commands``
39
+ :attr:`~ya_tagscript.interpreter.Response.actions` key as shown above. It is
40
+ *up to the client* to implement actual command execution behaviour as desired.
41
+ """
42
+
43
+ requires_nonempty_payload = True
44
+
45
+ def __init__(self, limit: int = 3):
46
+ self.limit = limit
47
+
48
+ @property
49
+ def _accepted_names(self) -> set[str]:
50
+ return {"c", "com", "cmd", "command"}
51
+
52
+ def process(self, ctx: Context) -> str | None:
53
+ if (payload := ctx.node.payload) is None or payload.strip() == "":
54
+ return None
55
+
56
+ command = ctx.interpret_segment(payload)
57
+
58
+ commands: list[str] | None = ctx.response.actions.get("commands")
59
+ if commands is not None:
60
+ if len(commands) >= self.limit:
61
+ return f"`COMMAND LIMIT REACHED ({self.limit})`"
62
+ ctx.response.actions["commands"].append(command)
63
+ else:
64
+ ctx.response.actions["commands"] = [command]
65
+
66
+ return ""