微信朋友圈每日总结
概述
每天定时提醒用户上传微信朋友圈截图,AI 利用多模态视觉能力自动识别截图中的文字、图片和互动信息,生成结构化的朋友圈日报。总结内容涵盖热门话题、精彩内容、情感分析、互动亮点等维度,支持通过微信推送通道(Server酱 / PushPlus / 企业微信机器人)将总结发送到用户的微信。
触发词
- 朋友圈总结
- 每日朋友圈
- 朋友圈日报
- 总结朋友圈
- 微信朋友圈总结
- 朋友圈每日总结
- 修改朋友圈总结
- 查看朋友圈总结
- 删除朋友圈总结
- moments summary
工作流程
阶段一:配置收集(弹窗交互)
当用户触发本技能时,首先检查是否已有配置(读取 ~/.workbuddy/skills/微信朋友圈每日总结/config.json),并根据状态弹出配置窗口:
无配置 → 弹出空白配置窗口(显示「保存并创建定时任务 ↗」按钮)
有配置 → 弹出已填充的配置窗口(显示「更新定时任务 ↗」和「删除定时任务」按钮)
配置窗口收集以下信息:
-
推送通道(单选,可选):
- Server酱 Turbo(推荐,免费每日5条,需在 https://sct.ftqq.com/ 注册获取 SendKey)
- PushPlus(免费每日200条,需在 http://www.pushplus.plus/ 注册获取 Token)
- 企业微信机器人 Webhook(需企业微信管理员权限)
- 不推送,仅在对话中查看总结 -
API Key / Token:对应推送通道的密钥(选择"不推送"时隐藏)
-
总结时间:每天几点提醒上传截图(如 21:00,默认晚上朋友圈内容最丰富时)
-
提醒模式(单选):
- 每日提醒(固定每天同一时间)
- 仅工作日(周一至周五)
- 单次提醒(指定某一天) -
总结风格(单选):
- 简洁版(3-5 条要点,适合快速浏览)
- 标准版(包含话题、精彩内容、情感分析,适中篇幅)
- 详细版(全面深度分析,含每条动态详情) -
总结内容(多选复选框):
- 热门话题(关键词聚类)
- 精彩内容(有趣/有价值的动态摘录)
- 情感分析(整体情绪走向:积极/消极/中性)
- 互动亮点(点赞数高的、评论热烈的)
- 分类统计(工作/生活/美食/旅行/育儿等分类占比)
- 图片视频亮点(描述有特色的图片或视频内容)
- 好友动态榜(发朋友圈最活跃的好友 Top3) -
自定义消息前缀(可选):在总结内容前附加的文字,如"今日朋友圈日报:"
阶段二:配置验证与保存
- 用户在弹窗中填写完成后点击「保存并创建定时任务 ↗」
- AI 收到配置数据后:
- 验证推送通道连通性(发送一条测试消息到微信,若选择"不推送"则跳过)
- 将配置保存到~/.workbuddy/skills/微信朋友圈每日总结/config.json
- 创建定时任务(automation)
阶段三:创建定时任务
使用 automation_update 工具创建定时任务:
- name:
朋友圈每日总结 - scheduleType:
recurring(每日/工作日)或once(单次) - rrule: 根据用户选择的模式和总结时间生成 RRULE
- 每日:
FREQ=DAILY;BYHOUR={时};BYMINUTE={分} - 仅工作日:
FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;BYHOUR={时};BYMINUTE={分} - prompt: 执行朋友圈总结的指令(见下方自动化执行指令)
- cwds: 当前工作目录
阶段四:每日执行流程
定时任务触发后,AI 执行以下步骤:
-
提醒上传截图:向用户发送消息,提醒上传今日的朋友圈截图
- 告知用户:打开微信 → 发现 → 朋友圈 → 截图(可多张)→ 通过聊天附件上传
- 说明:截图应覆盖今日所有可见的朋友圈内容 -
等待用户上传:用户通过聊天附件上传截图(一张或多张)
-
识别截图内容:逐张读取截图,利用多模态视觉能力识别:
- 发布者昵称
- 发布时间
- 文字内容
- 图片/视频描述
- 点赞数、评论数、评论内容
- 定位信息
- 分享链接标题 -
生成结构化总结:根据配置的总结风格和内容选项,生成总结报告
-
推送总结:
- 在对话中展示完整总结
- 若配置了推送通道,将总结通过 Server酱/PushPlus/企业微信推送到微信
总结报告格式模板
简洁版
## 朋友圈日报 — {日期}
### 今日要点
1. {要点1}
2. {要点2}
3. {要点3}
### 热门话题
{话题1}、{话题2}、{话题3}
---
共 {N} 位好友发了 {M} 条动态 | 由微信朋友圈每日总结自动生成
标准版
## 朋友圈日报 — {日期} {星期}
{自定义前缀}
### 今日概览
共 {N} 位好友发布了 {M} 条动态
### 热门话题
1. {话题1}:{简述}
2. {话题2}:{简述}
3. {话题3}:{简述}
### 精彩内容
- **{好友名}**:{动态摘录}
- **{好友名}**:{动态摘录}
- **{好友名}**:{动态摘录}
### 情感分析
今日朋友圈整体氛围:{积极/平淡/消极}
{情感分析说明}
### 互动亮点
- 最高点赞:{好友名} 的 {内容摘要}({N} 个赞)
- 最热评论:{好友名} 的 {内容摘要}({N} 条评论)
---
{自定义后缀或页脚}
详细版
## 朋友圈日报 — {日期} {星期}
{自定义前缀}
### 今日数据概览
- 发布动态的好友数:{N}
- 动态总数:{M}
- 含图片动态:{X} 条
- 含视频动态:{Y} 条
- 纯文字动态:{Z} 条
- 总点赞数:{P}
- 总评论数:{Q}
### 热门话题排行
1. {话题1}({N}人提及):{话题简述与代表性动态}
2. {话题2}({N}人提及):{话题简述与代表性动态}
3. {话题3}({N}人提及):{话题简述与代表性动态}
4. {话题4}({N}人提及):{话题简述与代表性动态}
5. {话题5}({N}人提及):{话题简述与代表性动态}
### 精彩内容精选
#### {好友名1} — {时间}
> {动态文字内容}
{图片/视频描述}
点赞 {N} | 评论 {M}
#### {好友名2} — {时间}
> {动态文字内容}
{图片/视频描述}
点赞 {N} | 评论 {M}
#### {好友名3} — {时间}
> {动态文字内容}
{图片/视频描述}
点赞 {N} | 评论 {M}
### 情感分析
- 整体情绪:{积极/平淡/消极/混合}
- 情绪分布:积极 {X}% | 中性 {Y}% | 消极 {Z}%
- 情绪分析:{对今日朋友圈整体情绪的解读}
### 互动亮点
- 最高点赞动态:{好友名} — {内容摘要}({N} 个赞)
- 最多评论动态:{好友名} — {内容摘要}({M} 条评论)
- 精彩评论互动:{好友A} 与 {好友B} 在 {好友C} 的动态下互动
### 分类统计
| 分类 | 动态数 | 占比 |
|------|--------|------|
| 生活日常 | {N} | {X}% |
| 美食 | {N} | {X}% |
| 工作 | {N} | {X}% |
| 旅行 | {N} | {X}% |
| 育儿 | {N} | {X}% |
| 其他 | {N} | {X}% |
### 图片视频亮点
- {好友名}:{图片/视频描述}
- {好友名}:{图片/视频描述}
### 好友动态榜
1. {好友名}:{N} 条动态
2. {好友名}:{N} 条动态
3. {好友名}:{N} 条动态
---
由微信朋友圈每日总结自动生成 · {时间}
配置弹窗
当技能触发时,使用 show_widget 展示以下交互式配置弹窗:
<div style="font-family:'Segoe UI','Microsoft YaHei',sans-serif;max-width:680px;margin:0 auto;padding:24px;background:#f8fafc;border-radius:12px;box-sizing:border-box;">
<h2 style="margin:0 0 4px 0;color:#1a1a2e;font-size:22px;">朋友圈每日总结 — 配置</h2>
<p style="margin:0 0 20px 0;color:#666;font-size:13px;">设置总结推送通道、时间、风格和内容偏好</p>
<!-- ===== 推送通道 ===== -->
<div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:10px;">推送通道</label>
<div id="channelRadios" style="display:flex;flex-wrap:wrap;gap:8px;">
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #0078d4;border-radius:8px;background:#e8f4fd;cursor:pointer;font-size:13px;font-weight:600;color:#0078d4;transition:all 0.2s;">
<input type="radio" name="channel" value="serverchan" checked onchange="switchChannel(this.value)" style="accent-color:#0078d4;"/>
Server酱(推荐)
</label>
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #ddd;border-radius:8px;background:#fff;cursor:pointer;font-size:13px;transition:all 0.2s;">
<input type="radio" name="channel" value="pushplus" onchange="switchChannel(this.value)" style="accent-color:#0078d4;"/>
PushPlus
</label>
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #ddd;border-radius:8px;background:#fff;cursor:pointer;font-size:13px;transition:all 0.2s;">
<input type="radio" name="channel" value="wecom" onchange="switchChannel(this.value)" style="accent-color:#0078d4;"/>
企业微信机器人
</label>
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #ddd;border-radius:8px;background:#fff;cursor:pointer;font-size:13px;transition:all 0.2s;">
<input type="radio" name="channel" value="none" onchange="switchChannel(this.value)" style="accent-color:#0078d4;"/>
不推送
</label>
</div>
<div id="channelHint" style="margin-top:8px;font-size:12px;color:#888;line-height:1.6;">
Server酱:微信扫码注册 → <a href="https://sct.ftqq.com/" target="_blank" style="color:#0078d4;">sct.ftqq.com</a> 获取 SendKey
</div>
</div>
<!-- ===== API密钥 ===== -->
<div id="apiKeySection" style="background:#fff;border-radius:10px;padding:16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:4px;"><span id="keyLabel">SendKey</span></label>
<p style="margin:0 0 8px 0;font-size:12px;color:#999;">在对应平台注册后获取,用于微信消息推送</p>
<input id="apiKey" type="password" placeholder="请输入 SendKey" style="width:100%;padding:10px 14px;border:1.5px solid #ddd;border-radius:8px;font-size:14px;box-sizing:border-box;transition:border-color 0.2s;" onfocus="this.style.borderColor='#0078d4'" onblur="this.style.borderColor='#ddd'"/>
<button id="testBtn" onclick="testChannel()" style="margin-top:8px;padding:6px 18px;border:1px solid #0078d4;border-radius:6px;background:#fff;color:#0078d4;cursor:pointer;font-size:12px;font-weight:600;">发送测试消息</button>
<span id="testResult" style="margin-left:8px;font-size:12px;"></span>
</div>
<!-- ===== 总结时间 ===== -->
<div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:10px;">总结时间 & 频率</label>
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<input id="remindTime" type="time" value="21:00" style="padding:8px 12px;border:1.5px solid #ddd;border-radius:8px;font-size:14px;width:130px;"/>
<select id="remindMode" style="padding:8px 12px;border:1.5px solid #ddd;border-radius:8px;font-size:14px;min-width:140px;">
<option value="daily">每日提醒</option>
<option value="weekday">仅工作日 (周一~周五)</option>
<option value="once">单次提醒</option>
</select>
<input id="onceDate" type="date" style="padding:8px 12px;border:1.5px solid #ddd;border-radius:8px;font-size:14px;width:150px;display:none;"/>
</div>
<p style="margin:8px 0 0 0;font-size:12px;color:#999;">建议设置在晚上 20:00-22:00,此时朋友圈内容最丰富</p>
</div>
<!-- ===== 总结风格 ===== -->
<div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:10px;">总结风格</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #ddd;border-radius:8px;background:#fff;cursor:pointer;font-size:13px;transition:all 0.2s;" id="label_brief">
<input type="radio" name="style" value="brief" onchange="updateStyle(this.value)" style="accent-color:#0078d4;"/>
简洁版
</label>
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #0078d4;border-radius:8px;background:#e8f4fd;cursor:pointer;font-size:13px;font-weight:600;color:#0078d4;transition:all 0.2s;" id="label_standard">
<input type="radio" name="style" value="standard" checked onchange="updateStyle(this.value)" style="accent-color:#0078d4;"/>
标准版(推荐)
</label>
<label style="display:flex;align-items:center;gap:6px;padding:8px 14px;border:2px solid #ddd;border-radius:8px;background:#fff;cursor:pointer;font-size:13px;transition:all 0.2s;" id="label_detailed">
<input type="radio" name="style" value="detailed" onchange="updateStyle(this.value)" style="accent-color:#0078d4;"/>
详细版
</label>
</div>
</div>
<!-- ===== 总结内容 ===== -->
<div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:10px;">总结内容(可多选)</label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="topics" checked style="accent-color:#0078d4;"/> 热门话题
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="highlights" checked style="accent-color:#0078d4;"/> 精彩内容
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="sentiment" checked style="accent-color:#0078d4;"/> 情感分析
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="interactions" checked style="accent-color:#0078d4;"/> 互动亮点
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="categories" style="accent-color:#0078d4;"/> 分类统计
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;">
<input type="checkbox" class="summaryContent" value="media" style="accent-color:#0078d4;"/> 图片视频亮点
</label>
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;padding:6px 8px;border-radius:6px;grid-column:1/-1;">
<input type="checkbox" class="summaryContent" value="topPosters" style="accent-color:#0078d4;"/> 好友动态榜 Top3
</label>
</div>
</div>
<!-- ===== 自定义前缀 ===== -->
<div style="background:#fff;border-radius:10px;padding:16px;margin-bottom:18px;box-shadow:0 1px 3px rgba(0,0,0,0.06);">
<label style="display:block;font-weight:700;color:#333;font-size:14px;margin-bottom:4px;">自定义消息前缀(可选)</label>
<p style="margin:0 0 8px 0;font-size:12px;color:#999;">在总结内容前附加的问候语或备注</p>
<input id="prefix" type="text" placeholder="例如:今日朋友圈日报:" style="width:100%;padding:10px 14px;border:1.5px solid #ddd;border-radius:8px;font-size:14px;box-sizing:border-box;"/>
</div>
<!-- ===== 操作按钮 ===== -->
<div style="display:flex;gap:12px;align-items:center;">
<button id="saveBtn" onclick="saveAndSchedule()" style="flex:1;padding:12px 24px;border:none;border-radius:8px;background:linear-gradient(135deg,#0078d4,#005a9e);color:#fff;cursor:pointer;font-size:15px;font-weight:700;transition:all 0.2s;box-shadow:0 2px 8px rgba(0,120,212,0.3);">保存并创建定时任务 ↗</button>
<button id="updateBtn" onclick="updateSchedule()" style="display:none;flex:1;padding:12px 24px;border:none;border-radius:8px;background:linear-gradient(135deg,#00a854,#008040);color:#fff;cursor:pointer;font-size:15px;font-weight:700;transition:all 0.2s;box-shadow:0 2px 8px rgba(0,168,84,0.3);">更新定时任务 ↗</button>
<button id="deleteBtn" onclick="deleteSchedule()" style="display:none;padding:12px 24px;border:1.5px solid #e74c3c;border-radius:8px;background:#fff;color:#e74c3c;cursor:pointer;font-size:15px;font-weight:600;transition:all 0.2s;">删除定时任务</button>
</div>
<div id="statusMsg" style="margin-top:12px;font-size:13px;text-align:center;"></div>
</div>
<script>
// ===== 初始化:尝试加载已有配置 =====
let existingConfig = null;
try {
const stored = window._config_hint || null;
if (stored) {
existingConfig = JSON.parse(stored);
loadExistingConfig();
}
} catch(e) {}
function loadExistingConfig() {
if (!existingConfig) return;
if (existingConfig.channel) {
document.querySelector('input[name="channel"][value="'+existingConfig.channel+'"]').checked = true;
switchChannel(existingConfig.channel);
}
if (existingConfig.apiKey) document.getElementById('apiKey').value = existingConfig.apiKey;
if (existingConfig.remindTime) document.getElementById('remindTime').value = existingConfig.remindTime;
if (existingConfig.remindMode) {
document.getElementById('remindMode').value = existingConfig.remindMode;
if (existingConfig.remindMode === 'once') {
document.getElementById('onceDate').style.display = '';
if (existingConfig.onceDate) document.getElementById('onceDate').value = existingConfig.onceDate;
}
}
if (existingConfig.style) {
document.querySelector('input[name="style"][value="'+existingConfig.style+'"]').checked = true;
updateStyle(existingConfig.style);
}
if (existingConfig.contents) {
document.querySelectorAll('.summaryContent').forEach(cb => {
cb.checked = existingConfig.contents.includes(cb.value);
});
}
if (existingConfig.prefix) document.getElementById('prefix').value = existingConfig.prefix;
document.getElementById('saveBtn').style.display = 'none';
document.getElementById('updateBtn').style.display = '';
document.getElementById('deleteBtn').style.display = '';
}
// ===== 切换推送通道 =====
function switchChannel(channel) {
const labels = document.querySelectorAll('#channelRadios label');
labels.forEach(l => {
const radio = l.querySelector('input');
if (radio && radio.value === channel) {
l.style.borderColor = '#0078d4';
l.style.background = '#e8f4fd';
l.style.color = '#0078d4';
l.style.fontWeight = '600';
} else {
l.style.borderColor = '#ddd';
l.style.background = '#fff';
l.style.color = '#555';
l.style.fontWeight = '400';
}
});
const keySection = document.getElementById('apiKeySection');
const keyLabel = document.getElementById('keyLabel');
const keyInput = document.getElementById('apiKey');
const hint = document.getElementById('channelHint');
if (channel === 'none') {
keySection.style.display = 'none';
hint.innerHTML = '总结将在对话中直接展示,不推送到微信';
} else {
keySection.style.display = '';
if (channel === 'serverchan') {
keyLabel.textContent = 'SendKey';
keyInput.placeholder = '请输入 SendKey(如 SCT123456xxxxx)';
hint.innerHTML = 'Server酱:微信扫码注册 → <a href="https://sct.ftqq.com/" target="_blank" style="color:#0078d4;">sct.ftqq.com</a> 获取 SendKey';
} else if (channel === 'pushplus') {
keyLabel.textContent = 'Token';
keyInput.placeholder = '请输入 Token(如 a1b2c3d4e5f6)';
hint.innerHTML = 'PushPlus:微信扫码注册 → <a href="http://www.pushplus.plus/" target="_blank" style="color:#0078d4;">pushplus.plus</a> 获取 Token';
} else if (channel === 'wecom') {
keyLabel.textContent = 'Webhook URL';
keyInput.placeholder = '请输入 Webhook 地址';
hint.innerHTML = '企业微信机器人:群聊中添加机器人 → 复制 Webhook 地址';
}
}
document.getElementById('testResult').textContent = '';
}
// ===== 更新总结风格样式 =====
function updateStyle(style) {
['brief', 'standard', 'detailed'].forEach(s => {
const label = document.getElementById('label_' + s);
if (s === style) {
label.style.borderColor = '#0078d4';
label.style.background = '#e8f4fd';
label.style.color = '#0078d4';
label.style.fontWeight = '600';
} else {
label.style.borderColor = '#ddd';
label.style.background = '#fff';
label.style.color = '#555';
label.style.fontWeight = '400';
}
});
}
// ===== 提醒模式切换 =====
document.getElementById('remindMode').addEventListener('change', function() {
document.getElementById('onceDate').style.display = this.value === 'once' ? '' : 'none';
});
// ===== 收集配置数据 =====
function collectConfig() {
const channel = document.querySelector('input[name="channel"]:checked')?.value || 'serverchan';
const apiKey = document.getElementById('apiKey').value.trim();
const remindTime = document.getElementById('remindTime').value;
const remindMode = document.getElementById('remindMode').value;
const onceDate = document.getElementById('onceDate').value;
const style = document.querySelector('input[name="style"]:checked')?.value || 'standard';
const contents = [];
document.querySelectorAll('.summaryContent:checked').forEach(cb => contents.push(cb.value));
const prefix = document.getElementById('prefix').value.trim();
// 验证
if (channel !== 'none' && !apiKey) { showStatus('请输入推送通道的密钥', 'error'); return null; }
if (!remindTime) { showStatus('请选择总结时间', 'error'); return null; }
if (remindMode === 'once' && !onceDate) { showStatus('单次提醒请选择日期', 'error'); return null; }
return {
channel, apiKey, remindTime, remindMode, onceDate, style, contents, prefix,
isUpdate: !!existingConfig
};
}
// ===== 测试推送通道 =====
function testChannel() {
const config = collectConfig();
if (!config) return;
const btn = document.getElementById('testBtn');
const resultEl = document.getElementById('testResult');
btn.disabled = true;
btn.textContent = '发送中...';
resultEl.textContent = '';
resultEl.style.color = '#888';
sendPrompt('__MOMENTS_TEST__' + JSON.stringify(config));
}
// ===== 保存并创建定时任务 =====
function saveAndSchedule() {
const config = collectConfig();
if (!config) return;
config.isUpdate = false;
sendPrompt('__MOMENTS_SAVE__' + JSON.stringify(config));
}
// ===== 更新定时任务 =====
function updateSchedule() {
const config = collectConfig();
if (!config) return;
config.isUpdate = true;
sendPrompt('__MOMENTS_SAVE__' + JSON.stringify(config));
}
// ===== 删除定时任务 =====
function deleteSchedule() {
if (confirm('确定要删除此朋友圈总结定时任务吗?此操作不可恢复。')) {
sendPrompt('__MOMENTS_DELETE__' + JSON.stringify(existingConfig || {}));
}
}
// ===== 显示状态消息 =====
function showStatus(msg, type) {
const el = document.getElementById('statusMsg');
el.textContent = msg;
el.style.color = type === 'error' ? '#e74c3c' : (type === 'success' ? '#27ae60' : '#333');
}
window._setStatus = function(msg, type) { showStatus(msg, type); };
window._setTestResult = function(msg, success) {
const el = document.getElementById('testResult');
el.textContent = msg;
el.style.color = success ? '#27ae60' : '#e74c3c';
const btn = document.getElementById('testBtn');
btn.disabled = false;
btn.textContent = '发送测试消息';
};
</script>
AI 执行指令
入口判断
当技能被触发时,AI 按以下顺序判断当前状态:
- 检查是否已有配置:读取
~/.workbuddy/skills/微信朋友圈每日总结/config.json - 检查是否已有 automation:使用
automation_updatemode="list" 查找名为"朋友圈每日总结"的任务 - 根据状态决定行为:
- 无配置 + 无任务 → 弹出空白配置窗口
- 有配置 + 有任务 → 弹出已填充的配置窗口(显示更新/删除按钮)
- 用户说"查看"/"状态" → 直接显示当前配置和任务状态
- 用户说"删除"/"取消" → 删除配置和 automation
- 用户直接上传了朋友圈截图 → 跳转到阶段四的识别总结流程
配置保存流程(收到 __MOMENTS_SAVE__ 消息后)
-
解析配置JSON
-
发送测试消息(若 channel 不为 "none"):
- 向对应推送通道发送一条测试消息
- 标题:「朋友圈每日总结 — 测试消息」
- 内容:「这是一条测试消息,用于验证推送通道是否正常工作。如果您收到此消息,说明配置正确!」 -
保存配置文件:
json { "channel": "serverchan", "apiKey": "SCT123456xxxxx", "remindTime": "21:00", "remindMode": "daily", "style": "standard", "contents": ["topics", "highlights", "sentiment", "interactions"], "prefix": "今日朋友圈日报:", "createdAt": "2026-07-28T21:00:00", "updatedAt": "2026-07-28T21:00:00" }
写入~/.workbuddy/skills/微信朋友圈每日总结/config.json -
创建/更新 automation:
- 根据 remindMode 和 remindTime 生成 RRULE
- 每日提醒:FREQ=DAILY;BYHOUR={HH};BYMINUTE={MM}
- 仅工作日:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;BYHOUR={HH};BYMINUTE={MM}
- 单次提醒:使用scheduleType="once"+scheduledAt="YYYY-MM-DDTHH:MM+08:00"
- name:朋友圈每日总结
- prompt:自动化执行指令(见下方) -
通知用户:告知配置已保存,定时任务已创建,下次执行时间
自动化执行指令(automation prompt)
写入 automation 的 prompt 应为以下内容(完整自包含):
你是一个朋友圈每日总结任务。请执行以下步骤:
1. 读取配置文件 ~/.workbuddy/skills/微信朋友圈每日总结/config.json,获取总结风格、内容选项和推送通道配置。
2. 向用户发送消息提醒上传今日朋友圈截图:
"您好!现在是每日朋友圈总结时间。请打开微信 → 发现 → 朋友圈,截图今日的朋友圈内容并上传。
截图要求:
- 尽量覆盖今日所有可见的朋友圈动态
- 可以上传多张截图(滚动截图或分屏截图)
- 截图需清晰,文字可辨认
上传方式:通过聊天框左侧的附件按钮 📎 上传图片"
3. 等待用户上传截图。用户可能上传一张或多张图片。
4. 逐张读取截图,利用多模态视觉能力识别每张截图中的:
- 发布者昵称
- 发布时间
- 文字内容
- 图片或视频的描述
- 点赞数
- 评论数和评论内容
- 定位信息
- 分享链接标题
5. 汇总所有截图识别结果,根据配置中的 style(brief/standard/detailed)和 contents 选项,生成结构化总结报告。
6. 总结报告格式参考 SKILL.md 中的模板。
7. 在对话中展示完整总结。
8. 若配置了推送通道(channel 不为 "none"),将总结通过对应推送通道发送到微信:
- serverchan: POST https://sctapi.ftqq.com/{apiKey}.send 带 title 和 desp 参数
- pushplus: POST http://www.pushplus.plus/send 带 token, title, content, template 参数
- wecom: POST {webhook_url} 带 msgtype:markdown 和 markdown.content
9. 消息标题格式:"朋友圈日报 — {日期}"
10. 完成后简要告知用户总结已生成并推送。
测试消息流程(收到 __MOMENTS_TEST__ 消息后)
- 解析配置
- 向对应推送通道发送一条测试消息:
- 标题:「朋友圈每日总结 — 测试消息」
- 内容:「这是一条测试消息,用于验证推送通道是否正常工作。\n\n如果收到此消息,说明配置正确!\n\n推送通道:{channel}\n总结时间:{remindTime}\n总结风格:{style}」 - 通过 show_widget 的
_setTestResult回调通知用户测试结果
删除任务流程(收到 __MOMENTS_DELETE__ 消息后)
- 使用
automation_updatemode="list" 查找"朋友圈每日总结"相关任务 - 使用
automation_updatemode="delete" 删除找到的 automation - 删除
~/.workbuddy/skills/微信朋友圈每日总结/config.json - 通知用户已删除
手动总结流程(用户直接上传截图时)
当用户未配置定时任务但直接上传了朋友圈截图并要求总结时:
- 读取截图(逐张读取,利用多模态视觉能力)
- 识别截图中的朋友圈内容
- 使用默认的"标准版"风格生成总结
- 在对话中展示总结
- 询问用户是否需要配置每日定时总结
微信消息发送
通道1:Server酱 Turbo(推荐默认)
import urllib.request
import urllib.parse
def send_via_serverchan(sendkey, title, content):
url = f"https://sctapi.ftqq.com/{sendkey}.send"
data = urllib.parse.urlencode({"title": title, "desp": content}).encode("utf-8")
req = urllib.request.Request(url, data=data, method="POST")
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
注册地址:https://sct.ftqq.com/ (微信扫码登录 → 获取 SendKey)
通道2:PushPlus
import urllib.request
import json
def send_via_pushplus(token, title, content):
url = "http://www.pushplus.plus/send"
data = json.dumps({"token": token, "title": title, "content": content, "template": "markdown"}).encode("utf-8")
req = urllib.request.Request(url, data=data, method="POST",
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
注册地址:http://www.pushplus.plus/ (微信扫码登录 → 获取 Token)
通道3:企业微信机器人 Webhook
import urllib.request
import json
def send_via_wecom_webhook(webhook_url, content):
data = json.dumps({"msgtype": "markdown", "markdown": {"content": content}}).encode("utf-8")
req = urllib.request.Request(webhook_url, data=data, method="POST",
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
依赖说明
| 组件 | 服务 | 费用 |
|---|---|---|
| 截图识别 | WorkBuddy 多模态视觉能力 | 内置功能 |
| 微信推送 | Server酱 Turbo | 免费每日5条 |
| 微信推送 | PushPlus | 免费每日200条 |
| 微信推送 | 企业微信机器人 | 免费(需企业微信) |
| 定时调度 | WorkBuddy automation | 内置功能 |
注意事项
- 截图质量:截图需清晰,文字可辨认。模糊或过小的截图可能影响识别效果
- 截图覆盖:建议尽量覆盖今日所有可见的朋友圈内容,多张截图可拼接完整内容
- 隐私保护:总结内容仅在本地处理和用户指定的推送通道传输,不会上传到其他第三方服务
- 首次使用:若选择推送到微信,需先在 Server酱/PushPlus 注册并获取密钥(微信扫码即可)
- 网络要求:需能访问外网(推送服务 API)
- API限制:推送服务有每日条数上限
- 修改配置:重新触发技能即可进入编辑模式
- 总结质量:AI 识别截图内容后生成的总结基于视觉理解,可能与实际内容有细微差异
快速排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 未收到微信推送 | SendKey/Token 错误 | 检查并重新输入密钥 |
| 截图识别不全 | 截图不完整或模糊 | 重新截图,确保覆盖全部内容 |
| 定时未触发 | automation 被暂停 | 检查 automation 状态 |
| 推送格式混乱 | Markdown 不兼容 | PushPlus 尝试切换 template 为 "html" |
| 总结内容为空 | 截图中无朋友圈内容 | 确认截图来源正确 |
支付宝付费改造契约(PAYSKILL)
本技能为付费技能(PAYSKILL)。调用遵循支付宝 AI 生意画像契约,由平台调度层执行 probe → pay → complete → ack 流程:
- probe(探测):平台调用前先探测是否已持有本技能的支付宝有效订阅 / 付费凭证。已付费则直接放行。
- 402 Payment-Needed:未付费时,脚本向标准输出返回 HTTP 状态
402与Payment-Needed标记,提示用户通过支付宝完成付费(pay),平台据此唤起付费页。 - complete(扣费确认):用户在支付宝完成扣费后,平台下发
Payment-Proof(付费凭证)。 - ack(验签放行):平台携带
Payment-Proof重试本次调用;scripts/payment_gate.py验签该凭证,确认有效后放行主流程。
订阅持久化与幂等:
- 付费状态缓存在本地
.pay_cache/state.json,避免每次调用重复付费(订阅持久化)。 - 同一
Payment-Proof在 24h 幂等窗口内仅生效一次,避免重复扣费。
现金流改造后的调用套路:
用户调用 → 平台 probe →
未付费:下发 402 Payment-Needed → 支付宝 pay → complete → 下发 Payment-Proof
已付费/携带 Proof:脚本 ack 验签 → 放行主流程
> 本契约由 SkillHUB 平台调度层解析,脚本本地仅处理 402 / 验签 / 持久化 三环节;付费本体交给支付宝。