mqs-compat 0.3.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,34 @@
1
+ # Rust
2
+ /target/
3
+ **/*.rs.bk
4
+
5
+ # Node / frontend
6
+ web/node_modules/
7
+ web/dist/
8
+ desktop/node_modules/
9
+
10
+ # Python shim
11
+ __pycache__/
12
+ *.py[cod]
13
+ *.egg-info/
14
+ python/build/
15
+ python/dist/
16
+
17
+ # Sandbox runtime data
18
+ profiles/
19
+ .mqs/
20
+ runtime.json
21
+ *.sqlite
22
+ *.sqlite-wal
23
+ *.sqlite-shm
24
+
25
+ # Build artifacts
26
+ dist/
27
+ build/
28
+ *.spec
29
+ *.log
30
+ .env
31
+ .DS_Store
32
+
33
+ # Harness memory/state
34
+ .dsh/
@@ -0,0 +1,36 @@
1
+ Metadata-Version: 2.5
2
+ Name: mqs-compat
3
+ Version: 0.3.0
4
+ Summary: Point the real QQ SDKs (botpy) at a running QSandbox — Python-only compatibility shim
5
+ Project-URL: Homepage, https://github.com/Moonlark-Dev/qsandbox
6
+ Project-URL: Repository, https://github.com/Moonlark-Dev/qsandbox
7
+ Author: Moonlark Dev Team
8
+ License-Expression: AGPL-3.0-or-later
9
+ Keywords: botpy,moonlark,qqbot,sandbox
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Requires-Python: >=3.9
15
+ Provides-Extra: botpy
16
+ Requires-Dist: qq-botpy; extra == 'botpy'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # mqs-compat
20
+
21
+ Python-only companion of [QSandbox](https://github.com/Moonlark-Dev/qsandbox).
22
+
23
+ The sandbox itself is a Rust binary; only the *SDK shim* stays in Python, because
24
+ `botpy` hardcodes its endpoints and monkeypatching it is a Python-only job.
25
+
26
+ ```python
27
+ import mqs.compat.botpy # noqa: F401 — before creating botpy.Client
28
+ import botpy
29
+
30
+ client = botpy.Client(intents=botpy.Intents(public_messages=True))
31
+ client.run(appid="10000", secret="mqs-secret-10000")
32
+ ```
33
+
34
+ `MQS_BASE_URL` (default `http://127.0.0.1:5700`) or `patch("http://host:port")`
35
+ selects the sandbox. `patch_from_runtime()` reads `profiles/<name>/runtime.json`
36
+ written by `mqs serve`.
@@ -0,0 +1,18 @@
1
+ # mqs-compat
2
+
3
+ Python-only companion of [QSandbox](https://github.com/Moonlark-Dev/qsandbox).
4
+
5
+ The sandbox itself is a Rust binary; only the *SDK shim* stays in Python, because
6
+ `botpy` hardcodes its endpoints and monkeypatching it is a Python-only job.
7
+
8
+ ```python
9
+ import mqs.compat.botpy # noqa: F401 — before creating botpy.Client
10
+ import botpy
11
+
12
+ client = botpy.Client(intents=botpy.Intents(public_messages=True))
13
+ client.run(appid="10000", secret="mqs-secret-10000")
14
+ ```
15
+
16
+ `MQS_BASE_URL` (default `http://127.0.0.1:5700`) or `patch("http://host:port")`
17
+ selects the sandbox. `patch_from_runtime()` reads `profiles/<name>/runtime.json`
18
+ written by `mqs serve`.
@@ -0,0 +1,8 @@
1
+ """QSandbox Python companion — the SDK shim only.
2
+
3
+ The sandbox server, CLI and control API are the Rust ``mqs`` binary; this
4
+ package exists so that QQ SDKs which hardcode their endpoints (botpy) can be
5
+ pointed at a running sandbox without editing the SDK.
6
+ """
7
+
8
+ __version__ = "0.3.0"
@@ -0,0 +1,5 @@
1
+ """SDK compatibility shims (Python-only, not part of the Rust binary)."""
2
+
3
+ from .botpy import patch, patch_from_runtime
4
+
5
+ __all__ = ["patch", "patch_from_runtime"]
@@ -0,0 +1,78 @@
1
+ """Point the real botpy SDK at QSandbox.
2
+
3
+ botpy hardcodes both the REST domain (``botpy.http.Route.DOMAIN``) and the
4
+ access-token URL (``https://bots.qq.com/app/getAppAccessToken``), so a
5
+ ``base_url`` config alone cannot work. This module monkeypatches both.
6
+
7
+ Usage::
8
+
9
+ import mqs.compat.botpy # noqa: F401 — before creating botpy.Client
10
+ from botpy import Client
11
+
12
+ Client(intents=Intents(public_messages=True), is_sandbox=False)
13
+
14
+ Set ``MQS_BASE_URL`` (default ``http://127.0.0.1:5700``) or call
15
+ ``mqs.compat.botpy.patch("http://127.0.0.1:1234")`` explicitly.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import os
22
+ import time
23
+ from pathlib import Path
24
+ from urllib.parse import urlparse
25
+
26
+
27
+ def patch(base_url: str | None = None) -> str:
28
+ base_url = (base_url or os.environ.get("MQS_BASE_URL") or "http://127.0.0.1:5700").rstrip("/")
29
+ parsed = urlparse(base_url)
30
+ if parsed.scheme not in ("http", "https") or not parsed.netloc:
31
+ raise ValueError(f"invalid base_url: {base_url}")
32
+
33
+ try:
34
+ import botpy.robot as bot_robot
35
+ from botpy import http as bot_http
36
+ except ImportError as exc: # pragma: no cover
37
+ raise RuntimeError("botpy is not installed; pip install qq-botpy") from exc
38
+
39
+ bot_http.Route.SCHEME = parsed.scheme
40
+ bot_http.Route.DOMAIN = parsed.netloc
41
+ bot_http.Route.SANDBOX_DOMAIN = parsed.netloc
42
+
43
+ async def update_access_token(self) -> None: # type: ignore[no-untyped-def]
44
+ import aiohttp
45
+
46
+ async with aiohttp.ClientSession() as session:
47
+ async with session.post(
48
+ f"{base_url}/app/getAppAccessToken",
49
+ json={"appId": self.app_id, "clientSecret": self.secret},
50
+ timeout=aiohttp.ClientTimeout(total=20),
51
+ ) as response:
52
+ data = await response.json()
53
+ if "access_token" not in data or "expires_in" not in data:
54
+ raise RuntimeError(f"mqs rejected the bot credentials: {data}")
55
+ self.access_token = data["access_token"]
56
+ self.expires_in = int(data["expires_in"]) + int(time.time())
57
+
58
+ bot_robot.Token.update_access_token = update_access_token
59
+ return base_url
60
+
61
+
62
+ def patch_from_runtime(home: str | None = None, profile: str | None = None) -> str:
63
+ """Read ``profiles/<name>/runtime.json`` (written by ``mqs serve``) and patch."""
64
+ home_path = Path(home or os.environ.get("MQS_HOME") or "~/.mqs").expanduser()
65
+ name = profile or os.environ.get("MQS_PROFILE") or "default"
66
+ runtime = home_path / "profiles" / name / "runtime.json"
67
+ try:
68
+ payload = json.loads(runtime.read_text(encoding="utf-8"))
69
+ except FileNotFoundError as exc:
70
+ raise RuntimeError(f"no running QSandbox server for profile {name!r} ({runtime} missing)") from exc
71
+ return patch(payload["base_url"])
72
+
73
+
74
+ # importing the module is enough for the default local setup
75
+ try:
76
+ patch()
77
+ except (RuntimeError, ValueError): # botpy not installed — the shim keeps working
78
+ pass
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mqs-compat"
7
+ version = "0.3.0"
8
+ description = "Point the real QQ SDKs (botpy) at a running QSandbox — Python-only compatibility shim"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "AGPL-3.0-or-later"
12
+ authors = [{ name = "Moonlark Dev Team" }]
13
+ keywords = ["qqbot", "sandbox", "botpy", "moonlark"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Software Development :: Testing",
19
+ ]
20
+ # The shim has no hard dependency: it only imports botpy when patching it.
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ botpy = ["qq-botpy"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/Moonlark-Dev/qsandbox"
28
+ Repository = "https://github.com/Moonlark-Dev/qsandbox"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ packages = ["mqs"]