yourbot-sdk 0.6.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 YourBot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: yourbot-sdk
3
+ Version: 0.6.0
4
+ Summary: SDK for building YourBot Marketplace plugins
5
+ License: MIT
6
+ Project-URL: Homepage, https://yourbot.gg/dev
7
+ Project-URL: Documentation, https://yourbot.gg/dev/docs
8
+ Project-URL: Repository, https://github.com/NotUSeee/yourbot-sdk
9
+ Project-URL: Issues, https://github.com/NotUSeee/yourbot-sdk/issues
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # YourBot SDK
25
+
26
+ SDK for building plugins for the [YourBot](https://yourbot.gg) Discord bot platform.
27
+
28
+ YourBot runs marketplace plugins in sandboxed Docker containers. This SDK is the
29
+ official Python interface plugins use to receive Discord events, call the Discord
30
+ API, store data, render dashboards, and emit metrics — all routed through the
31
+ platform so plugins never need direct network access or credentials.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install yourbot-sdk
37
+ ```
38
+
39
+ Python 3.10 or newer.
40
+
41
+ ## Hello, plugin
42
+
43
+ ```python
44
+ from yourbot_sdk import Plugin, Context
45
+
46
+ plugin = Plugin()
47
+
48
+ @plugin.on_event("message_create")
49
+ def on_message(ctx: Context, event: dict):
50
+ if "!ping" in event.get("content", ""):
51
+ ctx.discord.send_message(
52
+ channel_id=event["channel_id"],
53
+ content="Pong!",
54
+ )
55
+
56
+ plugin.run() # must be the last line
57
+ ```
58
+
59
+ Drop this in a folder named `my_plugin/` as `__main__.py`, zip it, and upload it
60
+ via the [Developer Portal](https://yourbot.gg/dev).
61
+
62
+ ## CLI
63
+
64
+ The package installs a `yourbot` command for scaffolding and a local dev loop:
65
+
66
+ ```bash
67
+ yourbot new my_plugin # scaffold a new plugin from the template
68
+ yourbot dev # run your plugin locally against a mock host
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ Full plugin contract, capability reference, and publishing guide:
74
+
75
+ - **Docs:** https://yourbot.gg/dev/docs
76
+ - **Developer Portal:** https://yourbot.gg/dev
77
+
78
+ ## License
79
+
80
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,57 @@
1
+ # YourBot SDK
2
+
3
+ SDK for building plugins for the [YourBot](https://yourbot.gg) Discord bot platform.
4
+
5
+ YourBot runs marketplace plugins in sandboxed Docker containers. This SDK is the
6
+ official Python interface plugins use to receive Discord events, call the Discord
7
+ API, store data, render dashboards, and emit metrics — all routed through the
8
+ platform so plugins never need direct network access or credentials.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install yourbot-sdk
14
+ ```
15
+
16
+ Python 3.10 or newer.
17
+
18
+ ## Hello, plugin
19
+
20
+ ```python
21
+ from yourbot_sdk import Plugin, Context
22
+
23
+ plugin = Plugin()
24
+
25
+ @plugin.on_event("message_create")
26
+ def on_message(ctx: Context, event: dict):
27
+ if "!ping" in event.get("content", ""):
28
+ ctx.discord.send_message(
29
+ channel_id=event["channel_id"],
30
+ content="Pong!",
31
+ )
32
+
33
+ plugin.run() # must be the last line
34
+ ```
35
+
36
+ Drop this in a folder named `my_plugin/` as `__main__.py`, zip it, and upload it
37
+ via the [Developer Portal](https://yourbot.gg/dev).
38
+
39
+ ## CLI
40
+
41
+ The package installs a `yourbot` command for scaffolding and a local dev loop:
42
+
43
+ ```bash
44
+ yourbot new my_plugin # scaffold a new plugin from the template
45
+ yourbot dev # run your plugin locally against a mock host
46
+ ```
47
+
48
+ ## Documentation
49
+
50
+ Full plugin contract, capability reference, and publishing guide:
51
+
52
+ - **Docs:** https://yourbot.gg/dev/docs
53
+ - **Developer Portal:** https://yourbot.gg/dev
54
+
55
+ ## License
56
+
57
+ MIT — see [LICENSE](LICENSE).
@@ -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,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "yourbot-sdk"
7
+ dynamic = ["version"]
8
+ description = "SDK for building YourBot Marketplace plugins"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ dependencies = []
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://yourbot.gg/dev"
27
+ Documentation = "https://yourbot.gg/dev/docs"
28
+ Repository = "https://github.com/NotUSeee/yourbot-sdk"
29
+ Issues = "https://github.com/NotUSeee/yourbot-sdk/issues"
30
+
31
+ [project.scripts]
32
+ yourbot = "yourbot_sdk.cli:main"
33
+
34
+ # Ship BOTH the real package and the ``mmo_maid_sdk`` compatibility shim so
35
+ # ``pip install yourbot-sdk`` provides both ``import yourbot_sdk`` (preferred)
36
+ # and the legacy ``import mmo_maid_sdk`` (kept working for deployed plugins).
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["yourbot_sdk*", "mmo_maid_sdk*"]
40
+
41
+ [tool.setuptools.dynamic]
42
+ version = {attr = "yourbot_sdk.__version__"}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ # Legacy setup.py — version is read from yourbot_sdk/__init__.py via pyproject.toml
2
+ from setuptools import setup
3
+ setup()
@@ -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
+ }