feat: 通知脚本支持 Telegram 通道 #96

Merged
gjm merged 1 commits from company-desktop/company-desktop-computer/feat-notify-telegram AGit into main 2026-10-28 15:58:10 +08:00
2 changed files with 106 additions and 17 deletions

No files matched your search

+26 -2
View File
@@ -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` 当失败处理。
+80 -15
View File
@@ -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