nonebot-plugin-onebot2tg 1.0.2__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,176 @@
1
+ Metadata-Version: 2.3
2
+ Name: nonebot-plugin-onebot2tg
3
+ Version: 1.0.2
4
+ Summary: OneBot V11 与 Telegram 消息转发插件
5
+ Author: SoulGateKey
6
+ Author-email: SoulGateKey <SoulGateKey@gmail.com>
7
+ Requires-Dist: httpx>=0.27.0,<1.0.0
8
+ Requires-Dist: nonebot-plugin-alconna>=0.60.0,<1.0.0
9
+ Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
10
+ Requires-Dist: nonebot-plugin-localstore>=0.7.4,<1.0.0
11
+ Requires-Dist: nonebot-plugin-uninfo>=0.10.0,<1.0.0
12
+ Requires-Dist: nonebot2>=2.5.0,<3.0.0
13
+ Requires-Python: >=3.10
14
+ Project-URL: Homepage, https://github.com/SoulGateKey/nonebot-plugin-onebot2tg
15
+ Project-URL: Issues, https://github.com/SoulGateKey/nonebot-plugin-onebot2tg/issues
16
+ Project-URL: Repository, https://github.com/SoulGateKey/nonebot-plugin-onebot2tg.git
17
+ Description-Content-Type: text/markdown
18
+
19
+ <div align="center">
20
+ <a href="https://v2.nonebot.dev/store">
21
+ <img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
22
+
23
+ ## ✨ nonebot-plugin-onebot2tg ✨
24
+ [![LICENSE](https://img.shields.io/github/license/SoulGateKey/nonebot-plugin-onebot2tg.svg)](./LICENSE)
25
+ [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-onebot2tg.svg)](https://pypi.python.org/pypi/nonebot-plugin-onebot2tg)
26
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://www.python.org)
27
+ [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
28
+ <br/>
29
+ [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
30
+ [![pre-commit](https://results.pre-commit.ci/badge/github/SoulGateKey/nonebot-plugin-onebot2tg/master.svg)](https://results.pre-commit.ci/latest/github/SoulGateKey/nonebot-plugin-onebot2tg/master)
31
+ </div>
32
+
33
+ OneBot V11 与 Telegram 消息转发插件
34
+
35
+
36
+ ## 📖 功能
37
+
38
+ - [x] **互通模式**:指定一个 QQ 群与一个 Telegram 群/频道双向互通,消息实时同步
39
+ - [x] **转发模式**:将 QQ 所有接收到的消息单向转发到 Telegram 私聊/频道/群聊
40
+ - [x] 互通模式和转发模式可同时开启不会重复发送消息
41
+ - [x] Telegram 图片/贴纸下载会自动走适配器配置的代理
42
+ - [x] **消息类型支持**:文本、图片、贴纸
43
+ - [ ] 贴纸图片过大时自动压缩?
44
+ - [ ] GIF转发
45
+ - [ ] 显示具体表情而不是表情ID
46
+ - [ ] 显示@人的昵称而不是QQ号
47
+ - [ ] 双向转发Reply消息 这个可能需要做数据库
48
+ - [ ] 在实现Reply消息后 实现转发模式双向转发
49
+ ...
50
+
51
+ ## 💿 安装
52
+ <details open>
53
+ <summary>使用 nb-cli 安装</summary>
54
+ 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
55
+
56
+ nb plugin install nonebot-plugin-onebot2tg --upgrade
57
+ 使用 **pypi** 源安装
58
+
59
+ nb plugin install nonebot-plugin-onebot2tg --upgrade -i "https://pypi.org/simple"
60
+ 使用**清华源**安装
61
+
62
+ nb plugin install nonebot-plugin-onebot2tg --upgrade -i "https://pypi.tuna.tsinghua.edu.cn/simple"
63
+
64
+
65
+ </details>
66
+
67
+ <details>
68
+ <summary>使用包管理器安装</summary>
69
+ 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
70
+
71
+ <details open>
72
+ <summary>uv</summary>
73
+
74
+ uv add nonebot-plugin-onebot2tg
75
+ 安装仓库 master 分支
76
+
77
+ uv add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
78
+ </details>
79
+
80
+ <details>
81
+ <summary>pdm</summary>
82
+
83
+ pdm add nonebot-plugin-onebot2tg
84
+ 安装仓库 master 分支
85
+
86
+ pdm add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
87
+ </details>
88
+ <details>
89
+ <summary>poetry</summary>
90
+
91
+ poetry add nonebot-plugin-onebot2tg
92
+ 安装仓库 master 分支
93
+
94
+ poetry add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
95
+ </details>
96
+
97
+ 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
98
+
99
+ plugins = ["nonebot_plugin_onebot2tg"]
100
+
101
+ </details>
102
+
103
+ <details>
104
+ <summary>使用 nbr 安装(使用 uv 管理依赖可用)</summary>
105
+
106
+ [nbr](https://github.com/fllesser/nbr) 是一个基于 uv 的 nb-cli,可以方便地管理 nonebot2
107
+
108
+ nbr plugin install nonebot-plugin-onebot2tg
109
+ 使用 **pypi** 源安装
110
+
111
+ nbr plugin install nonebot-plugin-onebot2tg -i "https://pypi.org/simple"
112
+ 使用**清华源**安装
113
+
114
+ nbr plugin install nonebot-plugin-onebot2tg -i "https://pypi.tuna.tsinghua.edu.cn/simple"
115
+
116
+ </details>
117
+
118
+ ## 依赖适配器
119
+
120
+ - [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) (OneBot V11)
121
+ - [nonebot-adapter-telegram](https://github.com/nonebot/adapter-telegram) (Telegram)
122
+
123
+ ## ⚙️ 配置
124
+
125
+ 在 `.env` 文件中添加以下配置项:
126
+
127
+ ### 模式开关
128
+
129
+ | 配置项 | 类型 | 默认值 | 说明 |
130
+ |--------|------|--------|------|
131
+ | `ONEBOT2TG_ENABLE_BRIDGE` | `bool` | `True` | 开启互通模式(双向转发,需配置互通群号,默认开启) |
132
+ | `ONEBOT2TG_ENABLE_FORWARD` | `bool` | `False` | 开启转发模式(QQ 所有消息单向转发到 TG) |
133
+
134
+ ### 互通模式配置
135
+
136
+ | 配置项 | 类型 | 默认值 | 说明 |
137
+ |--------|------|--------|------|
138
+ | `ONEBOT2TG_BRIDGE_GROUP_ID` | `str \| int` | `""` | 互通模式下,要互通的 QQ 群号 |
139
+ | `ONEBOT2TG_BRIDGE_TG_CHAT_ID` | `str \| int` | `""` | 互通模式下,TG 对应的 chat_id(群或频道) |
140
+
141
+ ### 转发模式配置
142
+
143
+ | 配置项 | 类型 | 默认值 | 说明 |
144
+ |--------|------|--------|------|
145
+ | `ONEBOT2TG_FORWARD_TARGET_CHAT_ID` | `str \| int` | `""` | 转发模式下,QQ 消息转发到 TG 的目标 chat_id 或 用户ID |
146
+
147
+ ## 🎉 使用示例
148
+
149
+ ### 场景 1:QQ 群与 Telegram 群互通
150
+
151
+ ```env
152
+ ONEBOT2TG_ENABLE_BRIDGE=true
153
+ ONEBOT2TG_ENABLE_FORWARD=false
154
+ ONEBOT2TG_BRIDGE_GROUP_ID=123456789
155
+ ONEBOT2TG_BRIDGE_TG_CHAT_ID=-1001234567890
156
+ ```
157
+
158
+ 配置后,该 QQ 群与 Telegram 群的消息会实时双向同步。
159
+
160
+ ### 场景 2:QQ 消息单向转发到 Telegram
161
+
162
+ ```env
163
+ ONEBOT2TG_ENABLE_BRIDGE=false
164
+ ONEBOT2TG_ENABLE_FORWARD=true
165
+ ONEBOT2TG_FORWARD_TARGET_CHAT_ID=-1001234567890
166
+ ```
167
+
168
+ 配置后,QQ 群/私聊接收到的所有消息会单向转发到指定的 Telegram 聊天。
169
+
170
+ ### 场景 3:互通和转发模式同时开启
171
+
172
+ ## 注意事项
173
+ - 目前仅测试过QQ与TG互转,其他onebot实现欢迎提交测试结果及PR
174
+ - 互通模式为一对一配置,暂不支持多群互通
175
+ - 转发模式为单向(QQ → TG),TG 消息不会转发回 QQ
176
+
@@ -0,0 +1,158 @@
1
+ <div align="center">
2
+ <a href="https://v2.nonebot.dev/store">
3
+ <img src="https://raw.githubusercontent.com/fllesser/nonebot-plugin-template/refs/heads/resource/.docs/NoneBotPlugin.svg" width="310" alt="logo"></a>
4
+
5
+ ## ✨ nonebot-plugin-onebot2tg ✨
6
+ [![LICENSE](https://img.shields.io/github/license/SoulGateKey/nonebot-plugin-onebot2tg.svg)](./LICENSE)
7
+ [![pypi](https://img.shields.io/pypi/v/nonebot-plugin-onebot2tg.svg)](https://pypi.python.org/pypi/nonebot-plugin-onebot2tg)
8
+ [![python](https://img.shields.io/badge/python-3.10|3.11|3.12|3.13-blue.svg)](https://www.python.org)
9
+ [![uv](https://img.shields.io/badge/package%20manager-uv-black?style=flat-square&logo=uv)](https://github.com/astral-sh/uv)
10
+ <br/>
11
+ [![ruff](https://img.shields.io/badge/code%20style-ruff-black?style=flat-square&logo=ruff)](https://github.com/astral-sh/ruff)
12
+ [![pre-commit](https://results.pre-commit.ci/badge/github/SoulGateKey/nonebot-plugin-onebot2tg/master.svg)](https://results.pre-commit.ci/latest/github/SoulGateKey/nonebot-plugin-onebot2tg/master)
13
+ </div>
14
+
15
+ OneBot V11 与 Telegram 消息转发插件
16
+
17
+
18
+ ## 📖 功能
19
+
20
+ - [x] **互通模式**:指定一个 QQ 群与一个 Telegram 群/频道双向互通,消息实时同步
21
+ - [x] **转发模式**:将 QQ 所有接收到的消息单向转发到 Telegram 私聊/频道/群聊
22
+ - [x] 互通模式和转发模式可同时开启不会重复发送消息
23
+ - [x] Telegram 图片/贴纸下载会自动走适配器配置的代理
24
+ - [x] **消息类型支持**:文本、图片、贴纸
25
+ - [ ] 贴纸图片过大时自动压缩?
26
+ - [ ] GIF转发
27
+ - [ ] 显示具体表情而不是表情ID
28
+ - [ ] 显示@人的昵称而不是QQ号
29
+ - [ ] 双向转发Reply消息 这个可能需要做数据库
30
+ - [ ] 在实现Reply消息后 实现转发模式双向转发
31
+ ...
32
+
33
+ ## 💿 安装
34
+ <details open>
35
+ <summary>使用 nb-cli 安装</summary>
36
+ 在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
37
+
38
+ nb plugin install nonebot-plugin-onebot2tg --upgrade
39
+ 使用 **pypi** 源安装
40
+
41
+ nb plugin install nonebot-plugin-onebot2tg --upgrade -i "https://pypi.org/simple"
42
+ 使用**清华源**安装
43
+
44
+ nb plugin install nonebot-plugin-onebot2tg --upgrade -i "https://pypi.tuna.tsinghua.edu.cn/simple"
45
+
46
+
47
+ </details>
48
+
49
+ <details>
50
+ <summary>使用包管理器安装</summary>
51
+ 在 nonebot2 项目的插件目录下, 打开命令行, 根据你使用的包管理器, 输入相应的安装命令
52
+
53
+ <details open>
54
+ <summary>uv</summary>
55
+
56
+ uv add nonebot-plugin-onebot2tg
57
+ 安装仓库 master 分支
58
+
59
+ uv add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
60
+ </details>
61
+
62
+ <details>
63
+ <summary>pdm</summary>
64
+
65
+ pdm add nonebot-plugin-onebot2tg
66
+ 安装仓库 master 分支
67
+
68
+ pdm add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
69
+ </details>
70
+ <details>
71
+ <summary>poetry</summary>
72
+
73
+ poetry add nonebot-plugin-onebot2tg
74
+ 安装仓库 master 分支
75
+
76
+ poetry add git+https://github.com/SoulGateKey/nonebot-plugin-onebot2tg@master
77
+ </details>
78
+
79
+ 打开 nonebot2 项目根目录下的 `pyproject.toml` 文件, 在 `[tool.nonebot]` 部分追加写入
80
+
81
+ plugins = ["nonebot_plugin_onebot2tg"]
82
+
83
+ </details>
84
+
85
+ <details>
86
+ <summary>使用 nbr 安装(使用 uv 管理依赖可用)</summary>
87
+
88
+ [nbr](https://github.com/fllesser/nbr) 是一个基于 uv 的 nb-cli,可以方便地管理 nonebot2
89
+
90
+ nbr plugin install nonebot-plugin-onebot2tg
91
+ 使用 **pypi** 源安装
92
+
93
+ nbr plugin install nonebot-plugin-onebot2tg -i "https://pypi.org/simple"
94
+ 使用**清华源**安装
95
+
96
+ nbr plugin install nonebot-plugin-onebot2tg -i "https://pypi.tuna.tsinghua.edu.cn/simple"
97
+
98
+ </details>
99
+
100
+ ## 依赖适配器
101
+
102
+ - [nonebot-adapter-onebot](https://github.com/nonebot/adapter-onebot) (OneBot V11)
103
+ - [nonebot-adapter-telegram](https://github.com/nonebot/adapter-telegram) (Telegram)
104
+
105
+ ## ⚙️ 配置
106
+
107
+ 在 `.env` 文件中添加以下配置项:
108
+
109
+ ### 模式开关
110
+
111
+ | 配置项 | 类型 | 默认值 | 说明 |
112
+ |--------|------|--------|------|
113
+ | `ONEBOT2TG_ENABLE_BRIDGE` | `bool` | `True` | 开启互通模式(双向转发,需配置互通群号,默认开启) |
114
+ | `ONEBOT2TG_ENABLE_FORWARD` | `bool` | `False` | 开启转发模式(QQ 所有消息单向转发到 TG) |
115
+
116
+ ### 互通模式配置
117
+
118
+ | 配置项 | 类型 | 默认值 | 说明 |
119
+ |--------|------|--------|------|
120
+ | `ONEBOT2TG_BRIDGE_GROUP_ID` | `str \| int` | `""` | 互通模式下,要互通的 QQ 群号 |
121
+ | `ONEBOT2TG_BRIDGE_TG_CHAT_ID` | `str \| int` | `""` | 互通模式下,TG 对应的 chat_id(群或频道) |
122
+
123
+ ### 转发模式配置
124
+
125
+ | 配置项 | 类型 | 默认值 | 说明 |
126
+ |--------|------|--------|------|
127
+ | `ONEBOT2TG_FORWARD_TARGET_CHAT_ID` | `str \| int` | `""` | 转发模式下,QQ 消息转发到 TG 的目标 chat_id 或 用户ID |
128
+
129
+ ## 🎉 使用示例
130
+
131
+ ### 场景 1:QQ 群与 Telegram 群互通
132
+
133
+ ```env
134
+ ONEBOT2TG_ENABLE_BRIDGE=true
135
+ ONEBOT2TG_ENABLE_FORWARD=false
136
+ ONEBOT2TG_BRIDGE_GROUP_ID=123456789
137
+ ONEBOT2TG_BRIDGE_TG_CHAT_ID=-1001234567890
138
+ ```
139
+
140
+ 配置后,该 QQ 群与 Telegram 群的消息会实时双向同步。
141
+
142
+ ### 场景 2:QQ 消息单向转发到 Telegram
143
+
144
+ ```env
145
+ ONEBOT2TG_ENABLE_BRIDGE=false
146
+ ONEBOT2TG_ENABLE_FORWARD=true
147
+ ONEBOT2TG_FORWARD_TARGET_CHAT_ID=-1001234567890
148
+ ```
149
+
150
+ 配置后,QQ 群/私聊接收到的所有消息会单向转发到指定的 Telegram 聊天。
151
+
152
+ ### 场景 3:互通和转发模式同时开启
153
+
154
+ ## 注意事项
155
+ - 目前仅测试过QQ与TG互转,其他onebot实现欢迎提交测试结果及PR
156
+ - 互通模式为一对一配置,暂不支持多群互通
157
+ - 转发模式为单向(QQ → TG),TG 消息不会转发回 QQ
158
+
@@ -0,0 +1,138 @@
1
+ [project]
2
+ name = "nonebot-plugin-onebot2tg"
3
+ version = "1.0.2"
4
+ description = "OneBot V11 与 Telegram 消息转发插件"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{ name = "SoulGateKey", email = "SoulGateKey@gmail.com" }]
8
+ dependencies = [
9
+ "httpx>=0.27.0,<1.0.0",
10
+ "nonebot-plugin-alconna>=0.60.0,<1.0.0",
11
+ "nonebot-plugin-apscheduler>=0.5.0,<1.0.0",
12
+ "nonebot-plugin-localstore>=0.7.4,<1.0.0",
13
+ "nonebot-plugin-uninfo>=0.10.0,<1.0.0",
14
+ "nonebot2>=2.5.0,<3.0.0",
15
+ ]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/SoulGateKey/nonebot-plugin-onebot2tg"
19
+ Issues = "https://github.com/SoulGateKey/nonebot-plugin-onebot2tg/issues"
20
+ Repository = "https://github.com/SoulGateKey/nonebot-plugin-onebot2tg.git"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "bump-my-version>=1.2.7",
25
+ "nonebot2[fastapi]>=2.5.0,<3.0.0",
26
+ "poethepoet>=0.42.1",
27
+ "ruff>=0.15.2,<1.0.0",
28
+ { include-group = "test" },
29
+ ]
30
+ test = [
31
+ "nonebot-adapter-onebot>=2.4.6,<3.0.0",
32
+ "nonebot-adapter-telegram>=0.1.0b1,<1.0.0",
33
+ "nonebot2[fastapi]>=2.5.0,<3.0.0",
34
+ "nonebug>=0.4.4,<1.0.0",
35
+ "pytest>=9.0.2,<10.0.0",
36
+ "poethepoet>=0.42.1",
37
+ "pytest-asyncio>=1.3.0,<1.4.0",
38
+ "pytest-cov>=7.0.0",
39
+ "pytest-xdist>=3.8.0,<4.0.0",
40
+ ]
41
+
42
+ [build-system]
43
+ requires = ["uv_build>=0.10.0,<0.11.0"]
44
+ build-backend = "uv_build"
45
+
46
+ [tool.bumpversion]
47
+ current_version = "1.0.2"
48
+ commit = true
49
+ message = "release: bump version from {current_version} to {new_version}"
50
+ tag = true
51
+
52
+ [[tool.bumpversion.files]]
53
+ filename = "pyproject.toml"
54
+ search = 'version = "{current_version}"'
55
+ replace = 'version = "{new_version}"'
56
+
57
+ [tool.coverage.report]
58
+ exclude_lines = [
59
+ "raise NotImplementedError",
60
+ "if TYPE_CHECKING:",
61
+ "@overload",
62
+ "except ImportError:",
63
+ ]
64
+
65
+ [tool.nonebot]
66
+ plugins = ["nonebot_plugin_onebot2tg"]
67
+
68
+ [tool.poe.tasks]
69
+ test = "pytest --cov=src --cov-report xml --junitxml=./junit.xml -n auto"
70
+ bump = "bump-my-version bump"
71
+ show-bump = "bump-my-version show-bump"
72
+
73
+ [tool.pyright]
74
+ pythonVersion = "3.10"
75
+ pythonPlatform = "All"
76
+ defineConstant = { PYDANTIC_V2 = true }
77
+ executionEnvironments = [
78
+ { root = "./tests", extraPaths = [
79
+ "./src",
80
+ ] },
81
+ { root = "./src" },
82
+ ]
83
+ typeCheckingMode = "standard"
84
+ disableBytesTypePromotions = true
85
+
86
+ [tool.pytest]
87
+ addopts = [
88
+ "--import-mode=prepend",
89
+ "--strict-markers",
90
+ "--tb=short",
91
+ "-ra",
92
+ "-s",
93
+ "-v",
94
+ ]
95
+ pythonpath = ["src"]
96
+ asyncio_mode = "auto"
97
+ asyncio_default_fixture_loop_scope = "session"
98
+
99
+ [tool.ruff]
100
+ line-length = 88
101
+
102
+ [tool.ruff.format]
103
+ line-ending = "lf"
104
+
105
+ [tool.ruff.lint]
106
+ select = [
107
+ "F",
108
+ "W",
109
+ "E",
110
+ "I",
111
+ "UP",
112
+ "ASYNC",
113
+ "C4",
114
+ "T10",
115
+ "T20",
116
+ "PYI",
117
+ "PT",
118
+ "Q",
119
+ "TID",
120
+ "RUF",
121
+ ]
122
+ ignore = [
123
+ "E402",
124
+ "UP037",
125
+ "RUF001",
126
+ "RUF002",
127
+ "RUF003",
128
+ "W191",
129
+ "TID252",
130
+ ]
131
+
132
+ [tool.ruff.lint.isort]
133
+ length-sort = true
134
+ known-first-party = ["tests/*"]
135
+ extra-standard-library = ["typing_extensions"]
136
+
137
+ [tool.ruff.lint.pyupgrade]
138
+ keep-runtime-typing = true
@@ -0,0 +1,21 @@
1
+ from nonebot.plugin import PluginMetadata
2
+ from nonebot import get_plugin_config
3
+
4
+ from .config import Config
5
+ from . import forwarder
6
+
7
+ config = get_plugin_config(Config)
8
+ forwarder.config = config
9
+
10
+ __plugin_meta__ = PluginMetadata(
11
+ name="nonebot-plugin-onebot2tg",
12
+ description="OneBot V11 与 Telegram 双向消息转发插件",
13
+ usage="配置 ONEBOT2TG_TARGET_CHAT_ID 和 ONEBOT2TG_OB_TARGET_GROUP_ID 即可自动转发",
14
+ type="application",
15
+ homepage="",
16
+ config=Config,
17
+ supported_adapters={
18
+ "~onebot.v11",
19
+ "~telegram",
20
+ },
21
+ )
@@ -0,0 +1,25 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class Config(BaseModel):
5
+ """插件配置"""
6
+
7
+ # 模式开关
8
+ onebot2tg_enable_bridge: bool = True
9
+ """开启互通模式(双向转发,需配置互通群号,默认开启)"""
10
+
11
+ onebot2tg_enable_forward: bool = False
12
+ """开启转发模式(QQ所有消息单向转发到TG私聊)"""
13
+
14
+ # 互通模式配置(一对一)
15
+ onebot2tg_bridge_group_id: str | int = ""
16
+ """互通模式下,要互通的QQ群号"""
17
+
18
+ onebot2tg_bridge_tg_chat_id: str | int = ""
19
+ """互通模式下,TG对应的chat_id(群或频道)"""
20
+
21
+ # 转发模式配置
22
+ onebot2tg_forward_target_chat_id: str | int = ""
23
+ """转发模式下,QQ消息转发到TG的目标chat_id(私聊或群)"""
24
+
25
+
@@ -0,0 +1,296 @@
1
+ from __future__ import annotations
2
+
3
+ from nonebot import get_adapter, logger
4
+ from nonebot.adapters.onebot.v11 import (
5
+ Bot as OB11Bot,
6
+ Message as OB11Message,
7
+ MessageEvent as OB11MessageEvent,
8
+ GroupMessageEvent as OB11GroupMessageEvent,
9
+ PrivateMessageEvent as OB11PrivateMessageEvent,
10
+ MessageSegment as OB11Segment,
11
+ )
12
+ from nonebot.adapters.telegram import Bot as TGBot
13
+ from nonebot.adapters.telegram.event import MessageEvent as TGMessageEvent
14
+ from nonebot.adapters.telegram.message import (
15
+ Message as TGMessage,
16
+ File,
17
+ Entity,
18
+ )
19
+ from nonebot.drivers import Request
20
+ from nonebot.plugin.on import on_message
21
+ from nonebot.rule import is_type
22
+
23
+ from .config import Config
24
+
25
+ config: Config
26
+
27
+
28
+ async def get_tg_bot() -> TGBot | None:
29
+ try:
30
+ adapter = get_adapter("Telegram")
31
+ for bot in adapter.bots.values():
32
+ if isinstance(bot, TGBot):
33
+ return bot
34
+ except Exception as e:
35
+ logger.warning(f"获取 Telegram Bot 失败: {e}")
36
+ return None
37
+
38
+
39
+ async def get_ob11_bot() -> OB11Bot | None:
40
+ try:
41
+ adapter = get_adapter("OneBot V11")
42
+ for bot in adapter.bots.values():
43
+ if isinstance(bot, OB11Bot):
44
+ return bot
45
+ except Exception as e:
46
+ logger.warning(f"获取 OneBot V11 Bot 失败: {e}")
47
+ return None
48
+
49
+
50
+ def _ob11_display_name(event: OB11MessageEvent) -> str:
51
+ sender = event.sender
52
+ return sender.card or sender.nickname or event.get_user_id()
53
+
54
+
55
+ def _tg_display_name(event: TGMessageEvent) -> str:
56
+ user = getattr(event, "from_", None)
57
+ if user:
58
+ return user.username or user.first_name or event.get_user_id()
59
+ return event.get_user_id()
60
+
61
+
62
+ # ============================================================
63
+ # 下载 TG 文件(走代理)
64
+ # ============================================================
65
+ async def _download_tg_file(tg_bot: TGBot, file_id: str) -> bytes | None:
66
+ """通过代理下载 TG 文件"""
67
+ try:
68
+ file_info = await tg_bot.get_file(file_id=file_id)
69
+ if not file_info.file_path:
70
+ return None
71
+ api_server = tg_bot.bot_config.api_server.rstrip("/")
72
+ url = f"{api_server}/file/bot{tg_bot.bot_config.token}/{file_info.file_path}"
73
+ adapter = tg_bot.adapter
74
+ proxy = getattr(adapter, "adapter_config", None)
75
+ proxy_url = proxy.proxy if proxy else None
76
+ request = Request("GET", url, proxy=proxy_url)
77
+ response = await adapter.request(request)
78
+ if response.status_code == 200 and response.content:
79
+ return response.content
80
+ logger.warning(f"下载 TG 文件失败,状态码: {response.status_code}")
81
+ return None
82
+ except Exception as e:
83
+ logger.warning(f"下载 TG 文件失败: {e}")
84
+ return None
85
+
86
+
87
+ # ============================================================
88
+ # TG Message → OneBot V11 Message
89
+ # ============================================================
90
+ async def _tg_message_to_ob11(
91
+ tg_bot: TGBot, event: TGMessageEvent
92
+ ) -> OB11Message:
93
+ segments: list[OB11Segment] = []
94
+
95
+ for seg in event.get_message():
96
+ if seg.type == "text":
97
+ segments.append(OB11Segment.text(seg.data.get("text", "")))
98
+ elif seg.type == "photo":
99
+ file_id = seg.data.get("file", "")
100
+ if file_id:
101
+ data = await _download_tg_file(tg_bot, file_id)
102
+ if data:
103
+ segments.append(OB11Segment.image(data))
104
+ else:
105
+ segments.append(OB11Segment.text("[图片]"))
106
+ else:
107
+ segments.append(OB11Segment.text("[图片]"))
108
+ elif seg.type == "sticker":
109
+ file_id = seg.data.get("file", "")
110
+ if file_id:
111
+ data = await _download_tg_file(tg_bot, file_id)
112
+ if data:
113
+ segments.append(OB11Segment.image(data))
114
+ else:
115
+ segments.append(OB11Segment.text("[贴纸]"))
116
+ else:
117
+ segments.append(OB11Segment.text("[贴纸]"))
118
+ elif seg.type == "animation":
119
+ segments.append(OB11Segment.text("[动图]"))
120
+ elif seg.type == "document":
121
+ segments.append(OB11Segment.text("[文件]"))
122
+ elif seg.type == "video":
123
+ segments.append(OB11Segment.text("[视频]"))
124
+ elif seg.type == "voice":
125
+ segments.append(OB11Segment.text("[语音]"))
126
+ else:
127
+ segments.append(OB11Segment.text(f"[{seg.type}]"))
128
+
129
+ return OB11Message(segments)
130
+
131
+
132
+ # ============================================================
133
+ # OneBot V11 Message → TG Message
134
+ # ============================================================
135
+ async def _ob11_message_to_tg(
136
+ bot: OB11Bot, event: OB11MessageEvent
137
+ ) -> tuple[str, list]:
138
+ caption_parts: list[str] = []
139
+ file_segments: list = []
140
+
141
+ for seg in event.get_message():
142
+ if seg.type == "text":
143
+ caption_parts.append(seg.data.get("text", ""))
144
+ elif seg.type == "image":
145
+ try:
146
+ file_info = await bot.get_image(file=seg.data.get("file", ""))
147
+ url = file_info.get("url", "")
148
+ if url:
149
+ file_segments.append(File.photo(url))
150
+ else:
151
+ caption_parts.append("[图片]")
152
+ except Exception as e:
153
+ logger.warning(f"获取图片失败: {e}")
154
+ caption_parts.append("[图片]")
155
+ elif seg.type == "face":
156
+ caption_parts.append(f"[表情:{seg.data.get('id', '')}]")
157
+ elif seg.type == "at":
158
+ caption_parts.append(f"@{seg.data.get('qq', '')}")
159
+ elif seg.type == "reply":
160
+ pass
161
+ else:
162
+ caption_parts.append(f"[{seg.type}]")
163
+
164
+ return "".join(caption_parts), file_segments
165
+
166
+
167
+ async def _send_to_tg(
168
+ tg_bot: TGBot,
169
+ chat_id: str | int,
170
+ display_name: str,
171
+ caption: str,
172
+ file_segments: list,
173
+ mode: str,
174
+ ):
175
+ if file_segments:
176
+ first_seg = file_segments[0]
177
+ full_caption = f"{display_name}:\n{caption}" if caption else f"{display_name}:"
178
+
179
+ if len(file_segments) == 1:
180
+ try:
181
+ await tg_bot.send_to(
182
+ chat_id=chat_id,
183
+ message=TGMessage([first_seg]) + TGMessage([Entity.text(full_caption)]),
184
+ )
185
+ except Exception as e:
186
+ logger.error(f"[{mode}] 发送图片到 TG 失败: {e}")
187
+ else:
188
+ try:
189
+ await tg_bot.send_to(
190
+ chat_id=chat_id,
191
+ message=TGMessage([first_seg]) + TGMessage([Entity.text(full_caption)]),
192
+ )
193
+ for seg in file_segments[1:]:
194
+ await tg_bot.send_to(chat_id=chat_id, message=seg)
195
+ except Exception as e:
196
+ logger.error(f"[{mode}] 发送多张图片到 TG 失败: {e}")
197
+ else:
198
+ text = f"{display_name}:\n{caption}"
199
+ try:
200
+ await tg_bot.send_message(chat_id=chat_id, text=text)
201
+ except Exception as e:
202
+ logger.error(f"[{mode}] 发送文本到 TG 失败: {e}")
203
+
204
+
205
+ # ============================================================
206
+ # TG 消息处理器(仅互通模式)
207
+ # ============================================================
208
+ tg_msg = on_message(rule=is_type(TGMessageEvent))
209
+
210
+
211
+ @tg_msg.handle()
212
+ async def handle_tg_message(event: TGMessageEvent):
213
+ if not config.onebot2tg_enable_bridge:
214
+ return
215
+ if not config.onebot2tg_bridge_group_id:
216
+ return
217
+
218
+ chat_id = str(event.chat.id) if hasattr(event, "chat") else ""
219
+ if chat_id != str(config.onebot2tg_bridge_tg_chat_id):
220
+ return
221
+
222
+ tg_bot = await get_tg_bot()
223
+ if tg_bot is None:
224
+ return
225
+
226
+ ob11_bot = await get_ob11_bot()
227
+ if ob11_bot is None:
228
+ return
229
+
230
+ display_name = _tg_display_name(event)
231
+ ob11_msg = await _tg_message_to_ob11(tg_bot, event)
232
+ prefix = OB11Segment.text(f"{display_name}:\n")
233
+ final_msg = OB11Message([prefix]) + ob11_msg
234
+
235
+ try:
236
+ await ob11_bot.send_group_msg(
237
+ group_id=int(config.onebot2tg_bridge_group_id),
238
+ message=final_msg,
239
+ )
240
+ logger.debug("[互通] 已转发 TG 消息到 QQ群")
241
+ except Exception as e:
242
+ logger.error(f"[互通] 转发 TG 消息到 QQ群失败: {e}")
243
+
244
+
245
+ # ============================================================
246
+ # OneBot 消息处理器(互通 + 转发模式)
247
+ # ============================================================
248
+ ob11_msg = on_message(rule=is_type(OB11MessageEvent))
249
+
250
+
251
+ @ob11_msg.handle()
252
+ async def handle_ob11_message(event: OB11MessageEvent):
253
+ tg_bot = await get_tg_bot()
254
+ if tg_bot is None:
255
+ return
256
+
257
+ ob11_bot = await get_ob11_bot()
258
+ if ob11_bot is None:
259
+ return
260
+
261
+ is_group = isinstance(event, OB11GroupMessageEvent)
262
+ group_id = str(event.group_id) if is_group else ""
263
+ display_name = _ob11_display_name(event)
264
+
265
+ # ---------- 互通模式 ----------
266
+ if config.onebot2tg_enable_bridge and config.onebot2tg_bridge_tg_chat_id:
267
+ if is_group and group_id == str(config.onebot2tg_bridge_group_id):
268
+ caption, file_segments = await _ob11_message_to_tg(ob11_bot, event)
269
+ await _send_to_tg(
270
+ tg_bot,
271
+ config.onebot2tg_bridge_tg_chat_id,
272
+ display_name,
273
+ caption,
274
+ file_segments,
275
+ "互通",
276
+ )
277
+ return
278
+
279
+ # ---------- 转发模式(QQ → TG 单向) ----------
280
+ if config.onebot2tg_enable_forward and config.onebot2tg_forward_target_chat_id:
281
+ if is_group:
282
+ source = f"[群:{event.group_id}] {display_name}"
283
+ elif isinstance(event, OB11PrivateMessageEvent):
284
+ source = f"[私聊] {display_name}"
285
+ else:
286
+ source = f"[QQ] {display_name}"
287
+
288
+ caption, file_segments = await _ob11_message_to_tg(ob11_bot, event)
289
+ await _send_to_tg(
290
+ tg_bot,
291
+ config.onebot2tg_forward_target_chat_id,
292
+ source,
293
+ caption,
294
+ file_segments,
295
+ "转发",
296
+ )