From d58a4b3fa9ad90514b348ed685bda741a5c7d037 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 9 Nov 2026 09:52:30 +0800 Subject: [PATCH] vault backup: 2026-11-09 09:52:30 --- .obsidian/workspace.json | 4 +-- 小鱼/闲鱼消息通知/README.md | 26 ++++++++++++++++-- 小鱼/闲鱼消息通知/notify.js | 48 ++++++++++++++++++++++++++++++--- 小鱼/闲鱼消息通知/send-mail.ps1 | 18 +++++++++++++ 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 小鱼/闲鱼消息通知/send-mail.ps1 diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index ce4fe5d..0119961 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -199,6 +199,8 @@ }, "active": "b10e6fd1a0b41e79", "lastOpenFiles": [ + "小鱼/闲鱼消息通知/watch.log", + "小鱼/闲鱼消息通知/send-mail.ps1", "小鱼/闲鱼消息通知/throttle-test.js", "小鱼/闲鱼消息通知/README.md", "小鱼/闲鱼消息通知/stop-watch.bat", @@ -238,8 +240,6 @@ "AndroidManifest.xml", "思睿/vocab_audio_output/audio/Day17.wav", "思睿/vocab_audio_output/audio/Day18.wav", - "思睿/vocab_audio_output/audio/Day22.wav", - "妹妹/妹妹身高.md.tmp.19184.32689a71f9b9", "思睿/身高/第一医院/报告/20261019报告/extracted_images/血常规 20261019_p1_img2.png", "思睿/身高/第一医院/报告/20261019报告/extracted_images/血常规 20261019_p1_img1.png", "思睿/身高/第一医院/报告/20261019报告/extracted_images/胰岛素测定 20261019_p1_img2.png", diff --git a/小鱼/闲鱼消息通知/README.md b/小鱼/闲鱼消息通知/README.md index d037c1f..1e751c9 100644 --- a/小鱼/闲鱼消息通知/README.md +++ b/小鱼/闲鱼消息通知/README.md @@ -48,15 +48,37 @@ webhook 的 URL 本身就是密码:拿到 URL 的任何人都能往你群里 - 只想要本地弹窗/声音:默认即可。 - 想推到手机/群里:编辑 `config.json` 的 `webhooks` 数组,加一条即可,支持: -| type | url 说明 | +| type | 说明 | |---|---| | `feishu` | 飞书群 → 添加自定义机器人,复制 webhook 地址 | -| `wecom` | 企业微信群机器人地址 | +| `wecom` | 企业微信群机器人地址(群内至少要有两人,或管理员建应用) | +| `wecomapp` | **企业微信自建应用**:推给自己单聊,最干净。需管理员后台建应用,填 `corpid`/`corpsecret`/`agentid`,`touser` 填你的企微 UserID | | `dingtalk` | 钉钉自定义机器人地址 | | `bark` | `https://api.day.app/你的KEY`(iPhone 锁屏推送,推荐) | +| `ntfy` | ntfy 主题地址,如 `https://ntfy.sh/你的主题` | +| `email` | 邮件(QQ/163 SMTP),微信有邮件提醒,等于推到手机 | | `serverchan` | `https://sctapi.ftqq.com/你的KEY.send` | | `custom` | 任意 URL,可选 `method`/`headers`/`body`,body 里可用 `{{title}}` `{{text}}` | +邮件示例(QQ 邮箱要先去设置里开 SMTP 并拿**授权码**,不是登录密码): + +```json +"webhooks": [ + { "type": "email", "smtp": "smtp.qq.com", "port": 465, + "from": "你的QQ号@qq.com", "to": "你的QQ号@qq.com", "passEnv": "MAIL_PASS" } +] +``` + +企微自建应用示例: + +```json +"webhooks": [ + { "type": "wecomapp", "corpid": "ww1234abcd", "corpsecret": "env:WECOM_APP_SECRET", + "agentid": 1000002, "touser": "你的UserID" } +] +``` + + 示例: ```json diff --git a/小鱼/闲鱼消息通知/notify.js b/小鱼/闲鱼消息通知/notify.js index 07bace1..03923f6 100644 --- a/小鱼/闲鱼消息通知/notify.js +++ b/小鱼/闲鱼消息通知/notify.js @@ -27,12 +27,52 @@ async function postJson(url, payload, headers = {}) { async function sendWebhook(hook, title, text, extra) { const type = (hook.type || 'custom').toLowerCase(); // url 支持 "env:变量名" —— 密钥不落在配置文件里(Obsidian 库会同步/进 Git) - const url = typeof hook.url === 'string' && hook.url.startsWith('env:') - ? process.env[hook.url.slice(4)] - : hook.url; - if (!url) throw new Error('webhook.url 为空(或 env: 指定的环境变量未设置)'); + const rawUrl = hook.url; + const url = typeof rawUrl === 'string' && rawUrl.startsWith('env:') + ? process.env[rawUrl.slice(4)] + : rawUrl; + const needsUrl = !['wecomapp', 'email'].includes(type); + if (needsUrl && !url) throw new Error('webhook.url 为空(或 env: 指定的环境变量未设置)'); switch (type) { + case 'wecomapp': { // 企业微信自建应用:推给自己单聊,需管理员在后台建应用 + const secret = hook.corpsecret && hook.corpsecret.startsWith('env:') + ? process.env[hook.corpsecret.slice(4)] + : hook.corpsecret; + if (!hook.corpid || !secret || !hook.agentid) throw new Error('wecomapp 需要 corpid / corpsecret / agentid'); + const tr = await fetch(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${encodeURIComponent(hook.corpid)}&corpsecret=${encodeURIComponent(secret)}`, { signal: AbortSignal.timeout(10000) }); + const tj = await tr.json(); + if (!tj.access_token) throw new Error(`gettoken 失败: ${tj.errcode} ${tj.errmsg}`); + return postJson(`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tj.access_token}`, { + touser: hook.touser || '@all', + msgtype: 'text', + agentid: Number(hook.agentid), + text: { content: `${title}\n${text}` }, + safe: 0 + }); + } + + case 'email': { // 邮件:QQ/163 等 SMTP,微信里能收到邮件提醒 + const pass = hook.passEnv ? process.env[hook.passEnv] : hook.pass; + if (!hook.smtp || !hook.from || !hook.to) throw new Error('email 需要 smtp / from / to'); + if (!pass) throw new Error('email 缺少密码/授权码'); + const ps1 = path.join(__dirname, 'send-mail.ps1'); + const args = [ + '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', ps1, + '-Smtp', hook.smtp, '-Port', String(hook.port || 465), + '-From', hook.from, '-To', hook.to, + '-User', hook.user || hook.from, '-Pass', pass, + '-Subject', title, '-Body', text + ]; + const code = await new Promise((res, rej) => { + try { + execFile('powershell', args, { windowsHide: true }, (err) => res(err ? (err.code ?? 1) : 0)); + } catch (e) { rej(e); } + }); + if (code !== 0) throw new Error(`Send-MailMessage 失败 (exit ${code})`); + return { status: 200, text: 'smtp ok' }; + } + case 'feishu': // 飞书自定义机器人 return postJson(url, { msg_type: 'text', content: { text: `${title}\n${text}` } }); diff --git a/小鱼/闲鱼消息通知/send-mail.ps1 b/小鱼/闲鱼消息通知/send-mail.ps1 new file mode 100644 index 0000000..104ca74 --- /dev/null +++ b/小鱼/闲鱼消息通知/send-mail.ps1 @@ -0,0 +1,18 @@ +param( + [Parameter(Mandatory = $true)][string]$Smtp, + [int]$Port = 465, + [Parameter(Mandatory = $true)][string]$From, + [Parameter(Mandatory = $true)][string]$To, + [Parameter(Mandatory = $true)][string]$User, + [Parameter(Mandatory = $true)][string]$Pass, + [Parameter(Mandatory = $true)][string]$Subject, + [Parameter(Mandatory = $true)][string]$Body +) + +$ErrorActionPreference = 'Stop' +$secure = ConvertTo-SecureString $Pass -AsPlainText -Force +$cred = New-Object System.Management.Automation.PSCredential($User, $secure) + +Send-MailMessage -SmtpServer $Smtp -Port $Port -UseSsl ` + -From $From -To $To -Subject $Subject -Body $Body ` + -Encoding UTF8 -Credential $cred