tuitui-bot 1.0.11__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.
Files changed (49) hide show
  1. tuitui_bot-1.0.11/.gitignore +25 -0
  2. tuitui_bot-1.0.11/LICENSE +21 -0
  3. tuitui_bot-1.0.11/PKG-INFO +113 -0
  4. tuitui_bot-1.0.11/README.md +80 -0
  5. tuitui_bot-1.0.11/pyproject.toml +76 -0
  6. tuitui_bot-1.0.11/samples/.env.example +2 -0
  7. tuitui_bot-1.0.11/samples/README.md +34 -0
  8. tuitui_bot-1.0.11/samples/config.py +22 -0
  9. tuitui_bot-1.0.11/samples/receive.py +76 -0
  10. tuitui_bot-1.0.11/samples/send_file.py +40 -0
  11. tuitui_bot-1.0.11/samples/send_interactive.py +60 -0
  12. tuitui_bot-1.0.11/samples/send_mixed.py +47 -0
  13. tuitui_bot-1.0.11/samples/send_post.py +30 -0
  14. tuitui_bot-1.0.11/samples/send_text.py +39 -0
  15. tuitui_bot-1.0.11/src/tuitui_bot/__init__.py +162 -0
  16. tuitui_bot-1.0.11/src/tuitui_bot/_deduplicator.py +59 -0
  17. tuitui_bot-1.0.11/src/tuitui_bot/_mime.py +174 -0
  18. tuitui_bot-1.0.11/src/tuitui_bot/_time.py +86 -0
  19. tuitui_bot-1.0.11/src/tuitui_bot/client.py +46 -0
  20. tuitui_bot-1.0.11/src/tuitui_bot/config.py +101 -0
  21. tuitui_bot-1.0.11/src/tuitui_bot/errors.py +20 -0
  22. tuitui_bot-1.0.11/src/tuitui_bot/event.py +142 -0
  23. tuitui_bot-1.0.11/src/tuitui_bot/events.py +95 -0
  24. tuitui_bot-1.0.11/src/tuitui_bot/file.py +20 -0
  25. tuitui_bot-1.0.11/src/tuitui_bot/file_space.py +194 -0
  26. tuitui_bot-1.0.11/src/tuitui_bot/http.py +132 -0
  27. tuitui_bot-1.0.11/src/tuitui_bot/im.py +313 -0
  28. tuitui_bot-1.0.11/src/tuitui_bot/interactive.py +105 -0
  29. tuitui_bot-1.0.11/src/tuitui_bot/mention.py +13 -0
  30. tuitui_bot-1.0.11/src/tuitui_bot/property.py +85 -0
  31. tuitui_bot-1.0.11/src/tuitui_bot/py.typed +1 -0
  32. tuitui_bot-1.0.11/src/tuitui_bot/records.py +121 -0
  33. tuitui_bot-1.0.11/src/tuitui_bot/teams.py +376 -0
  34. tuitui_bot-1.0.11/src/tuitui_bot/to_target.py +85 -0
  35. tuitui_bot-1.0.11/src/tuitui_bot/upload.py +182 -0
  36. tuitui_bot-1.0.11/src/tuitui_bot/websocket.py +276 -0
  37. tuitui_bot-1.0.11/tests/e2e/README.md +23 -0
  38. tuitui_bot-1.0.11/tests/e2e/config.example.json +17 -0
  39. tuitui_bot-1.0.11/tests/e2e/conftest.py +115 -0
  40. tuitui_bot-1.0.11/tests/e2e/test_im.py +28 -0
  41. tuitui_bot-1.0.11/tests/e2e/test_property.py +16 -0
  42. tuitui_bot-1.0.11/tests/e2e/test_roundtrip.py +49 -0
  43. tuitui_bot-1.0.11/tests/e2e/test_teams_file_space.py +35 -0
  44. tuitui_bot-1.0.11/tests/mock/test_config_http.py +140 -0
  45. tuitui_bot-1.0.11/tests/mock/test_events_records.py +202 -0
  46. tuitui_bot-1.0.11/tests/mock/test_targets_utils.py +109 -0
  47. tuitui_bot-1.0.11/tests/mock/test_teams_file_space_property.py +189 -0
  48. tuitui_bot-1.0.11/tests/mock/test_upload_im.py +151 -0
  49. tuitui_bot-1.0.11/tests/mock/test_websocket_client_public.py +156 -0
