yourbot-sdk 0.6.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.
@@ -0,0 +1,52 @@
1
+ """Backward-compatibility shim for the renamed ``yourbot_sdk`` package.
2
+
3
+ ``mmo_maid_sdk`` was renamed to ``yourbot_sdk`` in 0.6.0. This module keeps
4
+ ``import mmo_maid_sdk`` (and every submodule / legacy nested import) working
5
+ by re-exporting the real package. It ships inside the ``yourbot-sdk`` wheel,
6
+ so installing either ``yourbot-sdk`` or the ``mmo-maid-sdk`` alias makes the
7
+ old import path resolve. Prefer ``import yourbot_sdk`` in new code.
8
+
9
+ The identical file is kept at the monorepo root (``mmo_maid_sdk/__init__.py``)
10
+ as a dev-time shim so in-process imports from the source tree behave the same
11
+ as the installed wheel.
12
+ """
13
+ import importlib as _importlib
14
+ import sys as _sys
15
+ import warnings as _warnings
16
+
17
+ import yourbot_sdk as _real
18
+ from yourbot_sdk import * # noqa: F403
19
+ from yourbot_sdk import __all__, __version__ # noqa: F401
20
+
21
+ _warnings.warn(
22
+ "'mmo_maid_sdk' is deprecated and was renamed to 'yourbot_sdk'; import "
23
+ "'yourbot_sdk' instead. The compatibility shim will be removed in a "
24
+ "future major release.",
25
+ DeprecationWarning,
26
+ stacklevel=2,
27
+ )
28
+
29
+ # A star import does NOT register submodules in sys.modules, so
30
+ # ``from mmo_maid_sdk.testing import ...`` and the legacy deep
31
+ # ``from mmo_maid_sdk.mmo_maid_sdk._transport import ...`` would fail without
32
+ # explicit aliasing. Map every submodule under both the flat ``mmo_maid_sdk.*``
33
+ # and the legacy nested ``mmo_maid_sdk.mmo_maid_sdk.*`` names to the real
34
+ # module object. The try/except covers both layouts: the installed wheel
35
+ # (flat ``yourbot_sdk.<name>``) and the monorepo dev tree
36
+ # (nested ``yourbot_sdk.yourbot_sdk.<name>``).
37
+ _SUBMODULES = (
38
+ "_plugin", "_context", "_transport", "_components", "_exceptions",
39
+ "_validation", "events", "dashboard", "testing", "cli",
40
+ )
41
+ for _name in _SUBMODULES:
42
+ try:
43
+ _mod = _importlib.import_module(f"yourbot_sdk.{_name}")
44
+ except ModuleNotFoundError:
45
+ _mod = _importlib.import_module(f"yourbot_sdk.yourbot_sdk.{_name}")
46
+ _sys.modules[f"mmo_maid_sdk.{_name}"] = _mod
47
+ _sys.modules[f"mmo_maid_sdk.mmo_maid_sdk.{_name}"] = _mod
48
+
49
+ # ``from mmo_maid_sdk.mmo_maid_sdk import Plugin`` resolves to the real package.
50
+ _sys.modules["mmo_maid_sdk.mmo_maid_sdk"] = _real
51
+
52
+ del _importlib, _sys, _warnings, _name, _mod, _real
@@ -0,0 +1,85 @@
1
+ """
2
+ YourBot Plugin SDK
3
+ ===================
4
+
5
+ Write a plugin in three steps::
6
+
7
+ from yourbot_sdk import Plugin, Context, Button, ActionRow
8
+
9
+ plugin = Plugin()
10
+
11
+ @plugin.on_ready
12
+ def ready(ctx: Context):
13
+ ctx.log("My plugin is alive!")
14
+
15
+ @plugin.on_event("message_create")
16
+ def on_message(ctx: Context, event: dict):
17
+ if "!hello" in event.get("content", ""):
18
+ ctx.discord.send_message(
19
+ channel_id=event["channel_id"],
20
+ content="Hello from my plugin!",
21
+ )
22
+
23
+ # For IDE autocomplete on event fields, import the typed shape:
24
+ # from yourbot_sdk.events import MessageCreate
25
+ # def on_message(ctx: Context, event: MessageCreate): ...
26
+
27
+ @plugin.on_slash_command("greet")
28
+ def greet(ctx: Context, event: dict):
29
+ ctx.interaction.respond(
30
+ content="Hey there!",
31
+ components=[ActionRow(Button("Click me", custom_id="btn_hi"))],
32
+ )
33
+
34
+ @plugin.on_component("btn_hi")
35
+ def btn_hi(ctx: Context, event: dict):
36
+ ctx.interaction.respond(content="You clicked!", ephemeral=True)
37
+
38
+ plugin.run()
39
+
40
+ The SDK handles JSON-RPC, event acking, boot handshake, and error
41
+ recovery so you don't have to.
42
+ """
43
+
44
+ from ._plugin import Plugin
45
+ from ._context import Context
46
+ from ._exceptions import (
47
+ SdkError,
48
+ CapabilityError,
49
+ RateLimitError,
50
+ DiscordApiError,
51
+ SdkPermissionError,
52
+ PermissionError, # alias for SdkPermissionError (backwards compat)
53
+ ValidationError,
54
+ KvQuotaError,
55
+ RpcTimeoutError,
56
+ TimeoutError, # alias for RpcTimeoutError (backwards compat)
57
+ )
58
+ from ._components import (
59
+ ActionRow,
60
+ Button,
61
+ SelectMenu,
62
+ SelectOption,
63
+ TextInput,
64
+ )
65
+
66
+ __all__ = [
67
+ "Plugin",
68
+ "Context",
69
+ "SdkError",
70
+ "CapabilityError",
71
+ "RateLimitError",
72
+ "DiscordApiError",
73
+ "SdkPermissionError",
74
+ "PermissionError", # alias
75
+ "ValidationError",
76
+ "KvQuotaError",
77
+ "RpcTimeoutError",
78
+ "TimeoutError", # alias
79
+ "ActionRow",
80
+ "Button",
81
+ "SelectMenu",
82
+ "SelectOption",
83
+ "TextInput",
84
+ ]
85
+ __version__ = "0.6.0"
@@ -0,0 +1,276 @@
1
+ """Discord UI component builders for marketplace plugins.
2
+
3
+ These produce JSON dicts matching Discord's component schema.
4
+ Used with ``ctx.interaction.respond(components=[...])`` or
5
+ ``ctx.discord.send_message(channel_id, components=[...])``.
6
+
7
+ Usage::
8
+
9
+ from yourbot_sdk import ActionRow, Button, SelectMenu, TextInput
10
+
11
+ # Buttons in a row
12
+ row = ActionRow(
13
+ Button("Click me", custom_id="btn_click", style="primary"),
14
+ Button("Cancel", custom_id="btn_cancel", style="secondary"),
15
+ )
16
+
17
+ # Select menu
18
+ row2 = ActionRow(
19
+ SelectMenu("pick_role", options=[
20
+ SelectOption("Tank", "tank", emoji="🛡"),
21
+ SelectOption("Healer", "healer", emoji="💚"),
22
+ SelectOption("DPS", "dps", emoji="⚔"),
23
+ ], placeholder="Pick your role"),
24
+ )
25
+
26
+ # Modal text inputs (used with ctx.interaction.send_modal)
27
+ fields = [
28
+ TextInput("Character Name", "char_name", placeholder="e.g. Aerilyn"),
29
+ TextInput("Notes", "notes", style="paragraph", required=False),
30
+ ]
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from typing import Any, Dict, List, Optional
36
+
37
+ __all__ = [
38
+ "ActionRow",
39
+ "Button",
40
+ "SelectMenu",
41
+ "SelectOption",
42
+ "TextInput",
43
+ ]
44
+
45
+ # Discord component types
46
+ _COMPONENT_ACTION_ROW = 1
47
+ _COMPONENT_BUTTON = 2
48
+ _COMPONENT_SELECT_MENU = 3
49
+ _COMPONENT_TEXT_INPUT = 4
50
+
51
+ # Button styles
52
+ _BUTTON_STYLES = {
53
+ "primary": 1,
54
+ "secondary": 2,
55
+ "success": 3,
56
+ "danger": 4,
57
+ "link": 5,
58
+ }
59
+
60
+ # Text input styles
61
+ _TEXT_INPUT_STYLES = {
62
+ "short": 1,
63
+ "paragraph": 2,
64
+ }
65
+
66
+
67
+ class Button:
68
+ """A clickable button component.
69
+
70
+ Args:
71
+ label: Button text (max 80 chars).
72
+ custom_id: Developer-defined ID for handling clicks (max 100 chars).
73
+ Not needed for link buttons.
74
+ style: One of "primary", "secondary", "success", "danger", "link".
75
+ emoji: Optional emoji string (e.g., "🎮" or a custom emoji dict).
76
+ url: URL for link-style buttons (required if style="link").
77
+ disabled: Whether the button is greyed out.
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ label: str,
83
+ custom_id: str = "",
84
+ *,
85
+ style: str = "primary",
86
+ emoji: Optional[str] = None,
87
+ url: Optional[str] = None,
88
+ disabled: bool = False,
89
+ ):
90
+ self.label = str(label)[:80]
91
+ self.custom_id = str(custom_id)[:100]
92
+ self.style = style
93
+ self.emoji = emoji
94
+ self.url = url
95
+ self.disabled = disabled
96
+
97
+ def to_dict(self) -> Dict[str, Any]:
98
+ d: Dict[str, Any] = {
99
+ "type": _COMPONENT_BUTTON,
100
+ "style": _BUTTON_STYLES.get(self.style, 1),
101
+ "label": self.label,
102
+ }
103
+ if self.style == "link" and self.url:
104
+ d["url"] = self.url
105
+ elif self.custom_id:
106
+ d["custom_id"] = self.custom_id
107
+ if self.emoji:
108
+ if isinstance(self.emoji, str):
109
+ d["emoji"] = {"name": self.emoji}
110
+ elif isinstance(self.emoji, dict):
111
+ d["emoji"] = self.emoji
112
+ if self.disabled:
113
+ d["disabled"] = True
114
+ return d
115
+
116
+
117
+ class SelectOption:
118
+ """A single option within a SelectMenu.
119
+
120
+ Args:
121
+ label: Display text (max 100 chars).
122
+ value: Developer-defined value returned on select (max 100 chars).
123
+ description: Optional secondary text (max 100 chars).
124
+ emoji: Optional emoji string.
125
+ default: Whether this option is pre-selected.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ label: str,
131
+ value: str,
132
+ *,
133
+ description: Optional[str] = None,
134
+ emoji: Optional[str] = None,
135
+ default: bool = False,
136
+ ):
137
+ self.label = str(label)[:100]
138
+ self.value = str(value)[:100]
139
+ self.description = str(description)[:100] if description else None
140
+ self.emoji = emoji
141
+ self.default = default
142
+
143
+ def to_dict(self) -> Dict[str, Any]:
144
+ d: Dict[str, Any] = {"label": self.label, "value": self.value}
145
+ if self.description:
146
+ d["description"] = self.description
147
+ if self.emoji:
148
+ if isinstance(self.emoji, str):
149
+ d["emoji"] = {"name": self.emoji}
150
+ elif isinstance(self.emoji, dict):
151
+ d["emoji"] = self.emoji
152
+ if self.default:
153
+ d["default"] = True
154
+ return d
155
+
156
+
157
+ class SelectMenu:
158
+ """A dropdown select menu component.
159
+
160
+ Args:
161
+ custom_id: Developer-defined ID for handling selections (max 100 chars).
162
+ options: List of SelectOption objects (max 25).
163
+ placeholder: Greyed-out text when nothing is selected (max 150 chars).
164
+ min_values: Minimum selections required (default 1).
165
+ max_values: Maximum selections allowed (default 1).
166
+ disabled: Whether the menu is greyed out.
167
+ """
168
+
169
+ def __init__(
170
+ self,
171
+ custom_id: str,
172
+ options: Optional[List[SelectOption]] = None,
173
+ *,
174
+ placeholder: str = "",
175
+ min_values: int = 1,
176
+ max_values: int = 1,
177
+ disabled: bool = False,
178
+ ):
179
+ self.custom_id = str(custom_id)[:100]
180
+ self.options = (options or [])[:25]
181
+ self.placeholder = str(placeholder)[:150]
182
+ self.min_values = min_values
183
+ self.max_values = max_values
184
+ self.disabled = disabled
185
+
186
+ def to_dict(self) -> Dict[str, Any]:
187
+ d: Dict[str, Any] = {
188
+ "type": _COMPONENT_SELECT_MENU,
189
+ "custom_id": self.custom_id,
190
+ "options": [o.to_dict() for o in self.options],
191
+ }
192
+ if self.placeholder:
193
+ d["placeholder"] = self.placeholder
194
+ if self.min_values != 1:
195
+ d["min_values"] = self.min_values
196
+ if self.max_values != 1:
197
+ d["max_values"] = self.max_values
198
+ if self.disabled:
199
+ d["disabled"] = True
200
+ return d
201
+
202
+
203
+ class TextInput:
204
+ """A text input field for modal dialogs.
205
+
206
+ Args:
207
+ label: Input label shown to the user (max 45 chars).
208
+ custom_id: Developer-defined ID (max 100 chars).
209
+ style: "short" (single line) or "paragraph" (multi-line).
210
+ placeholder: Greyed-out placeholder text (max 100 chars).
211
+ value: Pre-filled value (max 4000 chars).
212
+ required: Whether the field must be filled.
213
+ min_length: Minimum input length (0-4000).
214
+ max_length: Maximum input length (1-4000).
215
+ """
216
+
217
+ def __init__(
218
+ self,
219
+ label: str,
220
+ custom_id: str,
221
+ *,
222
+ style: str = "short",
223
+ placeholder: str = "",
224
+ value: str = "",
225
+ required: bool = True,
226
+ min_length: Optional[int] = None,
227
+ max_length: Optional[int] = None,
228
+ ):
229
+ self.label = str(label)[:45]
230
+ self.custom_id = str(custom_id)[:100]
231
+ self.style = style
232
+ self.placeholder = str(placeholder)[:100]
233
+ self.value = str(value)[:4000] if value else ""
234
+ self.required = required
235
+ self.min_length = min_length
236
+ self.max_length = max_length
237
+
238
+ def to_dict(self) -> Dict[str, Any]:
239
+ d: Dict[str, Any] = {
240
+ "type": _COMPONENT_TEXT_INPUT,
241
+ "custom_id": self.custom_id,
242
+ "style": _TEXT_INPUT_STYLES.get(self.style, 1),
243
+ "label": self.label,
244
+ }
245
+ if self.placeholder:
246
+ d["placeholder"] = self.placeholder
247
+ if self.value:
248
+ d["value"] = self.value
249
+ d["required"] = self.required
250
+ if self.min_length is not None:
251
+ d["min_length"] = self.min_length
252
+ if self.max_length is not None:
253
+ d["max_length"] = self.max_length
254
+ return d
255
+
256
+
257
+ class ActionRow:
258
+ """A container row for up to 5 components.
259
+
260
+ An ActionRow can contain either:
261
+ - Up to 5 Button components, OR
262
+ - 1 SelectMenu component, OR
263
+ - 1 TextInput component (in modals only)
264
+
265
+ Args:
266
+ *children: Component objects (Button, SelectMenu, or TextInput).
267
+ """
268
+
269
+ def __init__(self, *children):
270
+ self.children = list(children)[:5]
271
+
272
+ def to_dict(self) -> Dict[str, Any]:
273
+ return {
274
+ "type": _COMPONENT_ACTION_ROW,
275
+ "components": [c.to_dict() for c in self.children],
276
+ }