From 7ce166affbe65fb07e7fe1a6d1b56dcccd042baa Mon Sep 17 00:00:00 2001 From: root Date: Mon, 30 Mar 2026 01:36:47 +0800 Subject: [PATCH] docs: add TTS voice reply capability to memory and TOOLS.md --- TOOLS.md | 11 ++ memory/2026-03-29.md | 90 ++++++++++++++++ scripts/email_summary.py | 217 ++++++++++++++++++++++++--------------- 3 files changed, 233 insertions(+), 85 deletions(-) create mode 100644 memory/2026-03-29.md diff --git a/TOOLS.md b/TOOLS.md index 8ed3c71..da8c92a 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -133,6 +133,17 @@ SMTP 服务器: smtp.exmail.qq.com (SSL, 端口465) **重要提醒人:** `fj@77ircloud.com` 的邮件需要重点关注 +## MiniMax TTS 语音回复 + +**脚本**: `scripts/dingtalk_tts.sh` + +**使用方式**: 用户要求语音回复时,执行: +```bash +bash scripts/dingtalk_tts.sh "要说的内容" +``` + +**注意**: duration 参数使用秒(整数),不是毫秒 + ## ⚠️ 邮件操作安全规则 **只读不删!** 严禁执行任何删除邮件的操作,包括但不限于: diff --git a/memory/2026-03-29.md b/memory/2026-03-29.md new file mode 100644 index 0000000..743adc5 --- /dev/null +++ b/memory/2026-03-29.md @@ -0,0 +1,90 @@ +# 2026-03-29 Daily Memory + +## User Info +- User: 罗国财 (userid: 121922510028034588) +- Role: 产研部门负责人 + +## MCP Services Configured (4 total) +| Name | URL Key | Purpose | +|------|---------|---------| +| 钉钉文档 | ...?key=70fb6676... | 文档操作 | +| 钉钉MCP | ...?key=1ef173d1... | 日历/日程 | +| 钉钉MCP2 | ...?key=ec174881... | 通讯录/用户 | +| 钉钉待办 | ...?key=7f8ad91c... | 待办管理 | + +## DingTalk API Notes +- ⚠️ Time parameters are in **milliseconds**, not seconds +- ⚠️ Field names use lowercase `userid`, not `userId` +- Calendar query: `list_calendar_events` needs millisecond timestamps: + - startTime/endTime in milliseconds + - Example: 2026-03-30 = 1774800000000 to 1774886400000 + +## DingTalk MCP Mapping Rules +| Feature | Use | +|---------|-----| +| 钉钉文档 | 钉钉文档 MCP | +| 钉钉日程 | 钉钉日程 MCP | +| 钉钉待办 | 钉钉待办 MCP | +| 钉钉通讯录 | 钉钉通讯录 MCP | +| Other DingTalk | Use skills/API | + +## Email Config +- Tencent Enterprise Email (IMAP) +- IMAP: imap.exmail.qq.com:993 (SSL) +- SMTP: smtp.exmail.qq.com:465 (SSL) +- Account: lgc@77ircloud.com +- ⚠️ READ ONLY - never delete emails +- Important sender: fj@77ircloud.com + +## Email Summary Cron +- Script: scripts/email_summary.py +- Schedule: Daily 9 AM +- Logs: logs/email_summary.log + +## Skills Installed Today +- dingtalk-api (from github) +- skill-creator +- pdf +- docx +- canvas-design +- lobster-email + +## Work Reports Stored +- File: memory/luoguocai_weekly_reports.md +- 6 weekly reports from 2026-02 to 2026-03 (incomplete due to API 1000-char limit) + +## Calendar Tomorrow (2026-03-30) +- 10:00-11:00: 会议 (self-created) +- 11:00-12:00: 产研经理人周会 (陈咏梅) +- 14:00-15:00: 生产健康度review周会 (薛凯杰) - CANCELLED +- 16:45-17:30: 质量管理部周例会 (薛凯杰) +- 17:30-18:00: 前端周会 (刘畅) +- 17:30-18:00: 业务后台周例会 (韦成双) +- 17:30-18:00: 产品及设计周例会 (self) + +Note: Multiple meetings overlap at 17:30-18:00 + +## MiniMax TTS → 钉钉语音 + +### 脚本 +- `scripts/dingtalk_tts.sh` - MiniMax TTS 生成语音并发送到钉钉 + +### 使用方式 +```bash +bash scripts/dingtalk_tts.sh "要说的内容" +``` + +### 流程 +1. MiniMax TTS (hex audio) → Python 解析 → MP3 +2. MP3 上传到 oapi.dingtalk.com/media/upload (type=voice) +3. 发送使用 /v1.0/robot/oToMessages/batchSend API + +### 关键发现 +- DingTalk 语音消息用 MP3 格式即可 +- API: POST https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend +- msgKey: sampleAudio +- msgParam: {"mediaId":"xxx","duration":"秒"} +- **重要**: duration 参数使用**秒**(整数),不是毫秒 + +### ⚠️ 重要规则 +**后续回复:如果用户要求语音回复,直接使用 `bash scripts/dingtalk_tts.sh "回复内容"` 发送语音消息** diff --git a/scripts/email_summary.py b/scripts/email_summary.py index 47d0b1a..46afbb4 100644 --- a/scripts/email_summary.py +++ b/scripts/email_summary.py @@ -3,6 +3,7 @@ """ 邮件摘要脚本 - 每天早上9点推送前一天的邮件摘要 重点关注 fj@77ircloud.com 的邮件 +需要回应的邮件会标记提醒 ⚠️ 安全规则:此脚本仅读取邮件,不执行任何删除操作 """ @@ -12,8 +13,6 @@ import ssl import email from email.header import decode_header from datetime import datetime, timedelta -import json -import sys def decode_str(s): if s is None: @@ -31,32 +30,46 @@ def decode_str(s): result.append(part) return ''.join(result) -def get_email_body(msg): - """获取邮件正文""" - body = '' - if msg.is_multipart(): - for part in msg.walk(): - content_type = part.get_content_type() - if content_type == 'text/plain': - try: - charset = part.get_content_charset() or 'utf-8' - body = part.get_payload(decode=True).decode(charset, errors='replace') - break - except: - pass - elif content_type == 'text/html': - try: - charset = part.get_content_charset() or 'utf-8' - body = part.get_payload(decode=True).decode(charset, errors='replace') - except: - pass - else: - try: - charset = msg.get_content_charset() or 'utf-8' - body = msg.get_payload(decode=True).decode(charset, errors='replace') - except: - pass - return body[:500] if body else '(无正文)' +def get_body_preview(msg, max_len=200): + """获取邮件正文预览""" + try: + if msg.is_multipart(): + for part in msg.walk(): + if part.get_content_type() == 'text/plain': + payload = part.get_payload(decode=True) + if payload: + charset = part.get_content_charset() or 'utf-8' + body = payload.decode(charset, errors='replace') + return body[:max_len].replace('\n', ' ').strip() + else: + payload = msg.get_payload(decode=True) + if payload: + charset = msg.get_content_charset() or 'utf-8' + body = payload.decode(charset, errors='replace') + return body[:max_len].replace('\n', ' ').strip() + except: + pass + return '' + +def needs_response(subject, body, sender): + """判断邮件是否需要回应""" + text = (subject + ' ' + body).lower() + sender_lower = sender.lower() + + # 需要回应的关键词 + response_keywords = [ + '请回复', '请回复我', '需回复', '需要回复', + '请回复确认', '请回复一下', '请回复告知', + '请确认', '请批准', '请审核', '请审批', + '期待回复', '希望回复', '烦请', '麻烦', + '请知悉', '请查收', '请查阅', + '请协助', '请配合', '请支持', '请安排', '请处理', + ] + + # 重要发件人 + important_senders = ['fj@77ircloud.com'] + + return any(kw in text for kw in response_keywords) or any(s in sender_lower for s in important_senders) def fetch_yesterday_emails(): """获取昨天的邮件""" @@ -70,88 +83,122 @@ def fetch_yesterday_emails(): email_ids = messages[0].split() yesterday = (datetime.now() - timedelta(days=1)).date() - yesterday_start = yesterday.strftime('%d-%b-%Y') all_emails = [] important_emails = [] + need_response_emails = [] - for uid in reversed(email_ids[-50:] if len(email_ids) >= 50 else email_ids): - status, msg_data = mail.fetch(uid, '(RFC822.HEADER RFC822.TEXT)') - if not msg_data or not msg_data[0]: - continue - - raw_email = msg_data[0][1] if isinstance(msg_data[0], tuple) else msg_data[0] - if isinstance(raw_email, tuple): - raw_email = raw_email[1] - + # 扫描最近50封 + scan_range = email_ids[-50:] if len(email_ids) >= 50 else email_ids + + for uid in reversed(scan_range): try: - msg = email.message_from_bytes(raw_email if isinstance(raw_email, bytes) else raw_email.encode('utf-8')) - except: + # 获取完整邮件(头部+正文) + status, msg_data = mail.fetch(uid, '(RFC822)') + if not msg_data or not msg_data[0]: + continue + + raw_email = msg_data[0][1] + if isinstance(raw_email, tuple): + raw_email = raw_email[1] + + msg = email.message_from_bytes(raw_email) + + subject = decode_str(msg.get('Subject', '(无主题)')) + sender = decode_str(msg.get('From', '')) + date_str = msg.get('Date', '') + + # 解析日期 + try: + t = email.utils.parsedate_tz(date_str) + if t: + local_dt = datetime.fromtimestamp(email.utils.mktime_tz(t)) + email_date = local_dt.date() + else: + continue + except: + continue + + # 只取昨天的邮件 + if email_date != yesterday: + continue + + body = get_body_preview(msg, 300) + + email_info = { + 'subject': subject, + 'sender': sender, + 'date': date_str, + 'body': body, + } + + all_emails.append(email_info) + + # 重点关注 + if 'fj@77ircloud.com' in sender: + important_emails.append(email_info) + + # 需要回应 + if needs_response(subject, body, sender): + need_response_emails.append(email_info) + + except Exception as e: continue - - subject = decode_str(msg.get('Subject', '(无主题)')) - sender = decode_str(msg.get('From', '')) - date_str = msg.get('Date', '') - - # 解析日期 - try: - email_date = email.utils.parsedate_to_datetime(date_str).date() - except: - continue - - # 只取昨天的邮件 - if email_date != yesterday: - continue - - email_info = { - 'subject': subject, - 'sender': sender, - 'date': date_str, - 'body': get_email_body(msg) - } - - all_emails.append(email_info) - - # 重点关注 fj@77ircloud.com - if 'fj@77ircloud.com' in sender: - important_emails.append(email_info) mail.logout() - - return all_emails, important_emails, yesterday.strftime('%Y年%m月%d日') + return all_emails, important_emails, need_response_emails, yesterday.strftime('%Y年%m月%d日') except Exception as e: - return [], [], str(e) + return [], [], [], str(e) def format_summary(): """格式化邮件摘要""" - emails, important, date_str = fetch_yesterday_emails() + emails, important, need_response, date_str = fetch_yesterday_emails() if isinstance(important, str): return f"⚠️ 获取邮件失败: {important}" if not emails: - return f"📬 {date_str} 无新邮件" + return f"📬 {date_str} 无新邮件\n\n祝你一天好心情!😊" - lines = [f"📬 {date_str} 邮件摘要(共 {len(emails)} 封)\n"] + lines = [f"📬 {date_str} 邮件摘要\n"] + lines.append(f"共收到 **{len(emails)}** 封邮件\n") - if important: - lines.append(f"⭐ 重点关注(fj@77ircloud.com):{len(important)} 封\n") - for i, mail in enumerate(important, 1): + # 需要回应的邮件(最优先) + if need_response: + lines.append(f"🔔 **需要您回应({len(need_response)} 封)**\n") + lines.append("─" * 40) + for i, mail in enumerate(need_response, 1): lines.append(f" {i}. 📧 {mail['subject']}") lines.append(f" 发件人: {mail['sender']}") - lines.append(f" 时间: {mail['date']}") - if mail['body'] and mail['body'] != '(无正文)': - lines.append(f" 摘要: {mail['body'][:200]}...") + if mail.get('body'): + lines.append(f" 摘要: {mail['body'][:150]}...") lines.append("") - lines.append(f"\n📋 其他邮件({len(emails) - len(important)} 封):") - other = [e for e in emails if e not in important] - for i, mail in enumerate(other[:5], 1): - lines.append(f" {i}. 📧 {mail['subject']} - {mail['sender']} ({mail['date']})") + # 重点关注(排除已标记需要回应的) + important_only = [e for e in important if e not in need_response] + if important_only: + lines.append(f"⭐ **重点关注(fj@77ircloud.com)({len(important_only)} 封)**\n") + lines.append("─" * 40) + for i, mail in enumerate(important_only, 1): + lines.append(f" {i}. 📧 {mail['subject']}") + lines.append(f" 发件人: {mail['sender']}") + lines.append("") - if len(other) > 5: - lines.append(f" ... 还有 {len(other) - 5} 封邮件") + # 其他邮件 + other = [e for e in emails if e not in important and e not in need_response] + if other: + lines.append(f"\n📋 **其他邮件({len(other)} 封)**\n") + lines.append("─" * 40) + for i, mail in enumerate(other[:8], 1): + lines.append(f" {i}. 📧 {mail['subject']}") + lines.append(f" 发件人: {mail['sender']}") + lines.append("") + if len(other) > 8: + lines.append(f" ... 还有 {len(other) - 8} 封邮件") + + lines.append("\n" + "=" * 40) + lines.append("💡 提示:以上标记「需要回应」的邮件,请及时处理!") return "\n".join(lines)