mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-21 06:07:13 +08:00
feat: optimize weixin channel qr code generate
This commit is contained in:
@@ -160,6 +160,30 @@ class WeixinChannel(ChatChannel):
|
|||||||
print(f"\n 二维码链接: {qrcode_url}")
|
print(f"\n 二维码链接: {qrcode_url}")
|
||||||
print(" (安装 'qrcode' 包可在终端显示二维码)\n")
|
print(" (安装 'qrcode' 包可在终端显示二维码)\n")
|
||||||
|
|
||||||
|
def _notify_cloud_qrcode(self, qrcode_url: str):
|
||||||
|
"""Send QR code URL to cloud console when running in cloud mode."""
|
||||||
|
if not self.cloud_mode:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from common import cloud_client
|
||||||
|
client = getattr(cloud_client, "chat_client", None)
|
||||||
|
if client and getattr(client, "client_id", None):
|
||||||
|
client.send_channel_qrcode("weixin", qrcode_url)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Weixin] Failed to notify cloud QR code: {e}")
|
||||||
|
|
||||||
|
def _notify_cloud_connected(self):
|
||||||
|
"""Send connected status to cloud console when login succeeds."""
|
||||||
|
if not self.cloud_mode:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from common import cloud_client
|
||||||
|
client = getattr(cloud_client, "chat_client", None)
|
||||||
|
if client and getattr(client, "client_id", None):
|
||||||
|
client.send_channel_status("weixin", "connected")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[Weixin] Failed to notify cloud connected: {e}")
|
||||||
|
|
||||||
def _qr_login(self, base_url: str) -> dict:
|
def _qr_login(self, base_url: str) -> dict:
|
||||||
"""Perform interactive QR code login. Returns dict with token/base_url or empty dict."""
|
"""Perform interactive QR code login. Returns dict with token/base_url or empty dict."""
|
||||||
api = WeixinApi(base_url=base_url)
|
api = WeixinApi(base_url=base_url)
|
||||||
@@ -179,11 +203,12 @@ class WeixinChannel(ChatChannel):
|
|||||||
self._current_qr_url = qrcode_url
|
self._current_qr_url = qrcode_url
|
||||||
logger.info(f"[Weixin] QR code URL: {qrcode_url}")
|
logger.info(f"[Weixin] QR code URL: {qrcode_url}")
|
||||||
self._print_qr(qrcode_url)
|
self._print_qr(qrcode_url)
|
||||||
|
self._notify_cloud_qrcode(qrcode_url)
|
||||||
print(" 等待扫码...\n")
|
print(" 等待扫码...\n")
|
||||||
|
|
||||||
scanned_printed = False
|
scanned_printed = False
|
||||||
|
|
||||||
while True:
|
while not self._stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
status_resp = api.poll_qr_status(qrcode)
|
status_resp = api.poll_qr_status(qrcode)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -209,6 +234,7 @@ class WeixinChannel(ChatChannel):
|
|||||||
self._current_qr_url = qrcode_url
|
self._current_qr_url = qrcode_url
|
||||||
logger.info(f"[Weixin] New QR code: {qrcode_url}")
|
logger.info(f"[Weixin] New QR code: {qrcode_url}")
|
||||||
self._print_qr(qrcode_url)
|
self._print_qr(qrcode_url)
|
||||||
|
self._notify_cloud_qrcode(qrcode_url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[Weixin] QR refresh failed: {e}")
|
logger.error(f"[Weixin] QR refresh failed: {e}")
|
||||||
return {}
|
return {}
|
||||||
@@ -225,6 +251,7 @@ class WeixinChannel(ChatChannel):
|
|||||||
self._current_qr_url = ""
|
self._current_qr_url = ""
|
||||||
print(f"\n ✅ 微信登录成功!bot_id={bot_id}")
|
print(f"\n ✅ 微信登录成功!bot_id={bot_id}")
|
||||||
logger.info(f"[Weixin] Login confirmed: bot_id={bot_id}")
|
logger.info(f"[Weixin] Login confirmed: bot_id={bot_id}")
|
||||||
|
self._notify_cloud_connected()
|
||||||
|
|
||||||
creds = {
|
creds = {
|
||||||
"token": bot_token,
|
"token": bot_token,
|
||||||
@@ -237,9 +264,10 @@ class WeixinChannel(ChatChannel):
|
|||||||
|
|
||||||
return {"token": bot_token, "base_url": result_base_url}
|
return {"token": bot_token, "base_url": result_base_url}
|
||||||
|
|
||||||
time.sleep(1)
|
self._stop_event.wait(1)
|
||||||
|
|
||||||
logger.warning("[Weixin] QR login timed out")
|
logger.info("[Weixin] QR login cancelled by stop event")
|
||||||
|
self._current_qr_url = ""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# ── Long-poll loop ─────────────────────────────────────────────────
|
# ── Long-poll loop ─────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -260,11 +260,27 @@ class CloudClient(LinkAIClient):
|
|||||||
self._remove_channel_type(local_config, channel_type)
|
self._remove_channel_type(local_config, channel_type)
|
||||||
self._save_config_to_file(local_config)
|
self._save_config_to_file(local_config)
|
||||||
|
|
||||||
|
if channel_type in ("weixin", "wx"):
|
||||||
|
self._remove_weixin_credentials()
|
||||||
|
|
||||||
if self.channel_mgr:
|
if self.channel_mgr:
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=self._do_remove_channel, args=(channel_type,), daemon=True
|
target=self._do_remove_channel, args=(channel_type,), daemon=True
|
||||||
).start()
|
).start()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_weixin_credentials():
|
||||||
|
"""Remove the weixin token credentials file so next connect triggers QR login."""
|
||||||
|
cred_path = os.path.expanduser(
|
||||||
|
conf().get("weixin_credentials_path", "~/.weixin_cow_credentials.json")
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
if os.path.exists(cred_path):
|
||||||
|
os.remove(cred_path)
|
||||||
|
logger.info(f"[CloudClient] Removed weixin credentials: {cred_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"[CloudClient] Failed to remove weixin credentials: {e}")
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# channel credentials helpers
|
# channel credentials helpers
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -351,12 +367,31 @@ class CloudClient(LinkAIClient):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[CloudClient] Failed to remove channel '{channel_type}': {e}")
|
logger.error(f"[CloudClient] Failed to remove channel '{channel_type}': {e}")
|
||||||
|
|
||||||
|
def send_channel_qrcode(self, channel_type: str, qrcode_url: str):
|
||||||
|
"""Report QR code URL for a channel that requires scan-to-login."""
|
||||||
|
if self.client_id:
|
||||||
|
from linkai.api.client.client import ClientMsgType
|
||||||
|
msg = self._build_package(ClientMsgType.CHANNEL_STATUS)
|
||||||
|
msg["data"]["channelType"] = channel_type
|
||||||
|
msg["data"]["status"] = "qrcode"
|
||||||
|
msg["data"]["qrcodeUrl"] = qrcode_url
|
||||||
|
self._send_package(msg)
|
||||||
|
logger.info(f"[CloudClient] Sent QR code status for '{channel_type}'")
|
||||||
|
|
||||||
def _report_channel_startup(self, channel_type: str):
|
def _report_channel_startup(self, channel_type: str):
|
||||||
"""Wait for channel startup result and report to cloud."""
|
"""Wait for channel startup result and report to cloud."""
|
||||||
ch = self.channel_mgr.get_channel(channel_type)
|
ch = self.channel_mgr.get_channel(channel_type)
|
||||||
if not ch:
|
if not ch:
|
||||||
self.send_channel_status(channel_type, "error", "channel instance not found")
|
self.send_channel_status(channel_type, "error", "channel instance not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if channel_type in ("weixin", "wx") and hasattr(ch, "login_status"):
|
||||||
|
login_status = getattr(ch, "login_status", "")
|
||||||
|
if login_status in ("waiting_scan", "scanned", "idle"):
|
||||||
|
logger.info(f"[CloudClient] Channel '{channel_type}' is waiting for QR login, "
|
||||||
|
"skip reporting connected")
|
||||||
|
return
|
||||||
|
|
||||||
success, error = ch.wait_startup(timeout=3)
|
success, error = ch.wait_startup(timeout=3)
|
||||||
if success:
|
if success:
|
||||||
logger.info(f"[CloudClient] Channel '{channel_type}' connected, reporting status")
|
logger.info(f"[CloudClient] Channel '{channel_type}' connected, reporting status")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ description: 将 CowAgent 接入个人微信
|
|||||||
首次启动时,终端会显示一个二维码(有效期约 2 分钟)。使用微信扫描二维码并在手机上确认后即可完成登录。
|
首次启动时,终端会显示一个二维码(有效期约 2 分钟)。使用微信扫描二维码并在手机上确认后即可完成登录。
|
||||||
|
|
||||||
- 二维码过期后会自动刷新并重新显示
|
- 二维码过期后会自动刷新并重新显示
|
||||||
- 安装 `qrcode` Python 包可在终端直接渲染二维码图案(`pip3 install qrcode`)
|
- `requirements.txt` 中已默认包含 `qrcode` 依赖,安装后可在终端直接渲染二维码图案
|
||||||
|
|
||||||
### 凭证保存
|
### 凭证保存
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Login credentials are automatically saved to `~/.weixin_cow_credentials.json`. T
|
|||||||
On first startup, a QR code is displayed in the terminal (valid for approximately 2 minutes). Scan it with WeChat and confirm on your phone.
|
On first startup, a QR code is displayed in the terminal (valid for approximately 2 minutes). Scan it with WeChat and confirm on your phone.
|
||||||
|
|
||||||
- The QR code automatically refreshes when it expires
|
- The QR code automatically refreshes when it expires
|
||||||
- Install the `qrcode` Python package to render the QR code directly in the terminal: `pip3 install qrcode`
|
- The `qrcode` dependency is already included in `requirements.txt`, enabling QR code rendering directly in the terminal
|
||||||
|
|
||||||
### Credential Persistence
|
### Credential Persistence
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ description: CowAgent を個人の WeChat に接続する
|
|||||||
初回起動時に、ターミナルに QR コードが表示されます(有効期限は約 2 分)。WeChat でスキャンし、スマートフォンで確認してください。
|
初回起動時に、ターミナルに QR コードが表示されます(有効期限は約 2 分)。WeChat でスキャンし、スマートフォンで確認してください。
|
||||||
|
|
||||||
- QR コードが期限切れになると自動的に更新・再表示されます
|
- QR コードが期限切れになると自動的に更新・再表示されます
|
||||||
- `qrcode` Python パッケージをインストールすると、ターミナルに直接 QR コードを表示できます:`pip3 install qrcode`
|
- `qrcode` 依存関係は `requirements.txt` にデフォルトで含まれており、ターミナルに直接 QR コードを表示できます
|
||||||
|
|
||||||
### 認証情報の永続化
|
### 認証情報の永続化
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user