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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ wechatbridge = wechatbridge.main:main
@@ -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