docs: add TTS voice reply capability to memory and TOOLS.md
This commit is contained in:
11
TOOLS.md
11
TOOLS.md
@@ -133,6 +133,17 @@ SMTP 服务器: smtp.exmail.qq.com (SSL, 端口465)
|
|||||||
|
|
||||||
**重要提醒人:** `fj@77ircloud.com` 的邮件需要重点关注
|
**重要提醒人:** `fj@77ircloud.com` 的邮件需要重点关注
|
||||||
|
|
||||||
|
## MiniMax TTS 语音回复
|
||||||
|
|
||||||
|
**脚本**: `scripts/dingtalk_tts.sh`
|
||||||
|
|
||||||
|
**使用方式**: 用户要求语音回复时,执行:
|
||||||
|
```bash
|
||||||
|
bash scripts/dingtalk_tts.sh "要说的内容"
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**: duration 参数使用秒(整数),不是毫秒
|
||||||
|
|
||||||
## ⚠️ 邮件操作安全规则
|
## ⚠️ 邮件操作安全规则
|
||||||
|
|
||||||
**只读不删!** 严禁执行任何删除邮件的操作,包括但不限于:
|
**只读不删!** 严禁执行任何删除邮件的操作,包括但不限于:
|
||||||
|
|||||||
90
memory/2026-03-29.md
Normal file
90
memory/2026-03-29.md
Normal file
@@ -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 "回复内容"` 发送语音消息**
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
邮件摘要脚本 - 每天早上9点推送前一天的邮件摘要
|
邮件摘要脚本 - 每天早上9点推送前一天的邮件摘要
|
||||||
重点关注 fj@77ircloud.com 的邮件
|
重点关注 fj@77ircloud.com 的邮件
|
||||||
|
需要回应的邮件会标记提醒
|
||||||
|
|
||||||
⚠️ 安全规则:此脚本仅读取邮件,不执行任何删除操作
|
⚠️ 安全规则:此脚本仅读取邮件,不执行任何删除操作
|
||||||
"""
|
"""
|
||||||
@@ -12,8 +13,6 @@ import ssl
|
|||||||
import email
|
import email
|
||||||
from email.header import decode_header
|
from email.header import decode_header
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def decode_str(s):
|
def decode_str(s):
|
||||||
if s is None:
|
if s is None:
|
||||||
@@ -31,32 +30,46 @@ def decode_str(s):
|
|||||||
result.append(part)
|
result.append(part)
|
||||||
return ''.join(result)
|
return ''.join(result)
|
||||||
|
|
||||||
def get_email_body(msg):
|
def get_body_preview(msg, max_len=200):
|
||||||
"""获取邮件正文"""
|
"""获取邮件正文预览"""
|
||||||
body = ''
|
try:
|
||||||
if msg.is_multipart():
|
if msg.is_multipart():
|
||||||
for part in msg.walk():
|
for part in msg.walk():
|
||||||
content_type = part.get_content_type()
|
if part.get_content_type() == 'text/plain':
|
||||||
if content_type == 'text/plain':
|
payload = part.get_payload(decode=True)
|
||||||
try:
|
if payload:
|
||||||
charset = part.get_content_charset() or 'utf-8'
|
charset = part.get_content_charset() or 'utf-8'
|
||||||
body = part.get_payload(decode=True).decode(charset, errors='replace')
|
body = payload.decode(charset, errors='replace')
|
||||||
break
|
return body[:max_len].replace('\n', ' ').strip()
|
||||||
except:
|
else:
|
||||||
pass
|
payload = msg.get_payload(decode=True)
|
||||||
elif content_type == 'text/html':
|
if payload:
|
||||||
try:
|
charset = msg.get_content_charset() or 'utf-8'
|
||||||
charset = part.get_content_charset() or 'utf-8'
|
body = payload.decode(charset, errors='replace')
|
||||||
body = part.get_payload(decode=True).decode(charset, errors='replace')
|
return body[:max_len].replace('\n', ' ').strip()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
return ''
|
||||||
try:
|
|
||||||
charset = msg.get_content_charset() or 'utf-8'
|
def needs_response(subject, body, sender):
|
||||||
body = msg.get_payload(decode=True).decode(charset, errors='replace')
|
"""判断邮件是否需要回应"""
|
||||||
except:
|
text = (subject + ' ' + body).lower()
|
||||||
pass
|
sender_lower = sender.lower()
|
||||||
return body[:500] if body else '(无正文)'
|
|
||||||
|
# 需要回应的关键词
|
||||||
|
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():
|
def fetch_yesterday_emails():
|
||||||
"""获取昨天的邮件"""
|
"""获取昨天的邮件"""
|
||||||
@@ -70,88 +83,122 @@ def fetch_yesterday_emails():
|
|||||||
email_ids = messages[0].split()
|
email_ids = messages[0].split()
|
||||||
|
|
||||||
yesterday = (datetime.now() - timedelta(days=1)).date()
|
yesterday = (datetime.now() - timedelta(days=1)).date()
|
||||||
yesterday_start = yesterday.strftime('%d-%b-%Y')
|
|
||||||
|
|
||||||
all_emails = []
|
all_emails = []
|
||||||
important_emails = []
|
important_emails = []
|
||||||
|
need_response_emails = []
|
||||||
|
|
||||||
for uid in reversed(email_ids[-50:] if len(email_ids) >= 50 else email_ids):
|
# 扫描最近50封
|
||||||
status, msg_data = mail.fetch(uid, '(RFC822.HEADER RFC822.TEXT)')
|
scan_range = email_ids[-50:] if len(email_ids) >= 50 else email_ids
|
||||||
if not msg_data or not msg_data[0]:
|
|
||||||
continue
|
for uid in reversed(scan_range):
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
try:
|
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
|
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()
|
mail.logout()
|
||||||
|
return all_emails, important_emails, need_response_emails, yesterday.strftime('%Y年%m月%d日')
|
||||||
return all_emails, important_emails, yesterday.strftime('%Y年%m月%d日')
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return [], [], str(e)
|
return [], [], [], str(e)
|
||||||
|
|
||||||
def format_summary():
|
def format_summary():
|
||||||
"""格式化邮件摘要"""
|
"""格式化邮件摘要"""
|
||||||
emails, important, date_str = fetch_yesterday_emails()
|
emails, important, need_response, date_str = fetch_yesterday_emails()
|
||||||
|
|
||||||
if isinstance(important, str):
|
if isinstance(important, str):
|
||||||
return f"⚠️ 获取邮件失败: {important}"
|
return f"⚠️ 获取邮件失败: {important}"
|
||||||
|
|
||||||
if not emails:
|
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")
|
if need_response:
|
||||||
for i, mail in enumerate(important, 1):
|
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" {i}. 📧 {mail['subject']}")
|
||||||
lines.append(f" 发件人: {mail['sender']}")
|
lines.append(f" 发件人: {mail['sender']}")
|
||||||
lines.append(f" 时间: {mail['date']}")
|
if mail.get('body'):
|
||||||
if mail['body'] and mail['body'] != '(无正文)':
|
lines.append(f" 摘要: {mail['body'][:150]}...")
|
||||||
lines.append(f" 摘要: {mail['body'][:200]}...")
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
lines.append(f"\n📋 其他邮件({len(emails) - len(important)} 封):")
|
# 重点关注(排除已标记需要回应的)
|
||||||
other = [e for e in emails if e not in important]
|
important_only = [e for e in important if e not in need_response]
|
||||||
for i, mail in enumerate(other[:5], 1):
|
if important_only:
|
||||||
lines.append(f" {i}. 📧 {mail['subject']} - {mail['sender']} ({mail['date']})")
|
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)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user