From 18aa1d12601e5b7621b1cdddae1bb7424e068eef Mon Sep 17 00:00:00 2001 From: company-desktop-computer Date: Wed, 28 Oct 2026 15:55:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20Telegram=20=E9=80=9A=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户没有企业微信,改用 Telegram Bot。同一台机器的 api.telegram.org 需要走本地代理 (实测 127.0.0.1:7897),所以发送逻辑要显式支持代理。 - notify.json 新增 telegram 段:token / chatId / proxy (也认环境变量 PI_NOTIFY_TELEGRAM_TOKEN、PI_NOTIFY_TELEGRAM_CHAT) - 未配置 proxy 时回落 HTTPS_PROXY 环境变量,再回落系统(IE)代理 - 文本超过 3900 字符截断(Telegram 上限 4096) - 自检 -Probe 同时报告 webhook 与 telegram 两个通道的结果,token 只显示前 10 位 - SKILL.md 更新通道配置说明,补上 notify.json 的完整示例与代理注意事项 实测:假 token 时返回 "(401) 未经授权",说明请求确实穿过了代理到达 api.telegram.org, 只是凭据无效;未配置时正确报告 not configured。真实推送待用户提供 bot token。 --- .pi/skills/notify-decision/SKILL.md | 28 ++++++++- tools/notify_decision.ps1 | 95 ++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/.pi/skills/notify-decision/SKILL.md b/.pi/skills/notify-decision/SKILL.md index 7dbb8b5..64668f2 100644 --- a/.pi/skills/notify-decision/SKILL.md +++ b/.pi/skills/notify-decision/SKILL.md @@ -61,6 +61,30 @@ powershell -NoProfile -ExecutionPolicy Bypass -File tools/notify_decision.ps1 \ | --- | --- | | 桌面通知 | 默认开启,无需配置 | | 决策日志 | 默认写入 `~/.pi/agent/decisions/pending.md` | -| IM 机器人 | `setx PI_NOTIFY_WEBHOOK "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."` | +| 企业微信/钉钉/飞书 | `notify.json` 里 `webhook` 字段,或环境变量 `PI_NOTIFY_WEBHOOK` | +| Telegram | `notify.json` 里 `telegram.token` + `telegram.chatId` | -企业微信/钉钉用 `{"msgtype":"text",...}`;URL 里含 `feishu`/`lark` 时自动换成飞书格式。 +`%USERPROFILE%\.pi\agent\notify.json`(**仓库外**,密钥不能进 git): + +```json +{ + "webhook": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...", + "telegram": { + "token": "123456789:AAxxxx", + "chatId": "123456789", + "proxy": "http://127.0.0.1:7897" + } +} +``` + +- 企业微信/钉钉用 `{"msgtype":"text",...}`;URL 含 `feishu`/`lark` 时自动换飞书格式。 +- Telegram 在大陆需要代理(`telegram.proxy`,未配置时用 `HTTPS_PROXY` / 系统代理)。 +- 配了多个通道会全部发送。先自检: + +```bash +powershell -NoProfile -ExecutionPolicy Bypass -File tools/notify_decision.ps1 \ + -Title probe -Message x -NoToast -NoBeep -Probe +``` + +自检会打印每个通道的结果(URL/token 打码)。注意企业微信与 Telegram 都可能 +**出错时仍返回 HTTP 200**,脚本已解析响应体把 `errcode` / `ok=false` 当失败处理。 diff --git a/tools/notify_decision.ps1 b/tools/notify_decision.ps1 index da42024..4f1a836 100644 --- a/tools/notify_decision.ps1 +++ b/tools/notify_decision.ps1 @@ -91,25 +91,74 @@ function Mask-Url { return $u.Substring(0, 32) + '...' + $u.Substring($u.Length - 8) } -function Get-WebhookUrl { - # Order: -Url param > env var > ~/.pi/agent/notify.json > ~/.pi/agent/notify_webhook.txt - # Config lives OUTSIDE the repo on purpose: a webhook URL is a secret. - if ($Url) { return $Url } - if ($env:PI_NOTIFY_WEBHOOK) { return $env:PI_NOTIFY_WEBHOOK } +function Mask-Token { + param([string]$t) + if (-not $t) { return '(none)' } + if ($t.Length -le 12) { return '***' } + return $t.Substring(0, 10) + '***' +} - $cfg = Join-Path $env:USERPROFILE '.pi\agent\notify.json' - if (Test-Path $cfg) { +function Get-NotifyConfig { + # ~/.pi/agent/notify.json (outside the repo on purpose: tokens are secrets) + # { + # "webhook": "https://qyapi.weixin.qq.com/...", // WeCom / DingTalk / Feishu + # "telegram": { "token": "123456:ABC...", "chatId": "123456789", + # "proxy": "http://127.0.0.1:7897" } + # } + $cfg = @{ webhook = ''; tgToken = ''; tgChat = ''; proxy = '' } + $path = Join-Path $env:USERPROFILE '.pi\agent\notify.json' + if (Test-Path $path) { try { - $o = [System.IO.File]::ReadAllText($cfg, [System.Text.Encoding]::UTF8) | ConvertFrom-Json - if ($o.webhook) { return [string]$o.webhook } + $o = [System.IO.File]::ReadAllText($path, [System.Text.Encoding]::UTF8) | ConvertFrom-Json + if ($o.webhook) { $cfg.webhook = [string]$o.webhook } + if ($o.proxy) { $cfg.proxy = [string]$o.proxy } + if ($o.telegram) { + if ($o.telegram.token) { $cfg.tgToken = [string]$o.telegram.token } + if ($o.telegram.chatId) { $cfg.tgChat = [string]$o.telegram.chatId } + if ($o.telegram.proxy) { $cfg.proxy = [string]$o.telegram.proxy } + } } catch { } } - $txt = Join-Path $env:USERPROFILE '.pi\agent\notify_webhook.txt' - if (Test-Path $txt) { - $v = ([System.IO.File]::ReadAllText($txt, [System.Text.Encoding]::UTF8)).Trim() - if ($v) { return $v } + $txtHook = Join-Path $env:USERPROFILE '.pi\agent\notify_webhook.txt' + if (-not $cfg.webhook -and (Test-Path $txtHook)) { + $v = ([System.IO.File]::ReadAllText($txtHook, [System.Text.Encoding]::UTF8)).Trim() + if ($v) { $cfg.webhook = $v } + } + return $cfg +} + +function Get-ProxyArg { + param([string]$p) + if (-not $p) { $p = $env:HTTPS_PROXY } + if (-not $p) { $p = $env:https_proxy } + return $p +} + +function Send-Telegram { + # Telegram Bot API. In mainland China this needs the local proxy; Invoke-RestMethod + # otherwise falls back to the system (IE) proxy setting. + param([string]$token, [string]$chatId, [string]$t, [string]$m, [string]$proxy) + try { + if (-not $token -or -not $chatId) { return 'not configured' } + $txt = "$t`n$m" + if ($txt.Length -gt 3900) { $txt = $txt.Substring(0, 3900) + ' ...' } + $body = @{ chat_id = $chatId; text = $txt; disable_web_page_preview = $true } | + ConvertTo-Json -Compress + $bytes = [System.Text.Encoding]::UTF8.GetBytes($body) + $uri = "https://api.telegram.org/bot$token/sendMessage" + $args = @{ + Uri = $uri; Method = 'Post' + ContentType = 'application/json; charset=utf-8' + Body = $bytes; TimeoutSec = 15 + } + $px = Get-ProxyArg $proxy + if ($px) { $args['Proxy'] = $px } + $resp = Invoke-RestMethod @args + if ($resp.ok) { return 'ok' } + return "not ok: $($resp.description)" + } catch { + return "http: $($_.Exception.Message)" } - return '' } @@ -149,7 +198,16 @@ if (-not $NoToast) { } if (-not $NoBeep) { try { [console]::Beep(880, 180); [console]::Beep(1175, 220) } catch { } } -$hook = Get-WebhookUrl +$cfg = Get-NotifyConfig +$hook = $Url +if (-not $hook) { $hook = $env:PI_NOTIFY_WEBHOOK } +if (-not $hook) { $hook = $cfg.webhook } + +$tgToken = $env:PI_NOTIFY_TELEGRAM_TOKEN +if (-not $tgToken) { $tgToken = $cfg.tgToken } +$tgChat = $env:PI_NOTIFY_TELEGRAM_CHAT +if (-not $tgChat) { $tgChat = $cfg.tgChat } + if ($hook) { $st = Post-Webhook -u $hook -t $Title -m $text if ($Probe) { Write-Output ("webhook -> " + (Mask-Url $hook) + " result: " + $st) } @@ -157,4 +215,11 @@ if ($hook) { Write-Output 'webhook not configured' } +if ($tgToken -and $tgChat) { + $st = Send-Telegram -token $tgToken -chatId $tgChat -t $Title -m $text -proxy $cfg.proxy + if ($Probe) { Write-Output ("telegram -> token " + (Mask-Token $tgToken) + " chat " + $tgChat + " result: " + $st) } +} elseif ($Probe) { + Write-Output 'telegram not configured' +} + exit 0 -- 2.54.0