🔄 卡若AI 同步 2026-02-23 23:22 | 更新:总索引与入口、水桥平台对接、卡土、运营中枢工作台 | 排除 >20MB: 10 个
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -18,6 +18,8 @@ __pycache__/
|
||||
.env.*
|
||||
*.log
|
||||
sync_tokens.env
|
||||
# QQ 邮箱授权码(勿提交)
|
||||
**/QQ邮箱拉取/.qq_mail_env
|
||||
# 飞书妙记(用户 token / Cookie,勿提交)
|
||||
**/智能纪要/脚本/feishu_user_token.txt
|
||||
**/智能纪要/脚本/cookie_minutes.txt
|
||||
|
||||
121
02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md
Normal file
121
02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: MCP 搜索与连接
|
||||
description: 当卡若AI需要连接 MCP 时,使用本技能搜索、发现并安装 MCP 服务器。触发词:MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现。
|
||||
owner: 水桥
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-02-23"
|
||||
---
|
||||
|
||||
# MCP 搜索与连接 Skill
|
||||
|
||||
> 需要啥 MCP,搜一搜、连一连。 —— 水桥
|
||||
|
||||
---
|
||||
|
||||
## 核心能力
|
||||
|
||||
**当卡若AI遇到需要 MCP 能力的场景时,使用本技能完成:**
|
||||
1. 搜索 MCP 服务器(5000+ 可发现)
|
||||
2. 获取安装配置
|
||||
3. 写入 Cursor/Claude/Windsurf 等客户端配置
|
||||
4. 按需使用相应 MCP 工具
|
||||
|
||||
---
|
||||
|
||||
## 推荐工具:MCPfinder
|
||||
|
||||
**MCPfinder** 是「MCP 的 App Store」—— 一次安装,AI 自主发现并安装 MCP 服务器。
|
||||
|
||||
| 能力 | 说明 |
|
||||
|:---|:---|
|
||||
| 搜索 | 5000+ 服务器,多注册表聚合(Official、Glama、Smithery) |
|
||||
| 安装 | 一键生成 Cursor / Claude Desktop / Windsurf 等配置 |
|
||||
| 排名 | 按相关性、热度、多注册表覆盖、更新 recency 排序 |
|
||||
|
||||
### 安装(Cursor)
|
||||
|
||||
在 `~/.cursor/mcp.json` 或项目 `.cursor/mcp.json` 中加入:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mcpfinder": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@mcpfinder/server@beta"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注:首次运行会同步注册表(约 1~2 分钟),之后本地缓存自动刷新。
|
||||
|
||||
### MCPfinder 工具一览
|
||||
|
||||
| 工具 | 用途 | 触发场景 |
|
||||
|:---|:---|:---|
|
||||
| `search_mcp_servers` | 按关键词/用例/技术搜索 | 用户需要你当前没有的能力 |
|
||||
| `get_server_details` | 获取详情(描述、env、来源等) | 评估是否适合安装 |
|
||||
| `get_install_command` | 生成可直接粘贴的配置 | 用户要安装某个 MCP |
|
||||
| `list_categories` | 按分类浏览 | 用户不确定需要啥 |
|
||||
| `browse_category` | 查看某分类下的热门服务器 | 探索某领域(数据库、AI、云等) |
|
||||
|
||||
---
|
||||
|
||||
## 执行流程(卡若AI 使用本技能时)
|
||||
|
||||
```
|
||||
用户需求(需要某种 MCP 能力)
|
||||
↓
|
||||
① 判断:当前 MCP 工具是否已有该能力?
|
||||
├── 有 → 直接调用对应 MCP 工具
|
||||
└── 无 → 进入 ②
|
||||
↓
|
||||
② 调用 MCPfinder.search_mcp_servers(query) 搜索
|
||||
↓
|
||||
③ 若有结果,选 1~3 个候选,调用 get_server_details 评估
|
||||
↓
|
||||
④ 调用 get_install_command 生成 Cursor 配置
|
||||
↓
|
||||
⑤ 写入 ~/.cursor/mcp.json(或项目 mcp.json)
|
||||
↓
|
||||
⑥ 提示用户重启 Cursor 或等待自动检测
|
||||
↓
|
||||
⑦ 配置生效后,使用新 MCP 完成用户需求
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 其他 MCP 发现资源(备用)
|
||||
|
||||
当 MCPfinder 未安装或搜索无果时,可引导用户到:
|
||||
|
||||
| 资源 | 链接 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| GitHub MCP Registry | https://github.com/mcp | 官方精选,VS Code 一键安装 |
|
||||
| MCP Awesome | https://mcp-awesome.com | 1200+ 经核验服务器 |
|
||||
| Find My MCP | https://findmymcp.com | 可搜索目录 |
|
||||
| Cursor Directory | https://cursor.directory/mcp | Cursor 专用目录 |
|
||||
| awesome-mcp-servers | https://github.com/appcypher/awesome-mcp-servers | GitHub 5k+ star 列表 |
|
||||
|
||||
---
|
||||
|
||||
## 与 Skill 的配合
|
||||
|
||||
- **需要功能时**:本技能负责「找 MCP、连 MCP」
|
||||
- **功能落地时**:按 `SKILL_REGISTRY.md` 匹配现有 Skill,或结合「技能工厂」为新 MCP 写封装 Skill
|
||||
- **MCP 安装后**:该 MCP 工具即成为卡若AI 的扩展能力,可直接在对话中调用
|
||||
|
||||
---
|
||||
|
||||
## 触发词
|
||||
|
||||
`MCP`、`找MCP`、`连接MCP`、`MCP搜索`、`发现MCP`、`添加MCP`、`需要MCP`、`MCP安装`、`MCP发现`、`查MCP`、`装MCP`
|
||||
|
||||
---
|
||||
|
||||
## 引用
|
||||
|
||||
- MCPfinder 新仓库:https://github.com/lksrz/mcpfinder
|
||||
- MCPfinder 官网:https://mcpfinder.dev · https://findmcp.dev
|
||||
- npm 包:`@mcpfinder/server`(beta)
|
||||
32
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/README.md
Normal file
32
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# QQ 邮箱 IMAP 拉取
|
||||
|
||||
> 水桥 · 平台对接 | 命令行接收 QQ 邮件
|
||||
|
||||
## 配置说明
|
||||
|
||||
- **授权码**:已保存在 `.qq_mail_env`(本地,不提交 git)
|
||||
- **账号**:zhengzhiqun@qq.com
|
||||
- **用途**:脚本启动时自动读取 `.qq_mail_env`,无需再手动设置环境变量
|
||||
|
||||
## 命令行用法
|
||||
|
||||
```bash
|
||||
# 拉取最近 30 天(默认)
|
||||
python qq_mail_fetch.py
|
||||
|
||||
# 拉取最近 7 天
|
||||
python qq_mail_fetch.py --days 7
|
||||
|
||||
# 最多拉取 50 封
|
||||
python qq_mail_fetch.py --limit 50
|
||||
|
||||
# 组合:最近 7 天、最多 20 封
|
||||
python qq_mail_fetch.py --days 7 --limit 20
|
||||
```
|
||||
|
||||
## 登录失败排查
|
||||
|
||||
若出现 `Login fail`,请检查:
|
||||
1. QQ 邮箱 → 设置 → 账户 → POP3/IMAP/SMTP → **已开启 IMAP**
|
||||
2. 授权码为刚生成的可再试一次(有时需等待数分钟生效)
|
||||
3. 更换 QQ 密码会使授权码失效,需重新生成
|
||||
175
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_analyze.py
Normal file
175
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_analyze.py
Normal file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
QQ 邮箱导出数据分析 · 生成整体总结报告
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import Counter
|
||||
from datetime import datetime
|
||||
|
||||
def analyze(json_path: str) -> dict:
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
emails = json.load(f)
|
||||
|
||||
if not emails:
|
||||
return {"total": 0, "error": "无邮件数据"}
|
||||
|
||||
# 基础统计
|
||||
total = len(emails)
|
||||
dates = []
|
||||
senders = []
|
||||
subject_keywords = []
|
||||
|
||||
for e in emails:
|
||||
d = e.get("date", "")[:10]
|
||||
if d and len(d) >= 10:
|
||||
dates.append(d)
|
||||
from_addr = e.get("from", "")
|
||||
if "<" in from_addr:
|
||||
m = re.search(r"[\w.-]+@[\w.-]+", from_addr)
|
||||
sender = m.group(0) if m else from_addr[:50]
|
||||
else:
|
||||
sender = from_addr[:50] if from_addr else "unknown"
|
||||
senders.append(sender)
|
||||
|
||||
subj = e.get("subject", "")
|
||||
# 提取发件域名/服务
|
||||
if "github" in sender.lower():
|
||||
if "Run failed" in subj or "Sync" in subj:
|
||||
subject_keywords.append("GitHub_同步失败")
|
||||
elif "security" in subj.lower():
|
||||
subject_keywords.append("GitHub_安全告警")
|
||||
else:
|
||||
subject_keywords.append("GitHub_其他")
|
||||
elif "synology" in sender.lower():
|
||||
subject_keywords.append("Synology_NAS")
|
||||
elif "vercel" in sender.lower():
|
||||
subject_keywords.append("Vercel_部署")
|
||||
elif "ollama" in sender.lower():
|
||||
subject_keywords.append("Ollama_验证")
|
||||
elif "apple" in sender.lower() or "icloud" in sender.lower():
|
||||
subject_keywords.append("Apple_iCloud")
|
||||
elif "trip.com" in sender.lower():
|
||||
subject_keywords.append("Trip_推广")
|
||||
elif "facebook" in sender.lower():
|
||||
subject_keywords.append("Facebook_通知")
|
||||
elif "adobe" in sender.lower():
|
||||
subject_keywords.append("Adobe_推广")
|
||||
elif "docker" in sender.lower():
|
||||
subject_keywords.append("Docker_推广")
|
||||
elif "airbnb" in sender.lower():
|
||||
subject_keywords.append("Airbnb_通知")
|
||||
elif "cebbank" in sender.lower() or "95595" in sender:
|
||||
subject_keywords.append("光大银行")
|
||||
elif "bosszhipin" in sender.lower():
|
||||
subject_keywords.append("Boss直聘")
|
||||
elif "openrouter" in sender.lower():
|
||||
subject_keywords.append("OpenRouter_AI")
|
||||
else:
|
||||
subject_keywords.append("其他")
|
||||
|
||||
# 按发件人统计
|
||||
sender_counts = Counter(senders)
|
||||
top_senders = sender_counts.most_common(15)
|
||||
|
||||
# 按类型统计
|
||||
type_counts = Counter(subject_keywords)
|
||||
top_types = type_counts.most_common(15)
|
||||
|
||||
# 日期范围
|
||||
dates_ok = [d for d in dates if re.match(r"\d{4}-\d{2}-\d{2}", d)]
|
||||
date_min = min(dates_ok) if dates_ok else ""
|
||||
date_max = max(dates_ok) if dates_ok else ""
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"date_range": {"min": date_min, "max": date_max},
|
||||
"top_senders": top_senders,
|
||||
"top_types": top_types,
|
||||
"by_type": dict(type_counts),
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("json", nargs="?", default="/Users/karuo/Documents/卡若Ai的文件夹/报告/qq_mail_full_export.json")
|
||||
ap.add_argument("-o", "--output", help="输出报告路径")
|
||||
args = ap.parse_args()
|
||||
|
||||
r = analyze(args.json)
|
||||
|
||||
lines = [
|
||||
"# QQ 邮箱整体分析报告",
|
||||
"",
|
||||
"## 一、概览",
|
||||
"",
|
||||
f"- 邮件总数:**{r['total']}** 封",
|
||||
f"- 时间范围:{r['date_range']['min']} ~ {r['date_range']['max']}",
|
||||
"",
|
||||
"## 二、按发件人统计(Top 15)",
|
||||
"",
|
||||
"| 发件人 | 数量 |",
|
||||
"|:---|:---|",
|
||||
]
|
||||
for s, c in r["top_senders"]:
|
||||
lines.append(f"| {s[:60]} | {c} |")
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"## 三、按内容类型统计",
|
||||
"",
|
||||
"| 类型 | 数量 | 占比 |",
|
||||
"|:---|:---|:---|",
|
||||
])
|
||||
for t, c in r["top_types"]:
|
||||
pct = round(c / r["total"] * 100, 1) if r["total"] else 0
|
||||
lines.append(f"| {t} | {c} | {pct}% |")
|
||||
|
||||
# 四、核心发现
|
||||
lines.extend([
|
||||
"",
|
||||
"## 四、核心发现",
|
||||
"",
|
||||
])
|
||||
gh_fail = r["by_type"].get("GitHub_同步失败", 0)
|
||||
syno = r["by_type"].get("Synology_NAS", 0)
|
||||
vercel = r["by_type"].get("Vercel_部署", 0)
|
||||
boss = r["by_type"].get("Boss直聘", 0)
|
||||
if gh_fail:
|
||||
lines.append(f"- **GitHub 同步告警占 {round(gh_fail/r['total']*100,1)}%**:cunkebao_doc 的 Coding 同步长期失败,建议修复或停用工作流")
|
||||
if syno:
|
||||
lines.append(f"- **Synology NAS 通知 {syno} 封**:容器异常、连接断连频繁,需排查 nas-frpc、mongodb 等")
|
||||
if vercel:
|
||||
lines.append(f"- **Vercel 部署失败 {vercel} 封**:部署权限或集成问题待查")
|
||||
if boss:
|
||||
lines.append(f"- **Boss直聘 {boss} 封**:招聘/求职相关")
|
||||
lines.extend([
|
||||
"",
|
||||
"## 五、建议行动",
|
||||
"",
|
||||
"1. 优先处理 cunkebao_doc 同步失败,减少告警噪音",
|
||||
"2. 排查 Synology 容器稳定性与 lkdie 连接",
|
||||
"3. 检查 Vercel 与 GitHub 集成",
|
||||
"4. 按需归档或过滤推广类邮件(Trip、Facebook、Adobe 等)",
|
||||
"",
|
||||
"## 六、数据说明",
|
||||
"",
|
||||
"- 数据来源:IMAP 收件箱(INBOX)全量拉取",
|
||||
"- 网页版「我的文件夹」等需在 QQ 邮箱设置中勾选「收取我的文件夹」后,方能在 IMAP 中访问",
|
||||
"- 导出文件:`qq_mail_full_export.json`",
|
||||
"",
|
||||
])
|
||||
|
||||
text = "\n".join(lines)
|
||||
|
||||
if args.output:
|
||||
Path(args.output).write_text(text, encoding="utf-8")
|
||||
print(f"已写入 {args.output}")
|
||||
else:
|
||||
print(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
188
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch.py
Normal file
188
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
QQ 邮箱 IMAP 拉取脚本 · 水桥(平台对接)
|
||||
用途:拉取收件箱全部或指定时间范围的邮件,输出为可读格式
|
||||
|
||||
前置:QQ 邮箱设置 → 账户 → POP3/IMAP/SMTP → 开启 IMAP → 生成授权码
|
||||
用法:
|
||||
python qq_mail_fetch.py # 拉取最近 30 天
|
||||
python qq_mail_fetch.py --days 7 # 拉取最近 7 天
|
||||
python qq_mail_fetch.py --all # 拉取全部历史邮件(无日期限制)
|
||||
python qq_mail_fetch.py --all --output out.json # 导出到 JSON 便于分析
|
||||
"""
|
||||
import imaplib
|
||||
import email
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from email.utils import parsedate_to_datetime
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# 加载本地 .qq_mail_env(授权码保存于此,调用时直接使用)
|
||||
def _load_env():
|
||||
env_file = Path(__file__).resolve().parent / ".qq_mail_env"
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
_load_env()
|
||||
|
||||
# 配置:优先环境变量,次之 .qq_mail_env
|
||||
IMAP_HOST = "imap.qq.com"
|
||||
IMAP_PORT = 993
|
||||
EMAIL = os.environ.get("QQ_MAIL", "zhengzhiqun@qq.com")
|
||||
AUTH_CODE = os.environ.get("QQ_MAIL_AUTH_CODE", "") # 授权码,非 QQ 密码
|
||||
|
||||
|
||||
def list_folders() -> list[str]:
|
||||
"""列出所有 IMAP 文件夹"""
|
||||
if not AUTH_CODE:
|
||||
return []
|
||||
server = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
|
||||
server.login(EMAIL, AUTH_CODE)
|
||||
typ, data = server.list()
|
||||
server.logout()
|
||||
if typ != "OK":
|
||||
return []
|
||||
folders = []
|
||||
for line in data:
|
||||
if line:
|
||||
parts = line.decode().split('"')
|
||||
if len(parts) >= 2:
|
||||
folders.append(parts[-2])
|
||||
return folders
|
||||
|
||||
|
||||
def fetch_emails(days: int = 30, limit: int = 0, all_mail: bool = False, progress: bool = True, folder: str = "INBOX") -> list[dict]:
|
||||
"""拉取收件箱邮件,返回 [{date, from, subject, preview}, ...]"""
|
||||
if not AUTH_CODE:
|
||||
print("请设置环境变量 QQ_MAIL_AUTH_CODE(QQ 邮箱授权码)")
|
||||
print("或在脚本内填写 AUTH_CODE 变量")
|
||||
return []
|
||||
|
||||
server = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT)
|
||||
server.login(EMAIL, AUTH_CODE)
|
||||
# 含空格的文件夹名需加双引号(如 "Sent Messages")
|
||||
mb = f'"{folder}"' if " " in folder else folder
|
||||
try:
|
||||
server.select(mb, readonly=True)
|
||||
except (imaplib.IMAP4.error, imaplib.IMAP4.readonly):
|
||||
server.select(mb, readonly=False)
|
||||
|
||||
if all_mail:
|
||||
typ, data = server.search(None, "ALL")
|
||||
else:
|
||||
since = (datetime.now() - timedelta(days=days)).strftime("%d-%b-%Y")
|
||||
typ, data = server.search(None, f"(SINCE {since})")
|
||||
|
||||
if typ != "OK":
|
||||
server.close()
|
||||
server.logout()
|
||||
return []
|
||||
|
||||
ids = data[0].split()
|
||||
ids = list(reversed(ids)) # 新的在前
|
||||
total = len(ids)
|
||||
if limit and total > limit:
|
||||
ids = ids[:limit]
|
||||
total_fetch = len(ids)
|
||||
|
||||
if progress:
|
||||
print(f"共 {total} 封邮件,将拉取 {total_fetch} 封...", file=sys.stderr)
|
||||
|
||||
results = []
|
||||
for i, num in enumerate(ids):
|
||||
typ, msg_data = server.fetch(num, "(RFC822)")
|
||||
if typ != "OK":
|
||||
continue
|
||||
raw = msg_data[0][1]
|
||||
msg = email.message_from_bytes(raw)
|
||||
subject = msg.get("Subject", "")
|
||||
if isinstance(subject, bytes):
|
||||
from email.header import decode_header
|
||||
parts = decode_header(subject)
|
||||
subject = "".join(
|
||||
p.decode(c or "utf-8", errors="replace") if isinstance(p, bytes) else p
|
||||
for p, c in parts
|
||||
)
|
||||
from_addr = msg.get("From", "")
|
||||
date_str = msg.get("Date", "")
|
||||
try:
|
||||
dt = parsedate_to_datetime(date_str)
|
||||
date_display = dt.strftime("%Y-%m-%d %H:%M")
|
||||
except Exception:
|
||||
date_display = date_str[:20] if date_str else ""
|
||||
preview = ""
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
try:
|
||||
preview = part.get_payload(decode=True).decode("utf-8", errors="replace")[:200]
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
else:
|
||||
try:
|
||||
preview = msg.get_payload(decode=True).decode("utf-8", errors="replace")[:200]
|
||||
except Exception:
|
||||
preview = "(无法解析正文)"
|
||||
results.append({
|
||||
"date": date_display,
|
||||
"from": from_addr[:80],
|
||||
"subject": subject[:120],
|
||||
"preview": preview.replace("\n", " ").strip()[:200],
|
||||
})
|
||||
if progress and (i + 1) % 500 == 0:
|
||||
print(f" 已拉取 {i + 1}/{total_fetch} ...", file=sys.stderr)
|
||||
|
||||
server.close()
|
||||
server.logout()
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="QQ 邮箱 IMAP 拉取")
|
||||
ap.add_argument("--days", type=int, default=30, help="拉取最近 N 天(与 --all 互斥)")
|
||||
ap.add_argument("--all", dest="all_mail", action="store_true", help="拉取全部历史邮件")
|
||||
ap.add_argument("--folder", type=str, default="INBOX", help="指定文件夹,如 INBOX、我的文件夹 等;先 --list-folders 查看")
|
||||
ap.add_argument("--list-folders", action="store_true", help="列出所有 IMAP 文件夹")
|
||||
ap.add_argument("--limit", type=int, default=0, help="最多拉取 N 封,0 表示不限制")
|
||||
ap.add_argument("--output", "-o", type=str, default="", help="导出到 JSON 文件")
|
||||
ap.add_argument("--quiet", "-q", action="store_true", help="不显示进度")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.list_folders:
|
||||
for f in list_folders():
|
||||
print(f)
|
||||
return
|
||||
|
||||
days = 365 * 20 if args.all_mail else args.days
|
||||
emails = fetch_emails(
|
||||
days=days,
|
||||
limit=args.limit,
|
||||
all_mail=args.all_mail,
|
||||
progress=not args.quiet,
|
||||
folder=args.folder,
|
||||
)
|
||||
|
||||
if args.output:
|
||||
out_path = Path(args.output)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(emails, f, ensure_ascii=False, indent=None)
|
||||
print(f"已导出 {len(emails)} 封到 {out_path}", file=sys.stderr)
|
||||
else:
|
||||
for i, e in enumerate(emails, 1):
|
||||
print(f"[{i}] {e['date']} | {e['from']}")
|
||||
print(f" 主题: {e['subject']}")
|
||||
print(f" 摘要: {e['preview']}")
|
||||
print("-" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
60
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch_all.py
Normal file
60
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/qq_mail_fetch_all.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
QQ 邮箱多文件夹批量拉取 · 收件箱 + 垃圾箱 + 已发送 + 我的文件夹(若已开启)
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from qq_mail_fetch import fetch_emails, AUTH_CODE
|
||||
|
||||
OUT_DIR = Path("/Users/karuo/Documents/卡若Ai的文件夹/报告")
|
||||
|
||||
FOLDERS = [
|
||||
("INBOX", "收件箱"),
|
||||
("Junk", "垃圾箱"),
|
||||
("Sent Messages", "已发送"),
|
||||
("Drafts", "草稿箱"),
|
||||
("Deleted Messages", "已删除"),
|
||||
]
|
||||
|
||||
# 我的文件夹(需在 QQ 设置中勾选「收取我的文件夹」后才能在 IMAP 中看到子目录)
|
||||
MY_FOLDERS = ["&UXZO1mWHTvZZOQ-"] # 父级,可扩展子目录
|
||||
|
||||
|
||||
def main():
|
||||
if not AUTH_CODE:
|
||||
print("请配置 .qq_mail_env 中的授权码")
|
||||
return 1
|
||||
|
||||
results = {}
|
||||
for folder, label in FOLDERS:
|
||||
try:
|
||||
emails = fetch_emails(days=365 * 20, limit=0, all_mail=True, progress=True, folder=folder)
|
||||
results[label] = {"folder": folder, "count": len(emails), "emails": emails}
|
||||
out = OUT_DIR / f"qq_{folder.replace(' ', '_').lower()}_export.json"
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
json.dump(emails, f, ensure_ascii=False, indent=None)
|
||||
print(f" -> {out.name}: {len(emails)} 封")
|
||||
except Exception as e:
|
||||
print(f"{label} ({folder}): 失败 - {e}")
|
||||
results[label] = {"folder": folder, "count": 0, "error": str(e)}
|
||||
|
||||
for folder in MY_FOLDERS:
|
||||
try:
|
||||
emails = fetch_emails(days=365 * 20, limit=0, all_mail=True, progress=True, folder=folder)
|
||||
results["我的文件夹"] = {"folder": folder, "count": len(emails), "emails": emails}
|
||||
out = OUT_DIR / "qq_myfolders_export.json"
|
||||
with open(out, "w", encoding="utf-8") as f:
|
||||
json.dump(emails, f, ensure_ascii=False, indent=None)
|
||||
print(f" 我的文件夹 -> {out.name}: {len(emails)} 封")
|
||||
except Exception as e:
|
||||
print(f"我的文件夹: 失败 - {e}")
|
||||
|
||||
# 合并统计
|
||||
total = sum(r.get("count", 0) for r in results.values() if isinstance(r, dict))
|
||||
print(f"\n合计拉取: {total} 封")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
65
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/获取其他邮件_方法说明.md
Normal file
65
02_卡人(水)/水桥_平台对接/QQ邮箱拉取/获取其他邮件_方法说明.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 获取 QQ 邮箱其他邮件的办法
|
||||
|
||||
## 一、IMAP 已支持的文件夹
|
||||
|
||||
| 网页显示 | IMAP 文件夹名 | 状态 | 当前拉取数 |
|
||||
|:---|:---|:---|:---|
|
||||
| 收件箱 | INBOX | ✅ 已支持 | 643 |
|
||||
| 垃圾箱 | Junk | ✅ 已支持 | 22 |
|
||||
| 已发送 | Sent Messages | ✅ 已支持 | 40 |
|
||||
| 草稿箱 | Drafts | ✅ 已支持 | 0 |
|
||||
| 已删除 | Deleted Messages | ✅ 已支持 | 0 |
|
||||
|
||||
## 二、获取方式
|
||||
|
||||
### 方式 1:命令行逐文件夹拉取
|
||||
|
||||
```bash
|
||||
# 收件箱
|
||||
python3 qq_mail_fetch.py --all -o 报告/qq_inbox_export.json
|
||||
|
||||
# 垃圾箱
|
||||
python3 qq_mail_fetch.py --all --folder Junk -o 报告/qq_junk_export.json
|
||||
|
||||
# 已发送(含空格需用引号)
|
||||
python3 qq_mail_fetch.py --all --folder "Sent Messages" -o 报告/qq_sent_export.json
|
||||
|
||||
# 草稿箱
|
||||
python3 qq_mail_fetch.py --all --folder Drafts -o 报告/qq_drafts_export.json
|
||||
|
||||
# 已删除
|
||||
python3 qq_mail_fetch.py --all --folder "Deleted Messages" -o 报告/qq_deleted_export.json
|
||||
```
|
||||
|
||||
### 方式 2:一键批量拉取
|
||||
|
||||
```bash
|
||||
python3 qq_mail_fetch_all.py
|
||||
```
|
||||
|
||||
会依次拉取上述文件夹并导出到 `报告/` 目录。
|
||||
|
||||
## 三、我的文件夹(14239 封)
|
||||
|
||||
**现状**:IMAP 中 `&UXZO1mWHTvZZOQ-` 为父级目录,本身无邮件;子文件夹未在 LIST 中列出。
|
||||
|
||||
**解决办法**:
|
||||
|
||||
1. **QQ 邮箱网页** → 右上角 **设置** → **账户** → **收取选项**
|
||||
2. 勾选 **「收取我的文件夹」**
|
||||
3. 保存后重新执行拉取
|
||||
4. 执行 `python3 qq_mail_fetch.py --list-folders` 查看是否出现新的子文件夹
|
||||
|
||||
## 四、群邮件(212 封)
|
||||
|
||||
**现状**:QQ 邮箱 IMAP 标准文件夹列表中**未发现**群邮件对应目录。
|
||||
|
||||
**可能原因**:群邮件可能以标签/虚拟文件夹形式存在,需在网页端开启「收取群邮件」等选项后,才在 IMAP 中可见。
|
||||
|
||||
**建议**:在 QQ 邮箱设置中检查「群邮件」「收取选项」相关配置,确认是否支持 IMAP 收取。
|
||||
|
||||
## 五、已修复的技术点
|
||||
|
||||
1. **含空格文件夹名**:`Sent Messages`、`Deleted Messages` 需在脚本中加双引号,已在本仓库脚本中处理。
|
||||
2. **只读文件夹**:`我的文件夹` 父级为只读,已改为使用 `readonly=True`(EXAMINE)选择。
|
||||
3. **批量拉取**:新增 `qq_mail_fetch_all.py` 支持一键拉取多个文件夹。
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"access_token": "u-46dpgqFzddzUbmSNaiAQN2l5mWU5k1orhUaaZBM00xP7",
|
||||
"refresh_token": "ur-4v_0_alr52JanOSvj3DFNul5mgq5k1WpOoaaUNQ00BPj",
|
||||
"access_token": "u-5jTDb7Rkl57WWjJ3pzwPS6l5moW5k1MXV8aaJxQ00ACm",
|
||||
"refresh_token": "ur-5ve40_WDh2CUZE2rESveY6l5mOU5k1MphEaaUBM00xCm",
|
||||
"name": "飞书用户",
|
||||
"auth_time": "2026-02-23T09:58:30.247057"
|
||||
}
|
||||
@@ -43,6 +43,51 @@ ROWS = {
|
||||
# 场次→按日期列填写时的日期(表头为当月日期 1~31)
|
||||
SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23'}
|
||||
|
||||
# 小程序当日运营数据:日期号 → {访问次数, 访客, 交易金额},填表时自动写入对应日期列
|
||||
# 数据来源:微信公众平台 → 小程序 → 统计 → 实时访问/概况
|
||||
# 历史有数据的都填入,批量写入用 write_miniprogram_batch.py
|
||||
MINIPROGRAM_EXTRA = {
|
||||
'20': {'访问次数': 45, '访客': 45, '交易金额': 0}, # 2月20日
|
||||
'21': {'访问次数': 52, '访客': 52, '交易金额': 0}, # 2月21日
|
||||
'23': {'访问次数': 55, '访客': 55, '交易金额': 0}, # 2月23日
|
||||
}
|
||||
|
||||
|
||||
def _find_row_for_keyword(vals, keywords):
|
||||
"""在 vals 中找 A 列包含任一 keyword 的行号(1-based)"""
|
||||
for ri, row in enumerate(vals):
|
||||
a1 = (row[0] if row and len(row) > 0 else '')
|
||||
a1 = str(a1 or '').strip()
|
||||
for kw in keywords:
|
||||
if kw in a1:
|
||||
return ri + 1
|
||||
return None
|
||||
|
||||
|
||||
def _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter):
|
||||
"""若当日有小程序数据,写入 交易金额、访客、小程序访问 到对应行"""
|
||||
extra = MINIPROGRAM_EXTRA.get(date_col)
|
||||
if not extra:
|
||||
return
|
||||
# 行→extra 中的键
|
||||
writes = [
|
||||
(_find_row_for_keyword(vals, ['交易金额']), extra.get('交易金额', 0)),
|
||||
(_find_row_for_keyword(vals, ['访客']), extra.get('访客', extra.get('访问次数'))),
|
||||
(_find_row_for_keyword(vals, ['小程序访问']), extra.get('访问次数')),
|
||||
]
|
||||
written = 0
|
||||
for row_num, val in writes:
|
||||
if row_num is None or val is None:
|
||||
continue
|
||||
rng = f"{sheet_id}!{col_letter}{row_num}"
|
||||
code, body = update_sheet_range(token, spreadsheet_token, rng, [[_to_cell_value(val)]])
|
||||
if code == 401 or body.get('code') in (99991677, 99991663):
|
||||
return
|
||||
if code == 200 and body.get('code') in (0, None):
|
||||
written += 1
|
||||
if written > 0:
|
||||
print(f'✅ 已写入小程序运营数据(2月{date_col}日列):访问次数 {extra.get("访问次数","")}、访客 {extra.get("访客","")}、交易金额 {extra.get("交易金额",0)}')
|
||||
|
||||
|
||||
def load_token():
|
||||
if not os.path.exists(TOKEN_FILE):
|
||||
@@ -223,7 +268,7 @@ def main():
|
||||
values = [_to_cell_value(raw[0])] + [_to_cell_value(raw[i]) for i in range(1, EFFECT_COLS)]
|
||||
spreadsheet_token = WIKI_NODE_OR_SPREADSHEET_TOKEN
|
||||
sheet_id = SHEET_ID
|
||||
range_read = f"{sheet_id}!A1:AG30"
|
||||
range_read = f"{sheet_id}!A1:AG35"
|
||||
vals, read_code, read_body = read_sheet_range(token, spreadsheet_token, range_read)
|
||||
# 401 时刷新 token 并重试读取,确保能定位到日期列
|
||||
if (read_code == 401 or read_body.get('code') in (99991677, 99991663)) and not vals:
|
||||
@@ -302,6 +347,7 @@ def main():
|
||||
ok, msg = _verify_write(spreadsheet_token, sheet_id, col_letter, values, token)
|
||||
if ok:
|
||||
print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter}3:{col_letter}{2+len(values)},共{len(values)}格),校验通过')
|
||||
_write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter)
|
||||
_maybe_send_group(session, raw)
|
||||
return
|
||||
print(f'⚠️ 写入成功但校验未通过:{msg}')
|
||||
@@ -324,6 +370,7 @@ def main():
|
||||
ok, msg = _verify_write(spreadsheet_token, sheet_id, col_letter, values, token)
|
||||
if ok:
|
||||
print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter} 逐格),校验通过')
|
||||
_write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter)
|
||||
_maybe_send_group(session, raw)
|
||||
return
|
||||
print(f'⚠️ 逐格写入成功但校验未通过:{msg}')
|
||||
|
||||
43
02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_batch.py
Normal file
43
02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_batch.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
批量写入小程序运营数据到飞书运营报表(小程序访问行)。
|
||||
- 读取 soul_party_to_feishu_sheet 中的 MINIPROGRAM_EXTRA
|
||||
- 逐个日期调用 write_miniprogram_to_sheet,填入「小程序访问」「访客」「交易金额」
|
||||
用法:python3 write_miniprogram_batch.py
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
FEISHU_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, FEISHU_SCRIPT_DIR)
|
||||
from soul_party_to_feishu_sheet import MINIPROGRAM_EXTRA
|
||||
|
||||
|
||||
def main():
|
||||
if not MINIPROGRAM_EXTRA:
|
||||
print('MINIPROGRAM_EXTRA 为空,无可写入数据。')
|
||||
sys.exit(0)
|
||||
|
||||
script = os.path.join(FEISHU_SCRIPT_DIR, 'write_miniprogram_to_sheet.py')
|
||||
total = 0
|
||||
for date_col, extra in MINIPROGRAM_EXTRA.items():
|
||||
access = extra.get('访问次数')
|
||||
visitor = extra.get('访客', access)
|
||||
txn = extra.get('交易金额', 0)
|
||||
if access is None:
|
||||
continue
|
||||
cmd = [sys.executable, script, date_col, str(access), str(visitor), str(txn)]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, cwd=FEISHU_SCRIPT_DIR)
|
||||
if r.returncode == 0:
|
||||
total += 1
|
||||
print(f'✅ 2月{date_col}日:访问 {access}、访客 {visitor}、交易 {txn}')
|
||||
else:
|
||||
print(f'⚠️ 2月{date_col}日 写入失败:{r.stderr or r.stdout}')
|
||||
|
||||
print(f'✅ 批量写入完成,共 {total} 天')
|
||||
sys.exit(0 if total > 0 else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
185
02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_to_sheet.py
Normal file
185
02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_miniprogram_to_sheet.py
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
小程序运营数据写入飞书运营报表(当日交易金额、访客数、小程序访问次数)。
|
||||
- 自动查找表格中「交易金额」「访客」「小程序访问」对应行
|
||||
- 写入指定日期列
|
||||
用法:python3 write_miniprogram_to_sheet.py <日期列号> <访问次数> [访客数] [交易金额]
|
||||
例:python3 write_miniprogram_to_sheet.py 23 55 55 0
|
||||
例:python3 write_miniprogram_to_sheet.py 23 55 (访客=访问次数,交易金额=0)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
|
||||
FEISHU_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
TOKEN_FILE = os.path.join(FEISHU_SCRIPT_DIR, '.feishu_tokens.json')
|
||||
SPREADSHEET_TOKEN = os.environ.get('FEISHU_SPREADSHEET_TOKEN', 'wikcnIgAGSNHo0t36idHJ668Gfd')
|
||||
SHEET_ID = os.environ.get('FEISHU_SHEET_ID', '7A3Cy9')
|
||||
|
||||
# 指标名 → 匹配关键词(A列包含即认为找到)
|
||||
ROW_KEYWORDS = {
|
||||
'交易金额': ['交易金额'],
|
||||
'访客': ['访客'],
|
||||
'小程序访问': ['小程序访问'],
|
||||
}
|
||||
|
||||
|
||||
def load_token():
|
||||
if not os.path.exists(TOKEN_FILE):
|
||||
return None
|
||||
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f).get('access_token')
|
||||
|
||||
|
||||
def refresh_token():
|
||||
if not os.path.exists(TOKEN_FILE):
|
||||
return None
|
||||
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
r = requests.post(
|
||||
'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal',
|
||||
json={'app_id': 'cli_a48818290ef8100d', 'app_secret': 'dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4'},
|
||||
timeout=10,
|
||||
)
|
||||
app_token = (r.json() or {}).get('app_access_token')
|
||||
if not app_token:
|
||||
return None
|
||||
r2 = requests.post(
|
||||
'https://open.feishu.cn/open-apis/authen/v1/oidc/refresh_access_token',
|
||||
headers={'Authorization': f'Bearer {app_token}', 'Content-Type': 'application/json'},
|
||||
json={'grant_type': 'refresh_token', 'refresh_token': data.get('refresh_token')},
|
||||
timeout=10,
|
||||
)
|
||||
out = r2.json()
|
||||
if out.get('code') == 0 and out.get('data', {}).get('access_token'):
|
||||
data['access_token'] = out['data']['access_token']
|
||||
data['refresh_token'] = out['data'].get('refresh_token', data.get('refresh_token'))
|
||||
with open(TOKEN_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
return data['access_token']
|
||||
return None
|
||||
|
||||
|
||||
def read_range(token, range_str):
|
||||
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values/{quote(range_str, safe="")}'
|
||||
r = requests.get(url, headers={'Authorization': f'Bearer {token}'}, timeout=15)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
body = r.json()
|
||||
if body.get('code') != 0:
|
||||
return None
|
||||
return (body.get('data') or {}).get('valueRange', {}).get('values') or []
|
||||
|
||||
|
||||
def update_cell(token, range_str, value, value_input_option='USER_ENTERED'):
|
||||
if range_str.count('!') == 1 and ':' not in range_str.split('!')[1]:
|
||||
range_str = range_str + ':' + range_str.split('!')[1]
|
||||
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{SPREADSHEET_TOKEN}/values'
|
||||
params = {'valueInputOption': value_input_option}
|
||||
v = value if value is not None and value != '' else ''
|
||||
payload = {'valueRange': {'range': range_str, 'values': [[v]]}}
|
||||
r = requests.put(
|
||||
url, params=params,
|
||||
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
|
||||
json=payload, timeout=15,
|
||||
)
|
||||
try:
|
||||
return r.status_code, r.json()
|
||||
except Exception:
|
||||
return r.status_code, {}
|
||||
|
||||
|
||||
def _col_letter(n):
|
||||
s = ''
|
||||
while True:
|
||||
s = chr(65 + n % 26) + s
|
||||
n = n // 26
|
||||
if n <= 0:
|
||||
break
|
||||
return s
|
||||
|
||||
|
||||
def find_row_for_keyword(vals, keyword_list):
|
||||
"""在 vals 中找 A 列包含任一 keyword 的行号(1-based)"""
|
||||
for ri, row in enumerate(vals):
|
||||
a1 = (row[0] if row and len(row) > 0 else '')
|
||||
a1 = str(a1 or '').strip()
|
||||
for kw in keyword_list:
|
||||
if kw in a1:
|
||||
return ri + 1
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print('用法:python3 write_miniprogram_to_sheet.py <日期列号> <访问次数> [访客数] [交易金额]')
|
||||
print(' 例:python3 write_miniprogram_to_sheet.py 23 55 55 0')
|
||||
sys.exit(1)
|
||||
date_col_str = sys.argv[1].strip()
|
||||
access_count = sys.argv[2].strip()
|
||||
visitor_count = sys.argv[3].strip() if len(sys.argv) > 3 else access_count
|
||||
transaction = sys.argv[4].strip() if len(sys.argv) > 4 else '0'
|
||||
|
||||
token = load_token() or refresh_token()
|
||||
if not token:
|
||||
print('❌ 无法获取飞书 Token')
|
||||
sys.exit(1)
|
||||
|
||||
vals = read_range(token, f'{SHEET_ID}!A1:AG35')
|
||||
if not vals or len(vals) < 2:
|
||||
print('❌ 读取表格失败')
|
||||
sys.exit(1)
|
||||
|
||||
header = vals[0]
|
||||
col_idx = None
|
||||
for idx, cell in enumerate(header):
|
||||
if str(cell).strip() == date_col_str:
|
||||
col_idx = idx
|
||||
break
|
||||
if col_idx is None:
|
||||
print(f'❌ 未找到日期列 {date_col_str}')
|
||||
sys.exit(1)
|
||||
|
||||
# 查找三行:交易金额、访客、小程序访问
|
||||
row_txn = find_row_for_keyword(vals, ROW_KEYWORDS['交易金额'])
|
||||
row_visitor = find_row_for_keyword(vals, ROW_KEYWORDS['访客'])
|
||||
row_access = find_row_for_keyword(vals, ROW_KEYWORDS['小程序访问'])
|
||||
|
||||
# 若未找到「访客」单独行,可能和「小程序访问」共用,用访问次数
|
||||
if not row_visitor and row_access:
|
||||
row_visitor = row_access # 同一行填访客与访问
|
||||
|
||||
col_letter = _col_letter(col_idx)
|
||||
written = 0
|
||||
|
||||
def _write_one(row_num, val, name):
|
||||
nonlocal written, token
|
||||
if row_num is None:
|
||||
return
|
||||
rng = f'{SHEET_ID}!{col_letter}{row_num}'
|
||||
code, body = update_cell(token, rng, val)
|
||||
if code == 401 or body.get('code') in (99991677, 99991663):
|
||||
t = refresh_token()
|
||||
if t:
|
||||
token = t
|
||||
code, body = update_cell(token, rng, val)
|
||||
if code == 200 and body.get('code') in (0, None):
|
||||
print(f'✅ 已写入 {name} → 2月{date_col_str}日列:{val}')
|
||||
written += 1
|
||||
else:
|
||||
print(f'⚠️ 写入 {name} 失败:{code} {body}')
|
||||
|
||||
_write_one(row_txn, transaction, '交易金额')
|
||||
_write_one(row_visitor, visitor_count, '访客')
|
||||
_write_one(row_access, access_count, '小程序访问')
|
||||
|
||||
if written == 0:
|
||||
print('❌ 未找到可写入的行,请确认表格 A 列有「交易金额」「访客」「小程序访问」等指标')
|
||||
sys.exit(1)
|
||||
print(f'✅ 小程序运营数据已填入 2月{date_col_str}日列,共 {written} 项')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -53,7 +53,7 @@ python3 write_party_minutes_from_txt.py "/path/to/soul 派对 106场 20260221.tx
|
||||
| 表格链接 | https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=7A3Cy9 |
|
||||
| spreadsheet_token | `wikcnIgAGSNHo0t36idHJ668Gfd` |
|
||||
| sheet_id | `7A3Cy9` |
|
||||
| 表格结构 | A 列=指标名,第 1 行=日期(1、2…21…),第 3~12 行=效果数据,第 28 行=今日总结 |
|
||||
| 表格结构 | A 列=指标名,第 1 行=日期(1、2…21…),第 3~12 行=效果数据,第 15 行=小程序访问,第 28 行=今日总结 |
|
||||
|
||||
### 1.3 飞书群 Webhook
|
||||
|
||||
@@ -89,6 +89,29 @@ python3 write_party_minutes_from_txt.py "/path/to/soul 派对 106场 20260221.tx
|
||||
|:---|:---|:---|
|
||||
| `feishu_write_minutes_to_sheet.py` | 会议纪要/派对总结**图片**上传到对应单元格 | `python3 feishu_write_minutes_to_sheet.py [内部图] [派对图]` |
|
||||
| `feishu_sheet_monthly_stats.py` | 月度运营数据统计 | `python3 feishu_sheet_monthly_stats.py 2` 或 `all` |
|
||||
| `write_miniprogram_to_sheet.py` | **单独**写入小程序三核心数据(访问次数、访客、交易金额) | `python3 write_miniprogram_to_sheet.py 23 55 55 0` |
|
||||
|
||||
### 2.3 小程序运营数据(自动写入)
|
||||
|
||||
每日填表时,若在 `soul_party_to_feishu_sheet.py` 中配置了 `MINIPROGRAM_EXTRA`,会**自动**把当日小程序三核心数据写入对应日期列:
|
||||
|
||||
| 指标 | 数据来源 | 行(A 列关键词) |
|
||||
|:---|:---|:---|
|
||||
| 访问次数 | 微信公众平台 → 小程序 → 统计 → 实时访问 | 小程序访问 |
|
||||
| 访客 | 同上 | 访客 |
|
||||
| 交易金额 | 同上 | 交易金额 |
|
||||
|
||||
**配置方式**(在 `soul_party_to_feishu_sheet.py` 中):
|
||||
|
||||
```python
|
||||
MINIPROGRAM_EXTRA = {
|
||||
'23': {'访问次数': 55, '访客': 55, '交易金额': 0}, # 2月23日
|
||||
}
|
||||
```
|
||||
|
||||
- 数据来源:微信公众平台 → 小程序 → 统计,每日手动查看后填入
|
||||
- 仅填表当天:运行 `python3 soul_party_to_feishu_sheet.py 107` 时,若 107 场对应 2月23日,且 `MINIPROGRAM_EXTRA` 有 `'23'`,则自动写入
|
||||
- 单独写入:`python3 write_miniprogram_to_sheet.py 23 55 55 0`(日期列号 访问次数 访客 交易金额)
|
||||
|
||||
---
|
||||
|
||||
@@ -146,6 +169,7 @@ python3 write_party_minutes_from_txt.py "/Users/karuo/Downloads/soul 派对 107
|
||||
成功输出示例:
|
||||
```
|
||||
✅ 已写入飞书表格:107场 效果数据(竖列 W3:W12,共10格),校验通过
|
||||
✅ 已写入小程序运营数据(2月23日列):访问次数 55、访客 55、交易金额 0
|
||||
✅ 已同步推送到飞书群(竖状格式)
|
||||
✅ 已写入派对智能纪要到「今日总结」→ 2月22日列,校验通过
|
||||
```
|
||||
@@ -282,6 +306,8 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4
|
||||
第11行: 增加关注 | | | | | xx | xx | |
|
||||
第12行: 最高在线 | | | | | xx | xx | |
|
||||
...
|
||||
第15行: 小程序访问| | | | | xx | xx | | ← 访问次数、访客、交易金额写对应行
|
||||
...
|
||||
第28行: 今日总结 | | | | | xx | xx | | ← 智能纪要写这里
|
||||
```
|
||||
|
||||
@@ -289,7 +315,7 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4
|
||||
|
||||
## 十、新增场次模板
|
||||
|
||||
每次新增场次,只需改 `soul_party_to_feishu_sheet.py` 两处:
|
||||
每次新增场次,只需改 `soul_party_to_feishu_sheet.py` 两到三处:
|
||||
|
||||
```python
|
||||
# 1. ROWS 字典加一行
|
||||
@@ -299,6 +325,9 @@ export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4
|
||||
SESSION_DATE_COLUMN = {..., 'NEW': '日期号'}
|
||||
|
||||
# 3. _maybe_send_group 内 date_label 和 src_date 可选加映射(可选,不加则不发群)
|
||||
|
||||
# 4. 若当日有小程序数据,在 MINIPROGRAM_EXTRA 中加:
|
||||
# MINIPROGRAM_EXTRA = {..., '23': {'访问次数': 55, '访客': 55, '交易金额': 0}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
43
05_卡土(土)/土簿_财务管理/公司财务/SKILL.md
Normal file
43
05_卡土(土)/土簿_财务管理/公司财务/SKILL.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: 公司财务
|
||||
description: 芸归喜、卡卡猫等公司主体的收支、月度报表、工资表。仅公司开支,不含家庭。触发词:公司财务、芸归喜、卡卡猫、公司报表、公司开支。
|
||||
group: 土
|
||||
triggers: 公司财务、芸归喜、卡卡猫、公司报表、公司开支、中信0405、网商9532
|
||||
owner: 土簿
|
||||
version: "1.0"
|
||||
updated: "2026-02-08"
|
||||
parent: 财务管理
|
||||
---
|
||||
|
||||
# 公司财务
|
||||
|
||||
卡若名下公司主体的财务汇总、月度报表、工资表。**仅含公司收支,不含家庭财务**。
|
||||
|
||||
## 关联路径
|
||||
|
||||
| 项目 | 路径 |
|
||||
|:---|:---|
|
||||
| 月度报表 | `4、财务/报告/`(如 2026年1月财务报表_完整表格.md、2026年2月财务报表_完整表格.md) |
|
||||
| 工资表 | `4、财务/报告/`(如 2026年2月工资表.md) |
|
||||
| 重点机构交易明细 | `4、财务/财务报表/重点机构与主体_交易明细.csv` |
|
||||
| 数据解析脚本 | `4、财务/scripts/parse_feb_2026_data.py` |
|
||||
|
||||
## 核心能力
|
||||
|
||||
1. **报表**:生成/更新公司月度财务报表(芸归喜、卡卡猫、云消费、飞书工资)
|
||||
2. **数据来源**:中信 0405(**卡卡猫**)、网商 9532(**芸归喜**)、腾讯云、飞书钱袋子
|
||||
3. **执行**:运行 `parse_feb_2026_data.py [YYYY-MM]` 解析指定月份后更新报表
|
||||
|
||||
## 报表范围(仅公司)
|
||||
|
||||
- 云消费(腾讯云)
|
||||
- 卡卡猫(中信 0405)收支
|
||||
- 芸归喜(网商 9532)收支
|
||||
- 飞书·工资/报销
|
||||
- **不含**:家庭、鲨鱼记账
|
||||
|
||||
## 生成月度报表步骤
|
||||
|
||||
1. 执行 `python3 4、财务/scripts/parse_feb_2026_data.py YYYY-MM` 获取数据
|
||||
2. 更新 `4、财务/报告/YYYY年MM月财务报表_完整表格.md`
|
||||
3. 工资表从飞书钱袋子「2026年卡若公司工资表」同步
|
||||
39
05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md
Normal file
39
05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: 家庭财务
|
||||
description: 家庭收支、鲨鱼记账、个人/家庭账单。与公司财务分离。触发词:家庭财务、鲨鱼、家庭收支、家庭报表。
|
||||
group: 土
|
||||
triggers: 家庭财务、鲨鱼记账、家庭收支、家庭报表
|
||||
owner: 土簿
|
||||
version: "1.0"
|
||||
updated: "2026-02-08"
|
||||
parent: 财务管理
|
||||
---
|
||||
|
||||
# 家庭财务
|
||||
|
||||
卡若家庭收支、鲨鱼记账、个人账单。**与公司财务分离,仅家庭维度**。
|
||||
|
||||
## 关联路径
|
||||
|
||||
| 项目 | 路径 |
|
||||
|:---|:---|
|
||||
| 鲨鱼记账明细 | `4、财务/家庭财务/` 或 鲨鱼导出 |
|
||||
| 家庭报表 | 需从鲨鱼记账按月导出后填入 |
|
||||
|
||||
## 核心能力
|
||||
|
||||
1. **家庭收支**:鲨鱼记账月度汇总
|
||||
2. **数据来源**:鲨鱼记账导出(收入、支出、缺口/结余)
|
||||
3. **与公司分离**:公司报表不含家庭;家庭报表单独维护
|
||||
|
||||
## 报表范围(仅家庭)
|
||||
|
||||
- 收入
|
||||
- 支出
|
||||
- 缺口/结余
|
||||
|
||||
## 生成家庭月度报表步骤
|
||||
|
||||
1. 从鲨鱼记账导出指定月份(如 2026-02)
|
||||
2. 汇总收入、支出、结余
|
||||
3. 写入家庭财务专用报表或 `4、财务/报告/` 下家庭分表
|
||||
@@ -1,7 +1,7 @@
|
||||
# 卡若AI 技能注册表(Skill Registry)
|
||||
|
||||
> **一张表查所有技能**。任何 AI 拿到这张表,就能按关键词找到对应技能的 SKILL.md 路径并执行。
|
||||
> 57 技能 | 14 成员 | 5 负责人
|
||||
> 58 技能 | 14 成员 | 5 负责人
|
||||
> 版本:5.0 | 更新:2026-02-16
|
||||
|
||||
---
|
||||
@@ -56,6 +56,7 @@
|
||||
| W09 | 小程序管理 | 水桥 | 小程序、微信小程序 | `02_卡人(水)/水桥_平台对接/小程序管理/SKILL.md` | 微信小程序发布与维护 |
|
||||
| W10 | Soul文章上传 | 水桥 | **Soul文章上传、Soul派对文章、第9章上传、soul上传** | `02_卡人(水)/水桥_平台对接/Soul文章上传/SKILL.md` | 《一场soul的创业实验》第9章文章写好后上传到小程序,id 已存在则更新不重复 |
|
||||
| W11 | Soul派对运营报表 | 水桥 | **运营报表、派对填表、派对截图填表发群、派对纪要、智能纪要、106场、107场、本月运营数据** | `02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md` | 派对截图+TXT→飞书运营报表→智能纪要→飞书群推送,含Token自刷新与写入校验 |
|
||||
| W12 | MCP 搜索与连接 | 水桥 | **MCP、找MCP、连接MCP、MCP搜索、发现MCP、添加MCP、需要MCP、MCP安装、MCP发现、查MCP、装MCP** | `02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md` | 搜索 5000+ MCP 服务器→生成安装配置→写入 Cursor/Claude 等 |
|
||||
|
||||
## 木组 · 卡木(产品内容创造)
|
||||
|
||||
@@ -93,6 +94,8 @@
|
||||
| E03 | 流量自动化 | 土渠 | 刷流量、SEO | `05_卡土(土)/土渠_流量招商/流量自动化/SKILL.md` | SEO、流量投放自动化 |
|
||||
| E04 | 手机流量自动操作 | 土渠 | 手机自动化、AutoGLM | `05_卡土(土)/土渠_流量招商/手机与网页流量自动操作/SKILL.md` | 手机 App 自动化操作 |
|
||||
| E05 | 财务管理 | 土簿 | 财务、报表、银行 | `05_卡土(土)/土簿_财务管理/财务管理/SKILL.md` | 收支记录、财务报表 |
|
||||
| E05a | 公司财务 | 土簿 | **公司财务、芸归喜、卡卡猫、公司报表、公司开支** | `05_卡土(土)/土簿_财务管理/公司财务/SKILL.md` | 仅公司收支、月度报表、工资表 |
|
||||
| E05b | 家庭财务 | 土簿 | **家庭财务、鲨鱼记账、家庭收支** | `05_卡土(土)/土簿_财务管理/家庭财务/SKILL.md` | 家庭收支、鲨鱼记账,与公司分离 |
|
||||
| E06 | 商业工具集(财务) | 土簿 | 商业分析 | `05_卡土(土)/土簿_财务管理/商业工具集/SKILL.md` | 财务视角的商业分析 |
|
||||
|
||||
---
|
||||
@@ -117,8 +120,8 @@
|
||||
| 组 | 负责人 | 成员数 | 技能数 |
|
||||
|:--|:---|:--|:--|
|
||||
| 金 | 卡资 | 2 | 20 |
|
||||
| 水 | 卡人 | 3 | 11 |
|
||||
| 水 | 卡人 | 3 | 12 |
|
||||
| 木 | 卡木 | 3 | 6 |
|
||||
| 火 | 卡火 | 4 | 13 |
|
||||
| 土 | 卡土 | 4 | 7 |
|
||||
| **合计** | **5** | **14** | **57** |
|
||||
| **合计** | **5** | **14** | **58** |
|
||||
|
||||
@@ -117,3 +117,4 @@
|
||||
| 2026-02-23 20:58:31 | 🔄 卡若AI 同步 2026-02-23 20:58 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 |
|
||||
| 2026-02-23 21:08:00 | 🔄 卡若AI 同步 2026-02-23 21:07 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 |
|
||||
| 2026-02-23 21:19:17 | 🔄 卡若AI 同步 2026-02-23 21:19 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 |
|
||||
| 2026-02-23 21:31:11 | 🔄 卡若AI 同步 2026-02-23 21:31 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 |
|
||||
|
||||
@@ -120,3 +120,4 @@
|
||||
| 2026-02-23 20:58:31 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 20:58 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-02-23 21:08:00 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:07 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-02-23 21:19:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:19 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-02-23 21:31:11 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-23 21:31 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 10 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
112
运营中枢/工作台/家里NAS_DS213j_整体分析报表.md
Normal file
112
运营中枢/工作台/家里NAS_DS213j_整体分析报表.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 家里 NAS(DS213j)整体分析报表
|
||||
|
||||
> **生成时间**:2026-02
|
||||
> **数据来源**:端口扫描、现有文档、分布式算力管控 Skill、双 NAS 区分文档
|
||||
> **说明**:SSH/DSM API 当前未连接,部分为历史记录与文档汇总
|
||||
|
||||
---
|
||||
|
||||
## 一、设备概览
|
||||
|
||||
| 项目 | 值 |
|
||||
|:-----|:---|
|
||||
| **型号** | Synology DS213j (synology_armada370_213j) |
|
||||
| **主机名** | DiskStation / DiskStation.local |
|
||||
| **内网 IP** | 192.168.110.29 |
|
||||
| **外网域名** | opennas2.quwanzhi.com(frpc 穿透) |
|
||||
| **MAC 地址** | 00:11:32:30:4c:4f(Synology OUI) |
|
||||
| **用途** | 家庭存储、Time Machine 备份、PCDN(网心云) |
|
||||
|
||||
---
|
||||
|
||||
## 二、硬件与系统
|
||||
|
||||
| 项目 | 规格 |
|
||||
|:-----|:-----|
|
||||
| **CPU** | Marvell Armada370(ARMv7) |
|
||||
| **架构** | armv7l(32 位 ARM) |
|
||||
| **内存** | 497MB |
|
||||
| **内核** | Linux 3.2.40 |
|
||||
| **文件系统** | ext4,RAID(md2) |
|
||||
| **Docker** | ❌ 不支持(内核 3.x 无 cgroup) |
|
||||
|
||||
---
|
||||
|
||||
## 三、存储空间
|
||||
|
||||
| 指标 | 值 | 备注 |
|
||||
|:-----|:---|:-----|
|
||||
| **总容量** | 5.4TB | 双盘 RAID |
|
||||
| **已用** | 约 3.4TB~4.6TB | 不同时间点记录 |
|
||||
| **可用** | 约 885GB~2.0TB | 视时段与共享不同 |
|
||||
| **Time Machine 共享** | 2.18TB 可用 | 系统设置中「共享」卷 |
|
||||
| **空间占比** | 约 64%~84% | 建议保持 100GB+ 余量 |
|
||||
|
||||
---
|
||||
|
||||
## 四、网络与端口
|
||||
|
||||
### 4.1 当前开放端口(本次扫描)
|
||||
|
||||
| 端口 | 服务 | 状态 |
|
||||
|:-----|:-----|:-----|
|
||||
| 22 | SSH | 开放 |
|
||||
| 80 | HTTP | 开放 |
|
||||
| 139 | NetBIOS | 开放 |
|
||||
| 443 | HTTPS | 开放 |
|
||||
| 445 | SMB | 开放 |
|
||||
| 5000 | DSM HTTP | 开放 |
|
||||
| 5001 | DSM HTTPS | 开放 |
|
||||
|
||||
### 4.2 内网穿透(frpc → opennas2.quwanzhi.com)
|
||||
|
||||
| 服务 | 外网端口 | 外网访问 |
|
||||
|:-----|:---------|:---------|
|
||||
| SSH | 22202 | `ssh admin@opennas2.quwanzhi.com -p 22202` |
|
||||
| DSM | 5002 / 80 | http://opennas2.quwanzhi.com:5002 或 :80 |
|
||||
| SMB | 4452 | smb://opennas2.quwanzhi.com:4452/共享(需在 frpc 中配置) |
|
||||
| FTP / rsync / MariaDB 等 | 见双 NAS 文档 | — |
|
||||
|
||||
---
|
||||
|
||||
## 五、已部署服务
|
||||
|
||||
| 服务 | 说明 | 状态 |
|
||||
|:-----|:-----|:-----|
|
||||
| **DSM** | 群晖管理界面 | ✅ 运行中 |
|
||||
| **SMB/AFP** | 文件共享、Time Machine | ✅ 运行中 |
|
||||
| **frpc** | 内网穿透(opennas2) | ✅ 配置在 crontab 检活 |
|
||||
| **网心云 wxedge** | PCDN(chroot 部署) | ✅ 运行中,SN: CTWX09Y9Q2ILI4PV |
|
||||
|
||||
---
|
||||
|
||||
## 六、资源与负载(历史记录)
|
||||
|
||||
| 项目 | 状态 |
|
||||
|:-----|:-----|
|
||||
| **CPU 负载** | 曾出现 udevd 异常,已处理,当前约 1.6 |
|
||||
| **内存** | 497MB 总量,约 67MB~170MB 可用(视 PCDN 运行情况) |
|
||||
| **磁盘** | 已清理 core dump,空间从 100% 恢复到约 84% |
|
||||
|
||||
---
|
||||
|
||||
## 七、访问方式
|
||||
|
||||
| 场景 | 地址 |
|
||||
|:-----|:-----|
|
||||
| **内网 DSM** | http://192.168.110.29:5000 |
|
||||
| **外网 DSM** | http://opennas2.quwanzhi.com:5002 |
|
||||
| **Time Machine** | 共享 - DiskStation.local(或 smb://192.168.110.29/共享) |
|
||||
| **外网 1TB 挂载** | smb://opennas2.quwanzhi.com:4452/共享(需先添加 frpc SMB) |
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与建议
|
||||
|
||||
| 类型 | 说明 |
|
||||
|:-----|:-----|
|
||||
| **硬件老化** | DS213j 为较旧型号,ARM32 + 497MB,不适合再增重型服务 |
|
||||
| **无 Docker** | 无法运行容器,扩展能力有限 |
|
||||
| **外网 SSH 不稳定** | 外网 22202 有时不可达,与家庭网络、frpc 有关 |
|
||||
| **空间** | 定期清理,保持 10% 以上可用空间 |
|
||||
| **建议** | 维持当前用途(存储 + Time Machine + PCDN),避免新增高负载服务 |
|
||||
Reference in New Issue
Block a user