wechatbridge-cli 1.3.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.
- wechatbridge/__init__.py +2 -0
- wechatbridge/__main__.py +4 -0
- wechatbridge/agy.py +630 -0
- wechatbridge/config.py +266 -0
- wechatbridge/grok.py +682 -0
- wechatbridge/ilink.py +849 -0
- wechatbridge/main.py +755 -0
- wechatbridge/runner_common.py +1003 -0
- wechatbridge/update_check.py +175 -0
- wechatbridge_cli-1.3.0.dist-info/METADATA +29 -0
- wechatbridge_cli-1.3.0.dist-info/RECORD +15 -0
- wechatbridge_cli-1.3.0.dist-info/WHEEL +5 -0
- wechatbridge_cli-1.3.0.dist-info/entry_points.txt +2 -0
- wechatbridge_cli-1.3.0.dist-info/licenses/LICENSE +21 -0
- wechatbridge_cli-1.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
wechatbridge Update Check Module
|
|
3
|
+
Periodically check PyPI for new versions and notify admin users via WeChat.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
from .config import config
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
PYPI_URL = "https://pypi.org/pypi/wechatbridge-cli/json"
|
|
18
|
+
RELEASES_URL = "https://github.com/dorokuma/wechatbridge/releases"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Version comparison helpers
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def _parse_version(v: str) -> tuple:
|
|
26
|
+
"""Parse a semver string into a numeric tuple for comparison.
|
|
27
|
+
|
|
28
|
+
Each dot-segment is parsed by taking the leading digits; non-digit
|
|
29
|
+
suffixes (e.g. ``-beta``) are ignored. Missing segments default to 0.
|
|
30
|
+
"""
|
|
31
|
+
parts = v.split(".")
|
|
32
|
+
result = []
|
|
33
|
+
for seg in parts:
|
|
34
|
+
m = re.match(r"\d+", seg)
|
|
35
|
+
result.append(int(m.group()) if m else 0)
|
|
36
|
+
return tuple(result)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_newer(latest: str, current: str = __version__) -> bool:
|
|
40
|
+
"""Return True if ``latest`` is a higher version than ``current``."""
|
|
41
|
+
return _parse_version(latest) > _parse_version(current)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Module-level state
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
latest_version: str | None = None
|
|
49
|
+
_notified_admins: set = set()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Network operations
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
async def fetch_latest_version() -> str | None:
|
|
57
|
+
"""Query PyPI JSON API for the latest published version.
|
|
58
|
+
|
|
59
|
+
Returns version string on success, ``None`` on any failure.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
63
|
+
resp = await client.get(PYPI_URL)
|
|
64
|
+
resp.raise_for_status()
|
|
65
|
+
data = resp.json()
|
|
66
|
+
version: str = data["info"]["version"]
|
|
67
|
+
logger.debug("PyPI latest version: %s", version)
|
|
68
|
+
return version
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.debug("Failed to fetch latest version from PyPI: %s", e)
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# Background loop
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
async def update_check_loop() -> None:
|
|
79
|
+
"""Background task: check PyPI immediately, then every ``update_check_interval`` seconds.
|
|
80
|
+
|
|
81
|
+
Never raises; all exceptions are caught and logged.
|
|
82
|
+
"""
|
|
83
|
+
global latest_version
|
|
84
|
+
|
|
85
|
+
while True:
|
|
86
|
+
try:
|
|
87
|
+
version = await fetch_latest_version()
|
|
88
|
+
if version is not None:
|
|
89
|
+
if latest_version is None or is_newer(version, latest_version):
|
|
90
|
+
latest_version = version
|
|
91
|
+
if is_newer(version):
|
|
92
|
+
logger.warning(
|
|
93
|
+
"发现新版本: 当前 %s → 最新 %s。"
|
|
94
|
+
"更新: `pipx upgrade wechatbridge-cli` | %s",
|
|
95
|
+
__version__, version, RELEASES_URL,
|
|
96
|
+
)
|
|
97
|
+
elif version == latest_version:
|
|
98
|
+
pass # same version, no action
|
|
99
|
+
# else: fetch failed, will retry next cycle
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.exception("update_check_loop 未知异常: %s", e)
|
|
102
|
+
|
|
103
|
+
await asyncio.sleep(config.update_check_interval)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Query helpers (used by /version)
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def update_available() -> bool:
|
|
111
|
+
"""Return True if a newer version has been detected."""
|
|
112
|
+
if latest_version is None:
|
|
113
|
+
return False
|
|
114
|
+
return is_newer(latest_version)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def format_update_hint() -> str:
|
|
118
|
+
"""Return a formatted update hint string, or empty string if no update.
|
|
119
|
+
|
|
120
|
+
Designed to be appended to ``/version`` replies.
|
|
121
|
+
"""
|
|
122
|
+
if not update_available():
|
|
123
|
+
return ""
|
|
124
|
+
return (
|
|
125
|
+
f"\n\n🆕 发现新版本 `{latest_version}`,更新:`pipx upgrade wechatbridge-cli`"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Admin notification
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
async def maybe_notify_admin(client, from_user: str, context_token: str) -> None:
|
|
134
|
+
"""If an update is available and ``from_user`` is an admin, send a WeChat notification.
|
|
135
|
+
|
|
136
|
+
Each admin is only notified once per process lifetime (tracked in
|
|
137
|
+
``_notified_admins``). On send failure, the admin is removed from the set
|
|
138
|
+
so the next message from them will retry.
|
|
139
|
+
|
|
140
|
+
All network/send exceptions are caught and logged.
|
|
141
|
+
"""
|
|
142
|
+
if not update_available():
|
|
143
|
+
return
|
|
144
|
+
if from_user not in config.admin_users:
|
|
145
|
+
return
|
|
146
|
+
if from_user in _notified_admins:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Mark as notified immediately to prevent concurrent duplicates
|
|
150
|
+
_notified_admins.add(from_user)
|
|
151
|
+
|
|
152
|
+
text = (
|
|
153
|
+
f"🆕 **wechatbridge 有新版本** 🆕\n\n"
|
|
154
|
+
f"当前 `{__version__}` → 最新 `{latest_version}`\n\n"
|
|
155
|
+
f"更新方法:\n"
|
|
156
|
+
f"`pipx upgrade wechatbridge-cli && sudo systemctl restart wechatbridge`\n\n"
|
|
157
|
+
f"更新内容:{RELEASES_URL}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
success = await client.send_message(
|
|
162
|
+
to_user_id=from_user,
|
|
163
|
+
text=text,
|
|
164
|
+
context_token=context_token,
|
|
165
|
+
baseurl=client.state.baseurl,
|
|
166
|
+
bot_token=client.state.bot_token,
|
|
167
|
+
)
|
|
168
|
+
if success:
|
|
169
|
+
logger.info("已向管理员 %s 发送版本更新通知", from_user)
|
|
170
|
+
else:
|
|
171
|
+
logger.warning("向管理员 %s 发送版本更新通知失败", from_user)
|
|
172
|
+
_notified_admins.discard(from_user)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.warning("向管理员 %s 发送版本更新通知异常: %s", from_user, e)
|
|
175
|
+
_notified_admins.discard(from_user)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wechatbridge-cli
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Bridge WeChat messages to agy or Grok Build CLIs — text/image/file/voice in, CLI replies and generated files back.
|
|
5
|
+
Author: WeChatBridge contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dorokuma/wechatbridge
|
|
8
|
+
Project-URL: Repository, https://github.com/dorokuma/wechatbridge
|
|
9
|
+
Project-URL: Issues, https://github.com/dorokuma/wechatbridge/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/dorokuma/wechatbridge/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: wechat,bot,bridge,agy,grok
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: Chinese (Simplified)
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Communications :: Chat
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: httpx
|
|
26
|
+
Requires-Dist: qrcode
|
|
27
|
+
Requires-Dist: Pillow
|
|
28
|
+
Requires-Dist: cryptography
|
|
29
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
wechatbridge/__init__.py,sha256=RXm_TLO0Has6_lQKHEWEw2SQzvvt3K3Gt0n-8VVEalI,95
|
|
2
|
+
wechatbridge/__main__.py,sha256=siqkkOqvVtcsjpQnK5noeax5zAgB9M2xfPB7VBm39VI,74
|
|
3
|
+
wechatbridge/agy.py,sha256=hqZNXv2Kwmql594llk8G8aTxe7fxTKNF3AGcMNSD0qQ,24587
|
|
4
|
+
wechatbridge/config.py,sha256=S0nQCykibpYJ3sgkf-l6YgD9ynv8h-mAOHoyF0Y-a7s,10090
|
|
5
|
+
wechatbridge/grok.py,sha256=FO7vyj9oRqYg-WKNbrHfKyfg0lxucJ8xlszfYk5vmJE,25501
|
|
6
|
+
wechatbridge/ilink.py,sha256=qQYIxvkILQ2yEdLVckcTn7DyNMVgy-O8w3Hig0PFoyc,31309
|
|
7
|
+
wechatbridge/main.py,sha256=f4UwMyCBRzNAxyQL4O5Oq7WlWyFgEWtWSLA6o1TSkIk,29748
|
|
8
|
+
wechatbridge/runner_common.py,sha256=KVNF6ctpwrNea5mC4AknYPwwULAod8d5ILCA91i7Urk,35856
|
|
9
|
+
wechatbridge/update_check.py,sha256=CKBHR8QbXoEvuBlQYMop234UwnNm2N60SP5uv5vBmOI,6086
|
|
10
|
+
wechatbridge_cli-1.3.0.dist-info/licenses/LICENSE,sha256=N0uJ1olAtTpsi_pq08gi0sDDG18VxpF6ae0V-a7N3Rc,1082
|
|
11
|
+
wechatbridge_cli-1.3.0.dist-info/METADATA,sha256=Ek5oJnDC5xtjZ8MsQuOW2H3Og-aJIcjbVsvnaF_6GNI,1236
|
|
12
|
+
wechatbridge_cli-1.3.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
13
|
+
wechatbridge_cli-1.3.0.dist-info/entry_points.txt,sha256=NgIGRaOGDzV0QZH8-NgLMyW_WdiKpPiHpaNQeqLrupw,56
|
|
14
|
+
wechatbridge_cli-1.3.0.dist-info/top_level.txt,sha256=Ph3fc1evjcKb_Mdz_RsukQbviBX3Yeh8n-BNMlPpCgw,13
|
|
15
|
+
wechatbridge_cli-1.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WeChatBridge contributors
|
|
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 @@
|
|
|
1
|
+
wechatbridge
|