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.
- ya_tagscript/__init__.py +6 -0
- ya_tagscript/adapters/__init__.py +21 -0
- ya_tagscript/adapters/discord_adapters/__init__.py +11 -0
- ya_tagscript/adapters/discord_adapters/attribute_adapter.py +62 -0
- ya_tagscript/adapters/discord_adapters/channel_adapter.py +54 -0
- ya_tagscript/adapters/discord_adapters/guild_adapter.py +63 -0
- ya_tagscript/adapters/discord_adapters/member_adapter.py +60 -0
- ya_tagscript/adapters/function_adapter.py +24 -0
- ya_tagscript/adapters/int_adapter.py +17 -0
- ya_tagscript/adapters/object_adapter.py +49 -0
- ya_tagscript/adapters/string_adapter.py +93 -0
- ya_tagscript/blocks/__init__.py +70 -0
- ya_tagscript/blocks/actions/__init__.py +15 -0
- ya_tagscript/blocks/actions/command_block.py +66 -0
- ya_tagscript/blocks/actions/delete_block.py +68 -0
- ya_tagscript/blocks/actions/override_block.py +81 -0
- ya_tagscript/blocks/actions/react_block.py +106 -0
- ya_tagscript/blocks/actions/redirect_block.py +79 -0
- ya_tagscript/blocks/actions/silence_block.py +53 -0
- ya_tagscript/blocks/conditional/__init__.py +11 -0
- ya_tagscript/blocks/conditional/all_block.py +95 -0
- ya_tagscript/blocks/conditional/any_block.py +95 -0
- ya_tagscript/blocks/conditional/if_block.py +114 -0
- ya_tagscript/blocks/conditional/python_block.py +81 -0
- ya_tagscript/blocks/discord/__init__.py +7 -0
- ya_tagscript/blocks/discord/cooldown_block.py +122 -0
- ya_tagscript/blocks/discord/embed_block.py +358 -0
- ya_tagscript/blocks/flow/__init__.py +9 -0
- ya_tagscript/blocks/flow/break_block.py +54 -0
- ya_tagscript/blocks/flow/shortcutredirect_block.py +64 -0
- ya_tagscript/blocks/flow/stop_block.py +56 -0
- ya_tagscript/blocks/limiters/__init__.py +7 -0
- ya_tagscript/blocks/limiters/blacklist_block.py +84 -0
- ya_tagscript/blocks/limiters/require_block.py +86 -0
- ya_tagscript/blocks/lists/__init__.py +7 -0
- ya_tagscript/blocks/lists/cycle_block.py +65 -0
- ya_tagscript/blocks/lists/list_block.py +63 -0
- ya_tagscript/blocks/math/__init__.py +7 -0
- ya_tagscript/blocks/math/math_block.py +350 -0
- ya_tagscript/blocks/math/ordinal_block.py +79 -0
- ya_tagscript/blocks/meta/__init__.py +7 -0
- ya_tagscript/blocks/meta/comment_block.py +47 -0
- ya_tagscript/blocks/meta/debug_block.py +140 -0
- ya_tagscript/blocks/rng/__init__.py +9 -0
- ya_tagscript/blocks/rng/fiftyfifty_block.py +36 -0
- ya_tagscript/blocks/rng/random_block.py +96 -0
- ya_tagscript/blocks/rng/range_block.py +95 -0
- ya_tagscript/blocks/strings/__init__.py +15 -0
- ya_tagscript/blocks/strings/case_block.py +55 -0
- ya_tagscript/blocks/strings/join_block.py +47 -0
- ya_tagscript/blocks/strings/replace_block.py +64 -0
- ya_tagscript/blocks/strings/substring_block.py +78 -0
- ya_tagscript/blocks/strings/urldecode_block.py +54 -0
- ya_tagscript/blocks/strings/urlencode_block.py +52 -0
- ya_tagscript/blocks/time/__init__.py +7 -0
- ya_tagscript/blocks/time/strf_block.py +102 -0
- ya_tagscript/blocks/time/timedelta_block.py +171 -0
- ya_tagscript/blocks/variables/__init__.py +9 -0
- ya_tagscript/blocks/variables/assign_block.py +48 -0
- ya_tagscript/blocks/variables/loose_variable_getter_block.py +80 -0
- ya_tagscript/blocks/variables/strict_variable_getter_block.py +80 -0
- ya_tagscript/exceptions/__init__.py +19 -0
- ya_tagscript/exceptions/exceptions.py +111 -0
- ya_tagscript/interfaces/__init__.py +12 -0
- ya_tagscript/interfaces/adapterabc.py +28 -0
- ya_tagscript/interfaces/blockabc.py +151 -0
- ya_tagscript/interfaces/interpreterabc.py +107 -0
- ya_tagscript/interfaces/nodeabc.py +124 -0
- ya_tagscript/interpreter/__init__.py +11 -0
- ya_tagscript/interpreter/context.py +47 -0
- ya_tagscript/interpreter/interpreter.py +196 -0
- ya_tagscript/interpreter/node.py +75 -0
- ya_tagscript/interpreter/parse_state.py +96 -0
- ya_tagscript/interpreter/response.py +75 -0
- ya_tagscript/interpreter/ts_parser.py +364 -0
- ya_tagscript/py.typed +0 -0
- ya_tagscript/util/__init__.py +9 -0
- ya_tagscript/util/conditionals.py +83 -0
- ya_tagscript/util/escaping.py +13 -0
- ya_tagscript/util/splitter.py +40 -0
- ya_tagscript-1.0.0a4.dist-info/METADATA +104 -0
- ya_tagscript-1.0.0a4.dist-info/RECORD +84 -0
- ya_tagscript-1.0.0a4.dist-info/WHEEL +4 -0
- ya_tagscript-1.0.0a4.dist-info/licenses/LICENSE.md +1 -0
ya_tagscript/__init__.py
ADDED
|
@@ -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 ""
|