@@ -0,0 +1,25 @@
1
+ .DS_Store
2
+ node_modules
3
+ dist/
4
+ coverage/
5
+ build/
6
+ *.egg-info/
7
+ .venv/
8
+ venv/
9
+ __pycache__/
10
+ *.py[cod]
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ .package-install-test/
16
+ tests/e2e/config.local.json
17
+ samples/.env
18
+ samples/dist/
19
+ package-lock.json
20
+ .wolf
21
+ .wiscode/
22
+ docs/
23
+ governance/
24
+ .build-stage/
25
+ .cc-connect
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tuitui.cn
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,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: tuitui-bot
3
+ Version: 1.0.11
4
+ Summary: 推推机器人 Python SDK
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: httpx<1,>=0.27
18
+ Requires-Dist: websockets<18,>=14
19
+ Provides-Extra: dev
20
+ Requires-Dist: build>=1.2; extra == 'dev'
21
+ Requires-Dist: hatchling>=1.27; extra == 'dev'
22
+ Requires-Dist: mypy>=1.13; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=6; extra == 'dev'
25
+ Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
26
+ Requires-Dist: pytest>=8.3; extra == 'dev'
27
+ Requires-Dist: respx>=0.22; extra == 'dev'
28
+ Requires-Dist: ruff>=0.9; extra == 'dev'
29
+ Requires-Dist: twine<7,>=6; extra == 'dev'
30
+ Provides-Extra: samples
31
+ Requires-Dist: python-dotenv>=1; extra == 'samples'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # tuitui-bot
35
+
36
+ 推推机器人异步 Python SDK,移植自 npm 包 `@qihoo/tuitui-bot-sdk`。支持 Python 3.10 及以上版本。
37
+
38
+ ## 安装
39
+
40
+ ```bash
41
+ pip install tuitui-bot
42
+ ```
43
+
44
+ ## 发送消息
45
+
46
+ ```python
47
+ import asyncio
48
+
49
+ from tuitui_bot import TuituiBotClient
50
+
51
+
52
+ async def main() -> None:
53
+ client = TuituiBotClient("your-app-id", "your-app-secret")
54
+ await client.im.send_text(
55
+ to=client.to.account("user-account"),
56
+ text="**你好**,这是一条 `Markdown` 消息。",
57
+ )
58
+
59
+
60
+ asyncio.run(main())
61
+ ```
62
+
63
+ `TuituiBotClient` 构造时不连接网络,默认 HTTP 会话由 SDK 在每次请求后自动关闭,
64
+ 不需要 `async with` 或手动关闭客户端。
65
+
66
+ `client.to` 提供 `account()`、`uid()`、`group()`、`accounts()` 和 `uids()` 五种结构化目标。
67
+ IM 位于 `client.im`,频道和帖子能力位于 `client.teams`。
68
+
69
+ ## 事件订阅
70
+
71
+ 构造客户端或发送消息不会建立 WebSocket。只有调用 `client.event.subscribe()` 才会订阅事件:
72
+
73
+ ```python
74
+ import asyncio
75
+
76
+ from tuitui_bot import TuituiBotClient, TuituiSubscribeOptions
77
+
78
+
79
+ async def main() -> None:
80
+ client = TuituiBotClient("...", "...")
81
+
82
+ async def on_event(raw: dict[str, object]) -> None:
83
+ print(raw)
84
+
85
+ subscription = client.event.subscribe(
86
+ TuituiSubscribeOptions(on_event=on_event),
87
+ )
88
+ try:
89
+ await asyncio.Event().wait()
90
+ finally:
91
+ await subscription.unsubscribe()
92
+
93
+
94
+ asyncio.run(main())
95
+ ```
96
+
97
+ SDK 自动处理鉴权、ACK、保活过滤、去重、心跳超时和断线重连。原始事件字段会被保留;需要正文或
98
+ 媒体时,显式使用 `client.event.render_message_body(data)` 和
99
+ `client.event.get_message_media(data)`。
100
+
101
+ ## API 概览
102
+
103
+ - `client.im`:文本、图文混排、页面、链接、文件、交互卡片、编辑、表情和历史记录。
104
+ - `client.teams`:帖子、回复、编辑、文件、表情、频道、标签、公告、成员和帖子链。
105
+ - `client.file`:底层文件上传。
106
+ - `client.file_space`:文件空间节点、目录和文件。
107
+ - `client.property`:机器人信息、名称、头像、Webhook、交互地址和快捷指令。
108
+ - `client.event`:事件订阅、正文渲染和媒体提取。
109
+ - `client.request()`:调用尚未封装的 Bot API。
110
+
111
+ 文本消息和帖子支持 Markdown;图文混排中的文字是纯文本。上传支持 bytes、Path、本地路径、二进制
112
+ 文件对象、data URL 和 HTTP/HTTPS URL,最大 100MB。远程 URL 下载可通过
113
+ `TuituiClientOptions(fetch_with_ssrf=...)` 注入 SSRF 防护实现。
@@ -0,0 +1,80 @@
1
+ # tuitui-bot
2
+
3
+ 推推机器人异步 Python SDK,移植自 npm 包 `@qihoo/tuitui-bot-sdk`。支持 Python 3.10 及以上版本。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install tuitui-bot
9
+ ```
10
+
11
+ ## 发送消息
12
+
13
+ ```python
14
+ import asyncio
15
+
16
+ from tuitui_bot import TuituiBotClient
17
+
18
+
19
+ async def main() -> None:
20
+ client = TuituiBotClient("your-app-id", "your-app-secret")
21
+ await client.im.send_text(
22
+ to=client.to.account("user-account"),
23
+ text="**你好**,这是一条 `Markdown` 消息。",
24
+ )
25
+
26
+
27
+ asyncio.run(main())
28
+ ```
29
+
30
+ `TuituiBotClient` 构造时不连接网络,默认 HTTP 会话由 SDK 在每次请求后自动关闭,
31
+ 不需要 `async with` 或手动关闭客户端。
32
+
33
+ `client.to` 提供 `account()`、`uid()`、`group()`、`accounts()` 和 `uids()` 五种结构化目标。
34
+ IM 位于 `client.im`,频道和帖子能力位于 `client.teams`。
35
+
36
+ ## 事件订阅
37
+
38
+ 构造客户端或发送消息不会建立 WebSocket。只有调用 `client.event.subscribe()` 才会订阅事件:
39
+
40
+ ```python
41
+ import asyncio
42
+
43
+ from tuitui_bot import TuituiBotClient, TuituiSubscribeOptions
44
+
45
+
46
+ async def main() -> None:
47
+ client = TuituiBotClient("...", "...")
48
+
49
+ async def on_event(raw: dict[str, object]) -> None:
50
+ print(raw)
51
+
52
+ subscription = client.event.subscribe(
53
+ TuituiSubscribeOptions(on_event=on_event),
54
+ )
55
+ try:
56
+ await asyncio.Event().wait()
57
+ finally:
58
+ await subscription.unsubscribe()
59
+
60
+
61
+ asyncio.run(main())
62
+ ```
63
+
64
+ SDK 自动处理鉴权、ACK、保活过滤、去重、心跳超时和断线重连。原始事件字段会被保留;需要正文或
65
+ 媒体时,显式使用 `client.event.render_message_body(data)` 和
66
+ `client.event.get_message_media(data)`。
67
+
68
+ ## API 概览
69
+
70
+ - `client.im`:文本、图文混排、页面、链接、文件、交互卡片、编辑、表情和历史记录。
71
+ - `client.teams`:帖子、回复、编辑、文件、表情、频道、标签、公告、成员和帖子链。
72
+ - `client.file`:底层文件上传。
73
+ - `client.file_space`:文件空间节点、目录和文件。
74
+ - `client.property`:机器人信息、名称、头像、Webhook、交互地址和快捷指令。
75
+ - `client.event`:事件订阅、正文渲染和媒体提取。
76
+ - `client.request()`:调用尚未封装的 Bot API。
77
+
78
+ 文本消息和帖子支持 Markdown;图文混排中的文字是纯文本。上传支持 bytes、Path、本地路径、二进制
79
+ 文件对象、data URL 和 HTTP/HTTPS URL,最大 100MB。远程 URL 下载可通过
80
+ `TuituiClientOptions(fetch_with_ssrf=...)` 注入 SSRF 防护实现。
@@ -0,0 +1,76 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tuitui-bot"
7
+ version = "1.0.11"
8
+ description = "推推机器人 Python SDK"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ dependencies = [
14
+ "httpx>=0.27,<1",
15
+ "websockets>=14,<18",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Typing :: Typed",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "build>=1.2",
32
+ "hatchling>=1.27",
33
+ "mypy>=1.13",
34
+ "pytest>=8.3",
35
+ "pytest-asyncio>=0.24",
36
+ "pytest-cov>=6",
37
+ "pytest-timeout>=2.3",
38
+ "respx>=0.22",
39
+ "ruff>=0.9",
40
+ "twine>=6,<7",
41
+ ]
42
+ samples = ["python-dotenv>=1"]
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/tuitui_bot"]
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ exclude = [
49
+ "/.codex",
50
+ "/AGENTS.md",
51
+ "/MIGRATION.md",
52
+ "/dist",
53
+ ]
54
+
55
+ [tool.pytest.ini_options]
56
+ addopts = "-ra"
57
+ asyncio_mode = "auto"
58
+ testpaths = ["tests/mock"]
59
+ markers = ["e2e: 调用真实推推服务的测试"]
60
+
61
+ [tool.ruff]
62
+ line-length = 100
63
+ target-version = "py310"
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E", "F", "I", "UP", "B", "ASYNC", "RUF"]
67
+ ignore = ["RUF001", "RUF002", "RUF003"]
68
+
69
+ [tool.ruff.format]
70
+ quote-style = "double"
71
+
72
+ [tool.mypy]
73
+ python_version = "3.10"
74
+ strict = true
75
+ packages = ["tuitui_bot"]
76
+ mypy_path = "src"
@@ -0,0 +1,2 @@
1
+ TUITUI_BOT_APPID=
2
+ TUITUI_BOT_SECRET=
@@ -0,0 +1,34 @@
1
+ # SDK 使用示例
2
+
3
+ 所有示例都需要机器人的 App ID 和 App Secret。复制 `.env.example` 为 `.env`
4
+ 并填写:
5
+
6
+ ```dotenv
7
+ TUITUI_BOT_APPID=机器人 App ID
8
+ TUITUI_BOT_SECRET=机器人 App Secret
9
+ ```
10
+
11
+ 安装 SDK 和示例依赖:
12
+
13
+ ```bash
14
+ pip install -e ".[samples]"
15
+ ```
16
+
17
+ 发送目标通过命令行参数指定,与 JavaScript 版示例一致:
18
+
19
+ ```bash
20
+ python samples/send_text.py --account alice
21
+ python samples/send_text.py --uid 123456
22
+ python samples/send_text.py --group 987654
23
+
24
+ python samples/send_file.py --account alice
25
+ python samples/send_mixed.py --account alice
26
+ python samples/send_interactive.py --account alice
27
+ python samples/send_post.py --team 123456 --channel 789012
28
+ python samples/receive.py
29
+ ```
30
+
31
+ 所有 `send_*.py` 示例都会在发送成功后输出服务端返回的完整 JSON。
32
+
33
+ `receive.py` 连接成功后会立即输出监听状态,随后持续输出原始事件和解析文本。
34
+ 按 `Ctrl+C` 取消订阅并退出。`.env` 已被 Git 忽略,不要提交真实凭证。
@@ -0,0 +1,22 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+
5
+ from dotenv import load_dotenv
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class SampleConfig:
10
+ app_id: str
11
+ app_secret: str
12
+
13
+
14
+ def load_sample_config() -> SampleConfig:
15
+ env_path = Path(__file__).with_name(".env")
16
+ load_dotenv(dotenv_path=env_path)
17
+
18
+ app_id = os.getenv("TUITUI_BOT_APPID", "").strip()
19
+ app_secret = os.getenv("TUITUI_BOT_SECRET", "").strip()
20
+ if not app_id or not app_secret:
21
+ raise RuntimeError(f"请设置 TUITUI_BOT_APPID 和 TUITUI_BOT_SECRET,或在 {env_path} 中填写")
22
+ return SampleConfig(app_id=app_id, app_secret=app_secret)
@@ -0,0 +1,76 @@
1
+ import asyncio
2
+ import json
3
+ import sys
4
+ from datetime import datetime, timezone
5
+
6
+ from config import load_sample_config
7
+
8
+ from tuitui_bot import TuituiBotClient, TuituiRawEvent, TuituiSubscribeOptions
9
+
10
+
11
+ def timestamp() -> str:
12
+ return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
13
+
14
+
15
+ def js_value(value: object) -> str:
16
+ if value is None:
17
+ return "undefined"
18
+ if isinstance(value, bool):
19
+ return str(value).lower()
20
+ return str(value)
21
+
22
+
23
+ async def main() -> None:
24
+ config = load_sample_config()
25
+ client = TuituiBotClient(config.app_id, config.app_secret)
26
+
27
+ def on_connected() -> None:
28
+ print(f"[{timestamp()}] 已连接bot({config.app_id}),正在监听事件", flush=True)
29
+
30
+ def on_event(event: TuituiRawEvent) -> None:
31
+ body = event.get("body")
32
+ event_name = body.get("event", "unknown") if isinstance(body, dict) else "unknown"
33
+
34
+ print(f"[{timestamp()}] 收到事件:{event_name}", flush=True)
35
+ print(json.dumps(event, ensure_ascii=False, indent=2), flush=True)
36
+
37
+ if not isinstance(body, dict):
38
+ return
39
+ data = body.get("data")
40
+
41
+ if event_name == client.event.SINGLE_CHAT:
42
+ text = client.event.render_message_body(data)
43
+ print(f"[{timestamp()}] 单聊解析文本:\n{text}", flush=True)
44
+ elif event_name == client.event.GROUP_CHAT:
45
+ text = client.event.render_message_body(data)
46
+ print(f"[{timestamp()}] 群聊解析文本:\n{text}", flush=True)
47
+ at_me = data.get("at_me", False) if isinstance(data, dict) else False
48
+ print(f"[{timestamp()}] 群聊是否@自己:{js_value(at_me)}", flush=True)
49
+ elif event_name in (client.event.TEAMS_POST_CREATE, client.event.TEAMS_POST_MODIFY):
50
+ content = data.get("content", "") if isinstance(data, dict) else ""
51
+ at_me = data.get("at_me") if isinstance(data, dict) else None
52
+ print(f"[{timestamp()}] 团队帖子解析文本:\n{content}", flush=True)
53
+ print(f"[{timestamp()}] 团队帖子是否@自己:{js_value(at_me)}", flush=True)
54
+
55
+ def on_error(error: Exception) -> None:
56
+ print(f"[{timestamp()}] WebSocket 错误", error, file=sys.stderr, flush=True)
57
+
58
+ subscription = client.event.subscribe(
59
+ TuituiSubscribeOptions(
60
+ on_connected=on_connected,
61
+ on_event=on_event,
62
+ on_error=on_error,
63
+ )
64
+ )
65
+ try:
66
+ await asyncio.Event().wait()
67
+ finally:
68
+ print(f"\n[{timestamp()}] 停止监听", flush=True)
69
+ await subscription.unsubscribe()
70
+
71
+
72
+ if __name__ == "__main__":
73
+ try:
74
+ asyncio.run(main())
75
+ except KeyboardInterrupt:
76
+ pass
@@ -0,0 +1,40 @@
1
+ import asyncio
2
+ import json
3
+ from argparse import ArgumentParser, Namespace
4
+ from pathlib import Path
5
+
6
+ from config import load_sample_config
7
+
8
+ from tuitui_bot import ToTarget, TuituiBotClient
9
+
10
+
11
+ def parse_args() -> Namespace:
12
+ parser = ArgumentParser(description="发送 IM 文件")
13
+ target = parser.add_mutually_exclusive_group(required=True)
14
+ target.add_argument("--account", help="接收者账号")
15
+ target.add_argument("--uid", help="接收者 UID")
16
+ target.add_argument("--group", help="接收群 ID")
17
+ return parser.parse_args()
18
+
19
+
20
+ def resolve_target(client: TuituiBotClient, args: Namespace) -> ToTarget:
21
+ if args.account:
22
+ return client.to.account(args.account)
23
+ if args.uid:
24
+ return client.to.uid(args.uid)
25
+ return client.to.group(args.group)
26
+
27
+
28
+ async def main() -> None:
29
+ args = parse_args()
30
+ config = load_sample_config()
31
+ client = TuituiBotClient(config.app_id, config.app_secret)
32
+ response = await client.im.send_file(
33
+ to=resolve_target(client, args),
34
+ source=Path(__file__).with_name("README.md"),
35
+ )
36
+ print("文件发送成功")
37
+ print(json.dumps(response, ensure_ascii=False, indent=2))
38
+
39
+
40
+ asyncio.run(main())
@@ -0,0 +1,60 @@
1
+ import asyncio
2
+ import json
3
+ from argparse import ArgumentParser, Namespace
4
+
5
+ from config import load_sample_config
6
+
7
+ from tuitui_bot import ToTarget, TuituiBotClient
8
+
9
+
10
+ def parse_args() -> Namespace:
11
+ parser = ArgumentParser(description="发送 IM 交互消息")
12
+ target = parser.add_mutually_exclusive_group(required=True)
13
+ target.add_argument("--account", help="接收者账号")
14
+ target.add_argument("--uid", help="接收者 UID")
15
+ target.add_argument("--group", help="接收群 ID")
16
+ return parser.parse_args()
17
+
18
+
19
+ def resolve_target(client: TuituiBotClient, args: Namespace) -> ToTarget:
20
+ if args.account:
21
+ return client.to.account(args.account)
22
+ if args.uid:
23
+ return client.to.uid(args.uid)
24
+ return client.to.group(args.group)
25
+
26
+
27
+ async def main() -> None:
28
+ args = parse_args()
29
+ config = load_sample_config()
30
+ interactive = {
31
+ "head": {"text": "交互消息示例"},
32
+ "body": {"content": "请选择确认或取消"},
33
+ "action": [
34
+ {
35
+ "text": "确认",
36
+ "name": "confirm",
37
+ "value": "confirm",
38
+ "color": "FFFFFF",
39
+ "bgcolor": "#3873FA",
40
+ },
41
+ {
42
+ "text": "取消",
43
+ "name": "cancel",
44
+ "value": "cancel",
45
+ "color": "FFFFFF",
46
+ "bgcolor": "#FA5151",
47
+ },
48
+ ],
49
+ }
50
+
51
+ client = TuituiBotClient(config.app_id, config.app_secret)
52
+ response = await client.im.send_interactive(
53
+ to=resolve_target(client, args),
54
+ interactive=interactive,
55
+ )
56
+ print("交互消息发送成功")
57
+ print(json.dumps(response, ensure_ascii=False, indent=2))
58
+
59
+
60
+ asyncio.run(main())
@@ -0,0 +1,47 @@
1
+ import asyncio
2
+ import json
3
+ from argparse import ArgumentParser, Namespace
4
+
5
+ from config import load_sample_config
6
+
7
+ from tuitui_bot import ToTarget, TuituiBotClient, TuituiMixedItem
8
+
9
+
10
+ def parse_args() -> Namespace:
11
+ parser = ArgumentParser(description="发送 IM Mixed 消息")
12
+ target = parser.add_mutually_exclusive_group(required=True)
13
+ target.add_argument("--account", help="接收者账号")
14
+ target.add_argument("--uid", help="接收者 UID")
15
+ target.add_argument("--group", help="接收群 ID")
16
+ return parser.parse_args()
17
+
18
+
19
+ def resolve_target(client: TuituiBotClient, args: Namespace) -> ToTarget:
20
+ if args.account:
21
+ return client.to.account(args.account)
22
+ if args.uid:
23
+ return client.to.uid(args.uid)
24
+ return client.to.group(args.group)
25
+
26
+
27
+ async def main() -> None:
28
+ args = parse_args()
29
+ config = load_sample_config()
30
+ sample_image = (
31
+ "data:image/png;base64,"
32
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42Y"
33
+ "AAAAASUVORK5CYII="
34
+ )
35
+ items: list[TuituiMixedItem] = [
36
+ {"type": "text", "text": "Mixed 消息中的第一段文字"},
37
+ {"type": "image", "source": sample_image},
38
+ {"type": "text", "text": "Mixed 消息中的第二段文字"},
39
+ ]
40
+
41
+ client = TuituiBotClient(config.app_id, config.app_secret)
42
+ response = await client.im.send_mixed(to=resolve_target(client, args), items=items)
43
+ print("Mixed 消息发送成功")
44
+ print(json.dumps(response, ensure_ascii=False, indent=2))
45
+
46
+
47
+ asyncio.run(main())
@@ -0,0 +1,30 @@
1
+ import asyncio
2
+ import json
3
+ from argparse import ArgumentParser, Namespace
4
+
5
+ from config import load_sample_config
6
+
7
+ from tuitui_bot import TuituiBotClient
8
+
9
+
10
+ def parse_args() -> Namespace:
11
+ parser = ArgumentParser(description="发送频道帖子")
12
+ parser.add_argument("--team", required=True, help="团队 ID")
13
+ parser.add_argument("--channel", required=True, help="频道 ID")
14
+ return parser.parse_args()
15
+
16
+
17
+ async def main() -> None:
18
+ args = parse_args()
19
+ config = load_sample_config()
20
+ client = TuituiBotClient(config.app_id, config.app_secret)
21
+ response = await client.teams.send_post(
22
+ team_id=args.team,
23
+ channel_id=args.channel,
24
+ text="**Python SDK** 频道帖子",
25
+ )
26
+ print("频道帖子发送成功")
27
+ print(json.dumps(response, ensure_ascii=False, indent=2))
28
+
29
+
30
+ asyncio.run(main())
@@ -0,0 +1,39 @@
1
+ import asyncio
2
+ import json
3
+ from argparse import ArgumentParser, Namespace
4
+
5
+ from config import load_sample_config
6
+
7
+ from tuitui_bot import ToTarget, TuituiBotClient
8
+
9
+
10
+ def parse_args() -> Namespace:
11
+ parser = ArgumentParser(description="发送 IM 文本消息")
12
+ target = parser.add_mutually_exclusive_group(required=True)
13
+ target.add_argument("--account", help="接收者账号")
14
+ target.add_argument("--uid", help="接收者 UID")
15
+ target.add_argument("--group", help="接收群 ID")
16
+ return parser.parse_args()
17
+
18
+
19
+ def resolve_target(client: TuituiBotClient, args: Namespace) -> ToTarget:
20
+ if args.account:
21
+ return client.to.account(args.account)
22
+ if args.uid:
23
+ return client.to.uid(args.uid)
24
+ return client.to.group(args.group)
25
+
26
+
27
+ async def main() -> None:
28
+ args = parse_args()
29
+ config = load_sample_config()
30
+ client = TuituiBotClient(config.app_id, config.app_secret)
31
+ response = await client.im.send_text(
32
+ to=resolve_target(client, args),
33
+ text="**你好**,这是一条来自 Python SDK 的消息。",
34
+ )
35
+ print("消息发送成功")
36
+ print(json.dumps(response, ensure_ascii=False, indent=2))
37
+
38
+
39
+ asyncio.run(main())