🔄 卡若AI 同步 2026-02-26 16:41 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个

This commit is contained in:
2026-02-26 16:41:09 +08:00
parent 9ab3b8e13b
commit 9c8cae6e90
26 changed files with 1493 additions and 227 deletions

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# 微信朋友圈命令行发布脚本macOS
# 依赖:已安装 cliclick (brew install cliclick),微信已登录且窗口可见
# 用法:./post_moments.sh "要发的文案" 或 ./post_moments.sh -c 进入校准模式
set -e
WECHAT_APP="WeChat"
# ========== 坐标配置(首次使用请先运行 -c 校准,把下面 4 组坐标改成你本机的值)==========
# 1. 左侧栏「朋友圈」图标点击位置
: "${WECHAT_MOMENTS_X:=80}"
: "${WECHAT_MOMENTS_Y:=620}"
# 2. 朋友圈页面里的「发朋友圈」相机/按钮位置
: "${WECHAT_POST_BTN_X:=320}"
: "${WECHAT_POST_BTN_Y:=80}"
# 3. 发朋友圈窗口中的「文字输入框」位置
: "${WECHAT_INPUT_X:=400}"
: "${WECHAT_INPUT_Y:=280}"
# 4. 「发表」按钮位置
: "${WECHAT_SUBMIT_X:=500}"
: "${WECHAT_SUBMIT_Y:=420}"
usage() {
echo "用法:"
echo " 发布朋友圈: $0 \"你要发的文案\""
echo " 校准坐标: $0 -c"
echo "说明:首次使用请先运行 -c按提示把鼠标移到对应位置记下坐标并设置环境变量或修改本脚本顶部坐标。"
exit 0
}
# 校准模式:按提示把鼠标移到对应位置后按回车,脚本会输出坐标
calibrate() {
osascript -e "tell application \"$WECHAT_APP\" to activate"
sleep 1
echo "【校准】请把鼠标移到对应位置后按回车,脚本会打印坐标。"
echo ""
read -r -p "1. 鼠标移到左侧「朋友圈」图标上,按回车 → " && echo " WECHAT_MOMENTS_X,Y=$(cliclick p:stdout 2>/dev/null)"
read -r -p "2. 点进朋友圈,鼠标移到「发朋友圈」按钮上,按回车 → " && echo " WECHAT_POST_BTN_X,Y=$(cliclick p:stdout 2>/dev/null)"
read -r -p "3. 点开发朋友圈,鼠标移到文字输入框中心,按回车 → " && echo " WECHAT_INPUT_X,Y=$(cliclick p:stdout 2>/dev/null)"
read -r -p "4. 鼠标移到「发表」按钮上,按回车 → " && echo " WECHAT_SUBMIT_X,Y=$(cliclick p:stdout 2>/dev/null)"
echo ""
echo "请把上面 4 行坐标填到本脚本顶部坐标配置,或运行时 export 环境变量。"
}
if [[ "$1" == "-c" ]] || [[ "$1" == "--calibrate" ]]; then
calibrate
exit 0
fi
if [[ -z "$1" ]]; then
usage
fi
TEXT="$1"
# 检查 cliclick
if ! command -v cliclick &>/dev/null; then
echo "错误:未找到 cliclick。请先执行: brew install cliclick"
exit 1
fi
echo "正在激活微信并发送朋友圈..."
osascript -e "tell application \"$WECHAT_APP\" to activate"
sleep 1.2
# 1. 点左侧「朋友圈」
cliclick c:"$WECHAT_MOMENTS_X","$WECHAT_MOMENTS_Y"
sleep 1.5
# 2. 点「发朋友圈」按钮
cliclick c:"$WECHAT_POST_BTN_X","$WECHAT_POST_BTN_Y"
sleep 1.2
# 3. 把文案放进剪贴板,点输入框后粘贴(避免中文输入法问题)
echo -n "$TEXT" | pbcopy
cliclick c:"$WECHAT_INPUT_X","$WECHAT_INPUT_Y"
sleep 0.5
cliclick kd:cmd t:v ku:cmd
sleep 0.5
# 4. 点「发表」
cliclick c:"$WECHAT_SUBMIT_X","$WECHAT_SUBMIT_Y"
echo "已执行点击流程。请在本机微信窗口确认是否已进入发朋友圈界面并粘贴好文案;若坐标不对请运行 $0 -c 重新校准。"

View File

@@ -0,0 +1,42 @@
# 微信朋友圈命令行发布说明
**macOS 本机**通过命令行触发微信(已打开且登录)发朋友圈,通过 **cliclick** 模拟点击完成操作。
## 依赖
- 已安装 [cliclick](https://github.com/BlueM/cliclick)`brew install cliclick`(你本机已安装)
- 微信已登录,发朋友圈前**窗口保持可见**(可最小化到 Dock脚本会激活
## 用法
```bash
# 进入脚本目录
cd "/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/微信管理/脚本"
# 首次使用:校准坐标(必做一次)
./post_moments.sh -c
# 按提示把鼠标依次移到 4 个位置并回车,脚本会输出 4 组坐标,
# 把坐标填到 post_moments.sh 顶部的变量里(或 export 环境变量)
# 发一条朋友圈(文案用双引号包起来)
./post_moments.sh "今天天气不错~"
```
## 校准说明
坐标和屏幕分辨率、微信窗口位置有关,**每台机器要做一次校准**
1. 运行 `./post_moments.sh -c`
2. 按提示依次把鼠标移到:**朋友圈入口** → **发朋友圈按钮****文字输入框****发表按钮**,每次移好后按回车,记下输出的 `x,y`
3. 把 4 组坐标填到 `post_moments.sh` 里对应的 `WECHAT_*_X``WECHAT_*_Y`,或运行时 `export WECHAT_MOMENTS_X=80 WECHAT_MOMENTS_Y=620 ...` 再执行
## 注意事项
- 微信没有官方「发朋友圈」API本脚本通过**模拟点击**实现,微信界面改版后坐标可能失效,需重新校准。
- 文案通过**剪贴板粘贴**进输入框,发帖前不要手动改剪贴板。
- 若终端没有**辅助功能**权限cliclick 可能无法正常点击,需在 **系统设置 → 隐私与安全性 → 辅助功能** 里勾选终端(或 iTerm
## 脚本位置
`01_卡资/金仓_存储备份/微信管理/脚本/post_moments.sh`

View File

@@ -0,0 +1,67 @@
---
name: Soul创业实验
description: 《一场soul的创业实验》内容与运营统一入口——写作、上传、运营报表子类聚合
triggers: Soul创业实验、写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则、Soul文章上传、Soul派对文章、第9章上传、soul上传、写soul文章、运营报表、派对填表、派对纪要
owner: 水桥
group: 水
version: "1.0"
updated: "2026-02-26"
---
# Soul创业实验 Skill
> 《一场soul的创业实验》相关**写作**第9章单场文章、**上传**(文章到小程序)、**运营报表**(派对数据→飞书)。按需求进入对应子类执行。
**📌 交接备注2026-02-26****一场创业实验**Soul 创业实验场 / Mycontent 项目)**后续操作交由永平**。代码与进度以 GitHub 为准:**https://github.com/fnvtk/Mycontent/tree/yongpxu-soul**。卡若侧仅保留写作/上传/运营报表等 Skill 入口,具体开发与迭代以永平分支为准。
---
## 触发与子类导航
| 子类 | 触发词示例 | 说明 |
|:---|:---|:---|
| **写作** | 写Soul文章、写授文章、Soul派对写文章、第9章写文章、写soul场次、soul文章规则 | 按派对 TXT 写第9章单场文章人称「我」整篇最多一次、联系管理/切片/副业隐晦植入 |
| **上传** | Soul文章上传、Soul派对文章、第9章上传、soul上传、写soul文章、文章写好上传 | 文章写好后上传到小程序id 已存在则更新 |
| **运营报表** | 运营报表、派对填表、派对截图填表发群、派对纪要、106场、107场、本月运营数据 | 派对效果数据→飞书表格→智能纪要→飞书群,见飞书管理下运营报表 Skill |
执行时:根据用户说的关键词判断是**写作 / 上传 / 运营报表**,再进入对应子类(读本目录下 `写作/``上传/` 或引用运营报表)。
---
## 子类一:写作
- **入口**`写作/写作规范.md`(人称、结构、格式、隐晦植入等,唯一规范来源)。
- **何时用**:写第 X 场、写Soul文章、写授文章。写完后输出到书稿第9章目录 `9.xx 第X场主题.md`,再走子类二上传。
---
## 子类二:上传
- **入口**`上传/README.md`路径、content_upload 命令、飞书群)。
- **何时用**文章写好上传小程序、发到小程序。id 已存在→更新,否则创建。
---
## 子类三:运营报表
派对数据→飞书表格→智能纪要→飞书群。入口:`02_卡人/水桥_平台对接/飞书管理/运营报表_SKILL.md`
---
## 目录与唯一内容来源
| 路径 | 内容(不重复) |
|:---|:---|
| `SKILL.md` | 本文件,仅触发与子类导航 |
| `写作/写作规范.md` | 写作唯一规范(人称、结构、格式、隐晦植入) |
| `上传/README.md` | 上传唯一说明(路径、命令、飞书) |
原 Soul文章上传、Soul文章写作 已合并,仅保留一行重定向,无重复正文。
---
## 版本记录
| 版本 | 日期 | 说明 |
|:---|:---|:---|
| 1.0 | 2026-02-26 | 初版;写作、上传、运营报表统一为子类,原 Soul文章写作 / Soul文章上传 合并至此 |

View File

@@ -0,0 +1,56 @@
# Soul 文章上传 · 子类说明
> 本子类归属 **Soul创业实验** Skill。文章写好后按本文执行上传到小程序。
---
## 前置
- 文章已按 **写作/写作规范.md** 写好,保存为 `9.xx 第X场主题.md`位于书稿第9章目录。
---
## 路径与配置
| 项目 | 值 |
|:---|:---|
| 第9章文章目录 | `/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/` |
| 项目(含 content_upload | `一场soul的创业实验-永平``一场soul的创业实验-react`(根目录有 `content_upload.py` |
| 第9章参数 | part-4, chapter-9, price 1.0 |
---
## 上传命令
**永平****react** 项目根目录执行:
```bash
python3 content_upload.py --id 9.xx --title "9.xx 第X场标题" \
--content-file "<文章完整路径>" --part part-4 --chapter chapter-9 --price 1.0
```
示例9.23
```bash
cd "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-react"
python3 content_upload.py --id 9.23 --title "9.23 第110场Soul变现逻辑全程公开" \
--content-file "/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/9.23 第110场Soul变现逻辑全程公开.md" \
--part part-4 --chapter chapter-9 --price 1.0
```
- id 已存在 → **更新**;不存在 → **创建**
- 依赖:`pip install pymysql`;数据库为腾讯云 `soul_miniprogram.chapters`
---
## 发飞书群(可选)
- 永平项目下:`python3 scripts/post_to_feishu.py --release 9.xx --title "9.xx 第X场标题"` 可发新发布通知到卡若日志飞书群。
- 海报到飞书:需配置 `scripts/.env.feishu`FEISHU_APP_ID / FEISHU_APP_SECRET调用 `send_poster_to_feishu.py`(若存在)。
---
## 其他
- 查看篇章结构:`python3 content_upload.py --list-structure`
- 列出章节:`python3 content_upload.py --list-chapters`

View File

@@ -0,0 +1,90 @@
# 《一场soul的创业实验》第9章写作规范
> 本规范归属 **Soul创业实验** Skill → 子类「写作」。写第9章单场文章时以本文件 + 主 SKILL 内写作要点为准。
---
## 一、人称与专有名词(强制)
| 项目 | 规范 |
|:---|:---|
| **「我」** | **整篇文章最多出现一次**。建议不用或仅一处;用「这边」「直接」「就」等替代。成稿后全文搜索「我」,超过 1 处必须改写。 |
| **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 |
---
## 二、基础要求
| 项目 | 规范 |
|:---|:---|
| 字数 | 每小节 20005000 字 |
| 来源 | 以真实聊天内容为基础,不改原意 |
| 数据 | 以聊天内提到的数值为主,不编造(场观、人数、时长、营收等) |
| 配图 | 不需要 |
---
## 三、主题与结构
- **一个观点**:每节只表达一个核心观点,主题清晰
- **有数据**:用具体数值验证(场观、人数、时长、营收等)
- **与目录一致**:小节名称、内容与全书结构、其他小节风格统一
- **时间**:以文档/聊天记录时间为准
---
## 四、开篇设计
- 一句有冲突、世俗的金句(不抒情、不空泛)
- 以具体事件、场景、对话切入
- 日期 + 时间 + 关键数据(第 X 场,时长,进房人数)
---
## 五、内容结构
- **推进方式**:时间线或事件线,逻辑清晰(如「有人问 → 回答」「3 号问 → …」)
- **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题
- **穿插**:细节、对话、观点分析
- **多用对话**增强真实感「X 号问」「有人问」「直接回答」「这边说」等)
---
## 六、写作技巧
- 第一人称叙述时少用「我」,见第一节
- 短句,大白话,口语化
- 每句话后空一行,段间空行
- 善用对比、反转;适当自嘲或幽默
---
## 七、结尾
- 总结观点,升华主题
- 留有余味或行动号召
---
## 八、联系管理 / 做切片 / 做副业(隐晦植入)
在文中偶尔、自然地暗示「联系管理」「做切片」「做副业」,约 24 处,融入叙述,不单独成段喊话。示例:
- 想做切片、做副业的,可以私聊管理,有电脑、会剪辑的都能问问。
- 想参与副业、做切片的,联系管理就行。
- 想做切片的可以找管理了解一下流程。
- 有事想对接、想做副业或切片的,找管理就行。
---
## 九、项目分享的隐晦提问(融入对话)
若写派对内「项目分享」类内容,可把以下维度融入对话,不列清单:目标人群?生意逻辑?核心是什么?进群可联系管理?每月营收多少?
---
## 十、格式规范(统一)
- 每句话后空一行
- 段间空行
- 对话、细节、观点分行,避免大段堆砌
-`---` 做段落分隔(与全书一致)

View File

@@ -1,159 +1,5 @@
---
name: Soul文章上传
description: 《一场soul创业实验》第9章文章写入小程序后端写好即传id 已存在则更新不重复
triggers: Soul文章上传、Soul派对文章、第9章上传、soul 上传、写soul文章
owner: 水桥
group: 水
version: "1.0"
updated: "2026-02-20"
redirect: 02_卡人/水桥_平台对接/Soul创业实验
---
# Soul文章上传 Skill
> 写好文章直接上传到 Soul 小程序id 已存在则更新,保持不重复。 —— 水桥
---
## 触发条件
用户说以下关键词时自动激活:
- Soul文章上传、Soul派对文章、第9章上传
- soul 上传、写soul文章、上传到soul
- 写好文章上传、文章写好了上传
---
## 核心配置
| 项目 | 路径/值 |
|:---|:---|
| Soul 项目根目录 | `/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验` |
| 第9章文章目录 | `/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/` |
| 上传脚本 | `一场soul的创业实验/scripts/upload_soul_article.sh` |
| 底层工具 | `一场soul的创业实验/content_upload.py` |
| 第9章固定参数 | part-4第四篇真实的赚钱, chapter-9第9章我在Soul上亲访的赚钱案例 |
---
## 执行步骤
### 1. 写好文章
文章放在第9章目录文件名格式`9.xx 第X场主题.md`
示例:`9.18 第105场创业社群、直播带货与程序员.md`
### 2. 执行上传
```bash
/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验/scripts/upload_soul_article.sh "/Users/karuo/Documents/个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章我在Soul上亲访的赚钱案例/9.xx 第X场标题.md"
```
### 3. 一键命令(替换为实际文件路径)
```bash
cd /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验
./scripts/upload_soul_article.sh "<文章完整路径>"
```
---
## 输出说明
| 返回 | 含义 |
|:---|:---|
| `"action": "创建"` | 新章节已写入 |
| `"action": "更新"` | 同 id 已存在,内容已更新(不重复) |
---
## 配套脚本
| 脚本 | 用途 |
|:---|:---|
| `content_upload.py` | 直连数据库,创建/更新章节(依赖 pymysql |
| `scripts/upload_soul_article.sh` | 从文件名提取 id、title调用 content_upload |
### 手动调用 content_upload高级
```bash
cd /Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验
# 上传指定文章
python3 content_upload.py --id 9.18 --title "9.18 第105场主题" \
--content-file "<文章路径>" --part part-4 --chapter chapter-9 --price 1.0
# 查看篇章结构
python3 content_upload.py --list-structure
# 列出所有章节
python3 content_upload.py --list-chapters
```
---
## API 接口(备用)
管理后台 API需 Token
| 接口 | 说明 |
|:---|:---|
| `POST /api/admin` | 登录获取 TokenBody: `{"username":"admin","password":"admin123"}` |
| `POST /api/db/book` | 创建/更新章节Header: `Authorization: Bearer {token}` |
正式环境:`https://soulapi.quwanzhi.com`
开发环境:`https://souldev.quwanzhi.com`
---
## 经验与注意
### 1. 不重复机制
-`id`(如 9.18)为唯一键
- id 已存在 → **更新**;不存在 → **创建**
- 同一场次多次上传不会重复,只会覆盖
### 2. 文件名规范
- 格式:`9.xx 第X场主题.md`
- id 从文件名提取,必须形如 `9.18`(章.节)
### 3. 环境依赖
```bash
pip3 install pymysql
```
### 4. 与书的对应关系
- 书稿目录:`个人/2、我写的书/《一场soul的创业实验》/第四篇|真实的赚钱/第9章...`
- 小程序内容来源:腾讯云 MySQL `soul_miniprogram.chapters`
- content_upload 直连数据库,与 API 共用同一数据源
### 5. 发海报到飞书
上传成功后自动生成海报图片(含小程序码)并发送到飞书群,**不发链接**,直接发图。
- 海报格式与小程序「生成海报」一致Soul创业派对、标题、金句、日期、小程序码
- 小程序码由 Soul 后端 `/api/miniprogram/qrcode` 生成
- 需配置:`scripts/.env.feishu` 或环境变量 `FEISHU_APP_ID``FEISHU_APP_SECRET`(与 webhook 同租户)
- 依赖:`pip install Pillow requests`
手动发:`python3 scripts/send_poster_to_feishu.py "<文章路径>"``--id 9.15`
仅保存本地:`python3 scripts/send_poster_to_feishu.py "<路径>" --save poster.png`
### 6. 工作流建议
1. 根据 Soul 派对 TXT 写好文章(按书格式:一句一行、金句开头、日期、`---` 分段)
2. 保存为 `9.xx 第X场主题.md` 到第9章目录
3. 执行 `upload_soul_article.sh "<文章路径>"`
4. 检查返回为「创建」或「更新」,海报会自动发到飞书群
---
## 版本记录
| 版本 | 日期 | 说明 |
|:---|:---|:---|
| 1.0 | 2026-02-20 | 初版,从 Soul 项目抽取为独立技能 |
已合并至 **Soul创业实验** → 子类「上传」。见 `Soul创业实验/上传/README.md`

View File

@@ -0,0 +1,5 @@
---
name: Soul文章写作
redirect: 02_卡人/水桥_平台对接/Soul创业实验
---
已合并至 **Soul创业实验** → 子类「写作」。见 `Soul创业实验/写作/写作规范.md`

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""
单条妙记下载:先试飞书开放平台 API仅取元数据/标题),再 Cookie 导出正文,最后 Playwright 页面内导出。
说明:开放平台 GET /minutes/v1/minutes/{token} 只返回 title/duration/url 等,不包含转写正文。
用法:
python3 feishu_minutes_one_url.py "https://cunkebao.feishu.cn/minutes/obcn39v4qf533r7fr55un7qv"
python3 feishu_minutes_one_url.py --token obcn39v4qf533r7fr55un7qv -o /path/to/out
# Playwright 弹窗内需登录时加大等待: --playwright-wait 90
"""
from __future__ import annotations
import argparse
import re
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
COOKIE_FILE = SCRIPT_DIR / "cookie_minutes.txt"
OUT_DIR = Path("/Users/karuo/Documents/聊天记录/soul")
EXPORT_URL = "https://cunkebao.feishu.cn/minutes/api/export"
OPEN_API_BASE = "https://open.feishu.cn/open-apis"
def extract_token(url: str) -> str | None:
m = re.search(r"/minutes/([a-zA-Z0-9]+)", url)
return m.group(1) if m else None
def get_minute_meta_open_api(object_token: str) -> dict | None:
"""飞书开放平台 获取妙记信息仅元数据title/duration/url无转写正文"""
try:
import requests
import os
app_id = os.environ.get("FEISHU_APP_ID") or "cli_a48818290ef8100d"
app_secret = os.environ.get("FEISHU_APP_SECRET") or "dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4"
r = requests.post(
f"{OPEN_API_BASE}/auth/v3/tenant_access_token/internal",
json={"app_id": app_id, "app_secret": app_secret},
timeout=10,
)
j = r.json()
if j.get("code") != 0:
return None
token = j.get("tenant_access_token")
r2 = requests.get(
f"{OPEN_API_BASE}/minutes/v1/minutes/{object_token}",
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
d = r2.json()
if d.get("code") == 0 and d.get("data", {}).get("minute"):
return d["data"]["minute"]
except Exception:
pass
return None
def export_via_cookie(object_token: str) -> str | None:
"""使用 cookie_minutes.txt 或 feishu_minutes_export_github 逻辑导出。"""
sys.path.insert(0, str(SCRIPT_DIR))
try:
from feishu_minutes_export_github import get_cookie_from_args_or_file, export_transcript
except ImportError:
return None
cookie = get_cookie_from_args_or_file(None)
if not cookie:
return None
return export_transcript(cookie, object_token)
def export_via_playwright_page(object_token: str, title: str = "", wait_sec: int = 30) -> str | None:
"""Playwright 打开妙记页,在页面内 fetch 导出接口(带 credentials无感拿正文。"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
return None
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_one_")
result = [None]
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
pg.goto(f"https://cunkebao.feishu.cn/minutes/{object_token}", wait_until="domcontentloaded", timeout=25000)
print(f" 页面已打开,等待 {wait_sec} 秒(若未登录请先登录)…")
time.sleep(wait_sec)
js = f"""
async () => {{
const r = await fetch('{EXPORT_URL}?object_token={object_token}&add_speaker=true&add_timestamp=false&format=2', {{method:'POST', credentials:'include'}});
return await r.text();
}}
"""
text = pg.evaluate(js)
ctx.close()
if text and len(str(text)) > 400 and "please log in" not in str(text).lower():
result[0] = str(text).strip()
except Exception as e:
print(f" Playwright 失败: {e}", file=sys.stderr)
finally:
import shutil
shutil.rmtree(ud, ignore_errors=True)
return result[0]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("url_or_token", nargs="?", help="妙记链接或 object_token")
ap.add_argument("--token", "-t", help="object_token")
ap.add_argument("-o", "--output-dir", default=str(OUT_DIR), help="输出目录")
ap.add_argument("--title", help="保存文件名中的标题")
ap.add_argument("--playwright-wait", type=int, default=30, help="Playwright 打开页后等待秒数")
args = ap.parse_args()
token = args.token or (extract_token(args.url_or_token or "") or args.url_or_token)
if not token or len(token) < 20:
print("用法: python3 feishu_minutes_one_url.py <妙记URL或object_token>", file=sys.stderr)
return 1
out_dir = Path(args.output_dir)
title = (args.title or "").strip()
if not title:
meta = get_minute_meta_open_api(token)
if meta and meta.get("title"):
title = meta["title"]
else:
title = f"妙记_{token[:12]}"
print(f"📌 object_token: {token} title: {title[:50]}")
print(" 1) 尝试 Cookie 导出…")
text = export_via_cookie(token)
if text:
out_dir.mkdir(parents=True, exist_ok=True)
safe = re.sub(r'[\\/*?:"<>|]', "_", title)
path = out_dir / f"{safe}.txt"
path.write_text(f"标题: {title}\nobject_token: {token}\n\n文字记录:\n\n{text}", encoding="utf-8")
print(f" ✅ Cookie 导出成功 -> {path}")
return 0
print(" 2) Cookie 失败,改用 Playwright 页面内导出…")
text = export_via_playwright_page(token, title=title, wait_sec=args.playwright_wait)
if text:
out_dir.mkdir(parents=True, exist_ok=True)
safe = re.sub(r'[\\/*?:"<>|]', "_", title)
path = out_dir / f"{safe}.txt"
path.write_text(f"标题: {title}\nobject_token: {token}\n\n文字记录:\n\n{text}", encoding="utf-8")
print(f" ✅ Playwright 导出成功 -> {path}")
return 0
print(" ❌ 两种方式均失败", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,79 @@
# 飞书 Docx 文档插入图片 · API 说明
> 便于以后在日志/文档中直接插入图片。两步:先上传素材,再创建块。
---
## 一、两步流程
### 1. 上传图片(获取 file_token
- **接口**`POST https://open.feishu.cn/open-apis/drive/v1/medias/upload_all`
- **Content-Type**`multipart/form-data`
- **必填字段**
- `file_name`:文件名,如 `chart.png`
- `parent_type`:固定 `docx_image`(表示挂到某 docx 下)
- `parent_node`:目标文档的 **doc_token**(即 wiki 节点对应的 `obj_token`
- `size`:文件大小(字节数,字符串)
- `file`:图片二进制
- **返回**`data.file_token`,后续创建块时使用。
- **限制**:单文件 ≤ 20MB上传接口约 5 QPS。
### 2. 在文档中插入图片块
- **接口**`POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children`
- **说明**:在 `block_id`(通常用文档根 `document_id` 作为父块)下插入子块;`index` 为插入位置(从 0 起)。
- **Body** 示例:
- **图片块file**`block_type = 12`
```json
{
"children": [{
"block_type": 12,
"file": {
"file_token": "上传返回的 file_token",
"view_type": "inline",
"file_name": "chart.png"
}
}],
"index": 3
}
```
- **画廊块gallery**`block_type = 18`,单图也可用
```json
{
"children": [{
"block_type": 18,
"gallery": {
"image_list": [{"file_token": "xxx"}],
"gallery_style": {"align": "center"}
}
}],
"index": 3
}
```
- **常见错误**
- **1770001 invalid param**:多为请求体字段名/格式不符合(如需用 snake_case 或 camelCase需以实际文档为准
- **1770013**:图片/文件关联关系不正确,需先上传素材再使用返回的 `file_token`。
---
## 二、本仓库脚本参考
- **上传**`脚本/feishu_publish_blocks_with_images.py` 中 `upload_image_to_doc()`
- **file 块**:同上,`_make_file_block(file_token, filename)`
- **gallery 块**:同上,`_make_gallery_block(file_token)`
- **今日日志+图**`脚本/write_today_log_with_image.py`(生成对比图 → 上传 → 插入;若插入报 1770001可先手动拖入图片
---
## 三、手动插入图片
若 API 插入一直报错(如 1770001
1. 对比图已由 `write_today_log_with_image.py` 生成并复制到 **`参考资料/昨日今日完成度对比.png`**。
2. 打开对应飞书文档2 月日志),在今日日志处直接拖入该图片或粘贴。
---
**版本**2026-02-26

View File

@@ -1,6 +1,6 @@
{
"access_token": "u-6abbeUxh5eObG3pv6dpLKEl5kMMBk1UPWoaaVxA00BPj",
"refresh_token": "ur-6mvshlL2J7CH_kCODdIHB6l5mUUBk1UjP8aaIMQ00Azj",
"access_token": "u-7yJyWJPZx6Cq44qXV7ts9.l5mgWBk1WjWoaaVAM00AP7",
"refresh_token": "ur-5_8a_UjxF8zaD9RU3kIIkrl5kUg5k1MVUoaaVBQ00wyj",
"name": "飞书用户",
"auth_time": "2026-02-25T22:29:57.710659"
"auth_time": "2026-02-26T15:58:11.492177"
}

View File

@@ -0,0 +1,438 @@
#!/usr/bin/env python3
"""
从飞书 Wiki 页面下载所有图片(命令行 + 飞书 Token
用法:
# 方式一:环境变量传入 Token推荐
export FEISHU_TOKEN="t-xxx" # 或 user_access_token: u-xxx
python3 feishu_wiki_download_images.py "https://scnr5b9fypdq.feishu.cn/wiki/NAMUwdbuFiAy2GkmMZ3clXynnwc"
# 方式二:使用同目录下 .feishu_tokens.json与 feishu_wiki_create_doc 一致)
python3 feishu_wiki_download_images.py "https://scnr5b9fypdq.feishu.cn/wiki/NAMUwdbuFiAy2GkmMZ3clXynnwc"
# 指定保存目录
python3 feishu_wiki_download_images.py "URL" -o ./wiki_images
依赖requestspip install requests
"""
import os
import re
import sys
import json
import argparse
import time
from pathlib import Path
from urllib.parse import urlparse
from datetime import datetime
import requests
SCRIPT_DIR = Path(__file__).resolve().parent
CONFIG = {
"APP_ID": "cli_a48818290ef8100d",
"APP_SECRET": "dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4",
"TOKEN_FILE": SCRIPT_DIR / ".feishu_tokens.json",
}
def load_tokens():
if CONFIG["TOKEN_FILE"].exists():
with open(CONFIG["TOKEN_FILE"], encoding="utf-8") as f:
return json.load(f)
return {}
def get_app_token():
r = requests.post(
"https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal",
json={"app_id": CONFIG["APP_ID"], "app_secret": CONFIG["APP_SECRET"]},
timeout=10,
)
data = r.json()
return data.get("app_access_token") if data.get("code") == 0 else None
def refresh_user_token(tokens):
if not tokens.get("refresh_token"):
return None
app_token = get_app_token()
if not app_token:
return None
r = 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": tokens["refresh_token"]},
timeout=10,
)
result = r.json()
if result.get("code") == 0:
data = result.get("data", {})
tokens["access_token"] = data.get("access_token")
tokens["refresh_token"] = data.get("refresh_token", tokens["refresh_token"])
tokens["auth_time"] = datetime.now().isoformat()
with open(CONFIG["TOKEN_FILE"], "w", encoding="utf-8") as f:
json.dump(tokens, f, ensure_ascii=False, indent=2)
return tokens["access_token"]
return None
def get_token(node_token: str) -> str | None:
"""优先 FEISHU_TOKEN否则用 .feishu_tokens.json 的 user access_token必要时刷新"""
env_token = os.environ.get("FEISHU_TOKEN", "").strip()
if env_token:
return env_token
tokens = load_tokens()
access = tokens.get("access_token")
if access:
# 简单校验:能访问 get_node 即认为有效
try:
r = requests.get(
f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={node_token}",
headers={"Authorization": f"Bearer {access}"},
timeout=10,
)
if r.json().get("code") == 0:
return access
except Exception:
pass
new_token = refresh_user_token(tokens)
if new_token:
try:
r = requests.get(
f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={node_token}",
headers={"Authorization": f"Bearer {new_token}"},
timeout=10,
)
if r.json().get("code") == 0:
return new_token
except Exception:
pass
return None
def wiki_url_to_node_token(url: str) -> str | None:
"""从 Wiki 页面 URL 解析 node_token。"""
# .../wiki/NAMUwdbuFiAy2GkmMZ3clXynnwc 或 .../wiki/xxx?...
m = re.search(r"/wiki/([A-Za-z0-9_-]+)", url)
return m.group(1) if m else None
def get_wiki_node(token: str, node_token: str) -> dict | None:
"""获取 wiki 节点信息,返回 node 字典(含 obj_token, obj_type"""
r = requests.get(
f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={node_token}",
headers={"Authorization": f"Bearer {token}"},
timeout=30,
)
j = r.json()
if j.get("code") != 0:
return None
return j.get("data", {}).get("node")
def get_wiki_child_nodes(token: str, space_id: str, parent_node_token: str) -> list[dict]:
"""获取知识空间下某节点的子节点列表(分页拉全)。返回 [{node_token, obj_token, obj_type, title}, ...]。"""
headers = {"Authorization": f"Bearer {token}"}
out = []
page_token = None
while True:
params = {"parent_node_token": parent_node_token, "page_size": 50}
if page_token:
params["page_token"] = page_token
r = requests.get(
f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes",
headers=headers,
params=params,
timeout=30,
)
j = r.json()
if j.get("code") != 0:
break
data = j.get("data") or {}
items = data.get("items") or []
for n in items:
out.append({
"node_token": n.get("node_token"),
"obj_token": n.get("obj_token"),
"obj_type": (n.get("obj_type") or "").lower(),
"title": (n.get("title") or "未命名").strip() or "未命名",
})
page_token = data.get("page_token")
if not page_token:
break
time.sleep(0.2)
return out
def collect_file_tokens_from_sheet(token: str, spreadsheet_token: str) -> list[str]:
"""从电子表格中收集浮动图片的 file_token / float_image_token。"""
headers = {"Authorization": f"Bearer {token}"}
file_tokens = []
# 1. 获取表格元数据(含所有 sheet_id
r = requests.get(
f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/{spreadsheet_token}",
headers=headers,
params={"ext": "true"},
timeout=30,
)
j = r.json()
if j.get("code") != 0:
return []
sheets = (j.get("data") or {}).get("spreadsheet", {}).get("sheets") or []
if not sheets:
return []
# 2. 每个 sheet 查询浮动图片(部分版本 API 支持 list
for sh in sheets:
sheet_id = sh.get("sheet_id")
if not sheet_id:
continue
list_r = requests.get(
f"https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/{spreadsheet_token}/sheets/{sheet_id}/float_images",
headers=headers,
timeout=30,
)
list_j = list_r.json()
if list_j.get("code") == 0:
items = (list_j.get("data") or {}).get("items") or []
for item in items:
ft = item.get("file_token") or item.get("float_image_token")
if ft:
file_tokens.append(ft)
time.sleep(0.2)
# 3. 若没有浮动图片,尝试从单元格取值中解析附件 file_token公式计算值/格式化值)
if not file_tokens:
for sh in sheets:
sheet_id = sh.get("sheet_id")
if not sheet_id:
continue
range_str = f"{sheet_id}!A1:ZZ300"
vr = requests.get(
"https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{}/values_batch_get".format(
spreadsheet_token
),
headers=headers,
params={"ranges": range_str, "valueRenderOption": "FormattedValue"},
timeout=30,
)
vj = vr.json()
if vj.get("code") != 0:
continue
value_ranges = (vj.get("data") or {}).get("valueRanges") or (vj.get("data") or {}).get("valueRange") or []
if not isinstance(value_ranges, list):
value_ranges = [value_ranges] if value_ranges else []
for tab in value_ranges:
vals = tab.get("values") or []
for row in vals:
for cell in row if isinstance(row, (list, tuple)) else [row]:
_extract_file_tokens(cell, file_tokens)
time.sleep(0.2)
return file_tokens
def _extract_file_tokens(obj, out: list):
"""从单元格值(可能为嵌套 list/dict中递归提取 file_token/fileToken。"""
if isinstance(obj, dict):
ft = obj.get("file_token") or obj.get("fileToken")
if ft and isinstance(ft, str):
out.append(ft)
for v in obj.values():
_extract_file_tokens(v, out)
elif isinstance(obj, (list, tuple)):
for x in obj:
_extract_file_tokens(x, out)
def collect_file_tokens_from_docx(token: str, document_id: str) -> list[str]:
"""从 docx 文档拉取全部 blocks收集图片/文件的 file_token。"""
headers = {"Authorization": f"Bearer {token}"}
file_tokens = []
page_token = None
while True:
params = {"page_size": 200}
if page_token:
params["page_token"] = page_token
r = requests.get(
f"https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks",
headers=headers,
params=params,
timeout=30,
)
j = r.json()
if j.get("code") != 0:
break
data = j.get("data", {}) or {}
items = data.get("items", []) or []
for block in items:
bt = block.get("block_type")
# 12 = file, 18 = gallery
if bt == 12:
ft = (block.get("file") or {}).get("file_token")
if ft:
file_tokens.append(ft)
elif bt == 18:
gal = block.get("gallery") or {}
image_list = gal.get("image_list") or gal.get("imageList") or []
for img in image_list:
ft = (img if isinstance(img, dict) else {}).get("file_token")
if ft:
file_tokens.append(ft)
page_token = data.get("page_token")
if not page_token:
break
time.sleep(0.25)
return file_tokens
def get_tmp_download_urls(token: str, file_tokens: list[str]) -> dict[str, str]:
"""批量获取临时下载链接。返回 file_token -> url。"""
if not file_tokens:
return {}
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
# 接口单次最多 100 个
out = {}
for i in range(0, len(file_tokens), 100):
chunk = file_tokens[i : i + 100]
r = requests.post(
"https://open.feishu.cn/open-apis/drive/v1/medias/batch_get_tmp_download_url",
headers=headers,
json={"file_tokens": chunk},
timeout=30,
)
j = r.json()
if j.get("code") != 0:
continue
for item in (j.get("data") or {}).get("tmp_download_urls", []) or []:
ft = item.get("file_token")
url = item.get("tmp_download_url")
if ft and url:
out[ft] = url
time.sleep(0.25)
return out
def download_file(url: str, save_path: Path, token: str) -> bool:
"""用 curl 下载(带 Authorization到 save_path。"""
save_path.parent.mkdir(parents=True, exist_ok=True)
cmd = [
"curl",
"-sS",
"-L",
"-H",
f"Authorization: Bearer {token}",
"-o",
str(save_path),
url,
]
ret = os.system(" ".join(cmd))
return ret == 0
def _collect_from_node(token: str, obj_token: str, obj_type: str) -> list[str]:
"""从单个节点docx/sheet收集 file_tokens。"""
if obj_type == "sheet":
return collect_file_tokens_from_sheet(token, obj_token)
if obj_type == "docx":
return collect_file_tokens_from_docx(token, obj_token)
return []
def main():
ap = argparse.ArgumentParser(description="从飞书 Wiki 页面下载所有图片(命令行 + Token")
ap.add_argument("url", help="Wiki 页面 URL如 https://xxx.feishu.cn/wiki/NAMUwdbuFiAy2GkmMZ3clXynnwc")
ap.add_argument("-o", "--output", default="./feishu_wiki_images", help="图片保存目录,默认 ./feishu_wiki_images")
ap.add_argument("--all-pages", action="store_true", help="遍历当前节点下所有子页面docx/sheet汇总下载图片")
args = ap.parse_args()
node_token = wiki_url_to_node_token(args.url)
if not node_token:
print("无法从 URL 解析 wiki node_token")
sys.exit(1)
token = get_token(node_token)
if not token:
print("未设置 FEISHU_TOKEN 且无法从 .feishu_tokens.json 获取有效 Token请先授权或 export FEISHU_TOKEN=xxx")
sys.exit(1)
node = get_wiki_node(token, node_token)
if not node:
print("获取 Wiki 节点失败,请检查链接与权限")
sys.exit(1)
space_id = node.get("space_id") or (node.get("space") or {}).get("space_id") or node.get("origin_space_id")
obj_token = node.get("obj_token")
obj_type = (node.get("obj_type") or "").lower()
file_tokens = []
if args.all_pages:
if not space_id:
print("无法获取知识空间 space_id无法遍历子节点将仅处理当前节点。")
else:
# 遍历子节点,从每个 docx/sheet 收集图片
children = get_wiki_child_nodes(token, space_id, node_token)
docx_sheet = [c for c in children if (c.get("obj_type") or "") in ("docx", "sheet") and c.get("obj_token")]
print(f"当前节点下共 {len(children)} 个子节点,其中 docx/sheet: {len(docx_sheet)}")
for ch in docx_sheet:
ct = ch.get("obj_type") or ""
co = ch.get("obj_token")
title = ch.get("title") or "未命名"
tokens = _collect_from_node(token, co, ct)
if tokens:
print(f" 子页「{title}」: {len(tokens)} 个资源")
file_tokens.extend(tokens)
time.sleep(0.2)
# 若子页无图,且当前节点本身是 docx/sheet则从当前节点再收一次
if not file_tokens and obj_token and obj_type in ("docx", "sheet"):
file_tokens = _collect_from_node(token, obj_token, obj_type)
if file_tokens:
print(f" 当前节点({obj_type}: {len(file_tokens)} 个资源")
else:
# 仅当前节点
if not obj_token:
print("该节点无关联文档obj_token 为空)。可尝试加上 --all-pages 遍历子页面。")
sys.exit(1)
if obj_type not in ("docx", "doc", "sheet"):
print(f"当前仅支持 docx/doc/sheet该节点类型为: {obj_type}。可尝试加上 --all-pages 遍历子页面。")
sys.exit(1)
if obj_type == "doc":
print("旧版 doc 的图片需通过 doc/v2 接口解析,本脚本暂仅支持 docx可先在飞书内将页面另存为新版文档再试。")
sys.exit(1)
file_tokens = _collect_from_node(token, obj_token, obj_type)
if not file_tokens:
print("未发现任何图片或文件块。若为目录/表格,请使用 --all-pages 遍历子页面。")
sys.exit(0)
# 去重保持顺序
seen = set()
unique_tokens = []
for ft in file_tokens:
if ft not in seen:
seen.add(ft)
unique_tokens.append(ft)
urls = get_tmp_download_urls(token, unique_tokens)
out_dir = Path(args.output).resolve()
out_dir.mkdir(parents=True, exist_ok=True)
for i, ft in enumerate(unique_tokens):
url = urls.get(ft)
if not url:
print(f"跳过 {ft}(无临时链接)")
continue
ext = "png"
save_path = out_dir / f"image_{i+1:04d}.{ext}"
if download_file(url, save_path, token):
print(f"已保存: {save_path}")
else:
print(f"下载失败: {save_path}")
print(f"完成,共 {len(unique_tokens)} 个资源,已保存到: {out_dir}")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""
今日飞书日志:含昨日对比、完成度变化、并插入对比图到文档。
- 读取昨日(2月25日)进度,生成今日(2月26日)日志与对比说明
- 生成昨日vs今日完成度对比图
- 上传图片到飞书 docx插入到今日日志中
"""
import os
import sys
import re
import requests
from pathlib import Path
from datetime import datetime
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
from auto_log import (
get_token_silent,
build_blocks,
resolve_wiki_token_for_date,
parse_month_from_date_str,
CONFIG,
)
# 昨日进度(从 2月25日 日志解析或写死)
YESTERDAY_PROGRESS = {"卡若AI": 55, "小程序": 0, "投资": 0}
# 今日进度(可略增表示推进)
TODAY_PROGRESS = {"卡若AI": 56, "小程序": 0, "投资": 0}
FEB_TOKEN = CONFIG.get("MONTH_WIKI_TOKENS", {}).get(2) or "Jn2EwXP2OiTujNkAbNCcDcM7nRA"
TODAY_DATE = "2月26日"
def _make_today_tasks():
"""今日任务:含昨日对比、完成度是否增长"""
growth = TODAY_PROGRESS["卡若AI"] - YESTERDAY_PROGRESS["卡若AI"]
growth_text = f"较昨日+{growth}%" if growth > 0 else "与昨日持平" if growth == 0 else f"较昨日{growth}%"
return [
{
"person": "卡若",
"events": ["飞书日志今日汇总", "昨日对比", "完成度跟踪", "运营文档"],
"quadrant": "重要紧急",
"t_targets": [
f"卡若AI开发→接口与网站持续推进 🔧 ({TODAY_PROGRESS['卡若AI']}%)",
"创业实业小程序→完成对接与上线清单 📱 (0%)",
"投资落地→与阿猫确定目标与方向 💰 (0%)",
],
"n_process": [
"【昨日回顾】2月25日卡若AI 55%、小程序 0%、投资 0%;重点在服务器卡点与功能/方案清晰度",
f"【今日对比】卡若AI {TODAY_PROGRESS['卡若AI']}% {growth_text};小程序、投资仍待启动",
"【非程序侧】昨日文档gitea_push_log、代码管理、运营中枢已纳入梳理今日继续补齐功能项与解决方案映射",
"【后续】先解服务器与部署卡点,再推进接口/网站与小程序、投资事项",
],
"t_thoughts": [
"用昨日vs今日完成度做对比便于看是否增长",
"除程序开发外,文档与运营登记也要在日志里体现",
],
"w_work": ["日志登记", "昨日对比", "接口与网站推进", "方案梳理"],
"f_feedback": [
f"卡若AI→进行中 🔄({TODAY_PROGRESS['卡若AI']}%{growth_text}",
"下图:昨日 vs 今日 完成度对比",
],
}
]
def generate_comparison_image(out_path: Path) -> bool:
"""生成昨日vs今日完成度对比图简单柱状示意保存为 PNG"""
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
except ImportError:
# 无 matplotlib 时用纯色 PNG 占位(可选:用 Pillow 画简单矩形)
try:
from PIL import Image, ImageDraw
w, h = 640, 360
img = Image.new("RGB", (w, h), color=(248, 250, 252))
d = ImageDraw.Draw(img)
# 简单文字 + 矩形条
d.rectangle([(80, 120), (80 + 180, 220)], fill=(59, 130, 246), outline=(37, 99, 235))
d.rectangle([(320, 120), (320 + 200, 220)], fill=(34, 197, 94), outline=(22, 163, 74))
d.text((130, 250), "昨日 55%", fill=(31, 41, 55))
d.text((380, 250), "今日 56%", fill=(31, 41, 55))
img.save(out_path)
return True
except Exception as e:
print(f"⚠️ 生成图片失败: {e}")
return False
# matplotlib 路径
fig, ax = plt.subplots(figsize=(6, 3.5))
labels = ["昨日(2/25)", "今日(2/26)"]
values = [YESTERDAY_PROGRESS["卡若AI"], TODAY_PROGRESS["卡若AI"]]
colors = ["#3b82f6", "#22c55e"]
bars = ax.bar(labels, values, color=colors, edgecolor="white", linewidth=1.2)
ax.set_ylabel("完成度 (%)")
ax.set_ylim(0, 100)
for b, v in zip(bars, values):
ax.text(b.get_x() + b.get_width() / 2, b.get_height() + 2, f"{v}%", ha="center", fontsize=12)
ax.set_title("卡若AI 完成度 · 昨日 vs 今日")
fig.tight_layout()
fig.savefig(out_path, dpi=120, bbox_inches="tight")
plt.close()
return True
def upload_image_to_feishu(token: str, doc_token: str, image_path: Path) -> str | None:
"""上传图片到 docx返回 file_token"""
if not image_path.exists():
print(f"⚠️ 图片不存在: {image_path}")
return None
size = image_path.stat().st_size
if size > 20 * 1024 * 1024:
print("⚠️ 图片超过 20MB")
return None
url = "https://open.feishu.cn/open-apis/drive/v1/medias/upload_all"
headers = {"Authorization": f"Bearer {token}"}
with open(image_path, "rb") as f:
files = {
"file_name": (None, image_path.name),
"parent_type": (None, "docx_image"),
"parent_node": (None, doc_token),
"size": (None, str(size)),
"file": (image_path.name, f, "image/png"),
}
r = requests.post(url, headers=headers, files=files, timeout=60)
data = r.json()
if data.get("code") == 0:
return data.get("data", {}).get("file_token")
print(f"⚠️ 上传失败: {data.get('msg')}")
return None
def insert_image_block_into_doc(
token: str, doc_token: str, file_token: str, filename: str, after_index: int
) -> bool:
"""在文档指定位置插入一个图片块。先试 gallery(18),失败则试 file(12)。"""
# 飞书创建块:先试 file(12),与 feishu_publish_blocks_with_images 完全一致
block = {
"block_type": 12,
"file": {"file_token": file_token, "view_type": "inline", "file_name": filename},
}
url = f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children"
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
payload = {"children": [block], "index": after_index}
r = requests.post(url, headers=headers, json=payload, timeout=30)
j = r.json()
if j.get("code") == 0:
return True
# 若 1770001尝试 camelCase
if j.get("code") == 1770001:
block2 = {
"block_type": 12,
"file": {"fileToken": file_token, "viewType": "inline", "fileName": filename},
}
r2 = requests.post(url, headers=headers, json={"children": [block2], "index": after_index}, timeout=30)
if r2.json().get("code") == 0:
return True
print(f"⚠️ 插入图片块失败: {j.get('msg')} (code={j.get('code')})")
return False
def main():
token = get_token_silent()
if not token:
print("❌ 无法获取 Token")
sys.exit(1)
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
r = requests.get(
f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={FEB_TOKEN}",
headers=headers,
timeout=30,
)
if r.json().get("code") != 0:
print("❌ 获取 2 月文档失败")
sys.exit(1)
doc_token = r.json()["data"]["node"]["obj_token"]
# 1) 检查是否已有今日
bl = requests.get(
f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks",
headers=headers,
params={"page_size": 500},
timeout=30,
).json()
items = bl.get("data", {}).get("items", [])
root = [b for b in items if b.get("parent_id") == doc_token]
def text_of(b):
for k in ("heading4", "text", "todo"):
if k in b:
return "".join(
e.get("text_run", {}).get("content", "") for e in b.get(k, {}).get("elements", [])
).strip()
return ""
has_today = any(TODAY_DATE in text_of(b) for b in root)
insert_index = 1
for i, block in enumerate(root):
if "heading2" in block:
for el in block["heading2"].get("elements", []):
if "本月最重要的任务" in el.get("text_run", {}).get("content", ""):
insert_index = i + 1
break
# 2) 写入今日日志(若尚未存在)
tasks = _make_today_tasks()
content_blocks = build_blocks(TODAY_DATE, tasks)
if not has_today:
wr = requests.post(
f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children",
headers=headers,
json={"children": content_blocks, "index": insert_index},
timeout=30,
)
if wr.json().get("code") != 0:
print(f"❌ 写入日志失败: {wr.json().get('msg')}")
sys.exit(1)
print(f"{TODAY_DATE} 日志写入成功")
# 插入位置:新内容前两个块是 标题 + callout图片放在其后
image_insert_index = insert_index + 2
else:
# 已有今日:找到今日第一个块后的位置插入图片
idx = None
for i, b in enumerate(root):
if TODAY_DATE in text_of(b):
idx = i
break
if idx is None:
print("⚠️ 已有今日但未找到块,跳过插入图片")
sys.exit(0)
# 在文档根子块中的索引:需换算为 doc 的 children index
image_insert_index = idx + 2
# 3) 生成对比图
out_dir = SCRIPT_DIR / "temp_images"
out_dir.mkdir(exist_ok=True)
img_path = out_dir / "昨日今日完成度对比.png"
if not generate_comparison_image(img_path):
print("⚠️ 跳过图片插入")
else:
# 4) 上传并插入
file_token = upload_image_to_feishu(token, doc_token, img_path)
if file_token and insert_image_block_into_doc(
token, doc_token, file_token, img_path.name, image_insert_index
):
print("✅ 对比图已插入到今日日志中")
else:
ref_path = SCRIPT_DIR.parent / "参考资料" / "昨日今日完成度对比.png"
try:
import shutil
shutil.copy2(img_path, ref_path)
print(f"⚠️ 图片已保存到:{ref_path},请打开飞书文档后手动拖入今日日志。")
except Exception:
print(f"⚠️ 图片未插入,可手动插入:{img_path}")
# 打开飞书
url = f"https://cunkebao.feishu.cn/wiki/{FEB_TOKEN}"
import subprocess
subprocess.run(["open", url], capture_output=True)
print(f"📎 已打开飞书: {url}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,33 @@
# 飞书 Wiki 页面图片下载(命令行 + Token
## 脚本
`feishu_wiki_download_images.py`:从指定飞书 Wiki 页面 URL 下载该页面内所有图片,**仅用命令行 + 飞书 Token**,不使用 MCP。
## 用法
```bash
cd "/Users/karuo/Documents/个人/卡若AI/02_卡人/水桥_平台对接/飞书管理/脚本"
# 方式一:环境变量传入 Token推荐
export FEISHU_TOKEN="t-xxx" # 或 user_access_token: u-xxx
python3 feishu_wiki_download_images.py "https://xxx.feishu.cn/wiki/节点token" -o ./输出目录
# 方式二:使用同目录 .feishu_tokens.json与 feishu_wiki_create_doc 一致,需先完成飞书授权)
python3 feishu_wiki_download_images.py "https://xxx.feishu.cn/wiki/节点token" -o ./输出目录
# 遍历当前节点下所有子页面docx/sheet汇总下载适合目录/表格节点)
python3 feishu_wiki_download_images.py "https://xxx.feishu.cn/wiki/节点token" -o ./输出目录 --all-pages
```
## 支持的页面类型
- **docx**:新版文档(会拉取文档 blocks 中的图片/文件)
- **sheet**:电子表格(会尝试浮动图片 + 单元格附件中的 file_token
- **doc**:旧版文档暂不支持,需在飞书内另存为新版后再用本脚本
## 说明
- 若该节点是「目录/表格」且未发现图片,说明图片可能在子页面;请打开具体带图片的文档页,复制其 Wiki URL 再运行本脚本。
- 依赖:`pip install requests`
- 下载使用系统 `curl`,请求头带 `Authorization: Bearer <token>`

View File

@@ -145,118 +145,215 @@
<!-- Slide 1 封面 -->
<div class="slide" id="slide-1" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="width: 940px; padding: 58px 70px; text-align:center;">
<div class="kicker" style="margin-bottom: 12px;">Shensheshou · NAS MongoDB</div>
<h1 style="margin-bottom: 16px;">神射手用户画像极速检索</h1>
<p class="sub">聚焦“完整手机号 + 多画像 + 秒级命中</p>
<div class="kicker" style="margin-bottom: 12px;">神射手 · NAS 用户画像检索能力</div>
<h1 style="margin-bottom: 16px;">神射手用户画像极速查询方案</h1>
<p class="sub">目标:完整手机号输出 + 多库融合画像 + 高速稳定命中</p>
<div style="display:flex; justify-content:center; gap:12px; margin-top: 28px;">
<span class="pill">🎯 查询提速</span>
<span class="pill">🔐 数据完整</span>
<span class="pill">📊 画像统一</span>
<span class="pill">📱 手机号完整输出</span>
<span class="pill">🧩 画像聚合统一</span>
</div>
<div class="glass" style="margin-top: 20px; padding: 12px 18px;">
<div class="small">适用场景:风控核验、客户画像、线索清洗、业务决策前置检索。</div>
</div>
</div>
</div>
<!-- Slide 2 背景与目标 -->
<!-- Slide 2 业务痛点与目标 -->
<div class="slide" id="slide-2" style="justify-content:center;">
<h2>Background & Goal</h2>
<h2>一、业务痛点与目标</h2>
<div class="row">
<div class="glass-strong col" style="flex: 1; padding: 30px 36px;">
<div class="list">
<p>现状问题<span>跨库查询慢、命中字段不统一</span></p>
<p>核心诉求<span>姓名/地址/手机号/QQ 一次拉全</span></p>
<p>输出要求<span>手机号必须完整,不打码</span></p>
<p>④ 目标结果<span>NAS 端可稳定做快速画像检索</span></p>
<p>痛点一<span>跨库字段不统一,检索路径长,响应慢</span></p>
<p>痛点二<span>结果字段缺失,手机号存在打码影响核验</span></p>
<p>痛点三<span>大表无索引或弱索引,命中依赖运气</span></p>
<p>建设目标:<span>姓名、地址、手机号、QQ 一次性拉全</span></p>
</div>
<div class="glass" style="margin-top: 12px; padding: 14px 18px;">
<div class="small">目标标准:查询快、数据全、结构稳、输出可复用。</div>
</div>
</div>
<div class="glass col" style="width: 360px; padding: 22px;">
<div class="glass col" style="width: 380px; padding: 20px;">
<div class="card">
<div class="t">Query Focus</div>
<div class="t">检索焦点</div>
<div class="v">人、号、地址、关联账号</div>
</div>
<div class="card">
<div class="t">Success Criteria</div>
<div class="t">成功标准</div>
<div class="v">快 + 全 + 稳</div>
</div>
<div class="card">
<div class="t">Deployment</div>
<div class="t">部署环境</div>
<div class="v">远程 NAS MongoDB</div>
</div>
<div class="glass" style="padding: 14px; margin-top: 8px;">
<svg viewBox="0 0 340 140" width="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="320" height="120" rx="12" fill="rgba(255,255,255,0.6)"/>
<text x="170" y="38" text-anchor="middle" font-size="14" font-weight="700" fill="rgba(0,0,0,0.6)">检索流程示意</text>
<rect x="30" y="52" width="90" height="32" rx="8" fill="rgba(0,122,255,0.15)"/>
<text x="75" y="72" text-anchor="middle" font-size="12" font-weight="700" fill="rgba(0,0,0,0.55)">输入条件</text>
<path d="M120 68 L160 68" stroke="rgba(0,0,0,0.35)" stroke-width="2"/>
<rect x="165" y="52" width="90" height="32" rx="8" fill="rgba(52,199,89,0.15)"/>
<text x="210" y="72" text-anchor="middle" font-size="12" font-weight="700" fill="rgba(0,0,0,0.55)">索引命中</text>
<path d="M255 68 L295 68" stroke="rgba(0,0,0,0.35)" stroke-width="2"/>
<rect x="300" y="52" width="30" height="32" rx="8" fill="rgba(255,159,10,0.2)"/>
<text x="315" y="72" text-anchor="middle" font-size="11" font-weight="700" fill="rgba(0,0,0,0.55)">输出</text>
<text x="170" y="108" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.45)">手机号/姓名/证件 → 路由 → 完整画像</text>
</svg>
</div>
</div>
</div>
</div>
<!-- Slide 3 方案架构 -->
<!-- Slide 3 索引与架构 -->
<div class="slide" id="slide-3" style="justify-content:center;">
<h2>Solution Architecture</h2>
<div class="glass-strong" style="padding: 30px 34px;">
<div class="flow">
<div class="node">
<h3>数据源层</h3>
<p>KR、KR_京东、KR_腾讯、KR_微博、KR_顺丰、KR_酒店、KR_户口等画像相关库。</p>
<h2>二、索引架构设计(高速命中的基础)</h2>
<div class="row">
<div class="glass-strong" style="flex:1; padding: 28px 32px;">
<div class="flow">
<div class="node">
<h3>数据源层</h3>
<p>KR、KR_京东、KR_腾讯、KR_微博、KR_顺丰、KR_存客宝、KR_酒店、KR_户口等画像相关库。</p>
</div>
<div class="node">
<h3>索引层</h3>
<p>主键:手机号/QQ/证件;画像:姓名/邮箱/省市区统一视图user_key、评分、标签。后台创建分库分批。</p>
</div>
<div class="node">
<h3>查询层</h3>
<p>统一检索脚本,优先主键字段命中,再回填扩展画像字段,输出完整手机号。</p>
</div>
</div>
<div class="node">
<h3>索引层</h3>
<p>按 phone/mobile、name、email、qq、idcard、province+city 等核心字段建索引。</p>
</div>
<div class="node">
<h3>查询层</h3>
<p>统一检索脚本,优先主键字段命中,再回填扩展画像字段。</p>
<div class="glass" style="margin-top: 16px; padding: 18px 22px;">
<div class="small">策略要点:单字段精准索引 + 组合索引补充 + 大表后台建索引,减少线上阻塞。</div>
</div>
</div>
<div class="glass" style="margin-top: 16px; padding: 18px 22px;">
<div class="small">策略关键词:单字段精准索引 + 组合索引补充 + 大表后台建索引,减少线上阻塞。</div>
<div class="glass" style="width: 360px; padding: 18px;">
<div style="font-size:14px; font-weight:800; color:rgba(0,0,0,0.55); margin-bottom:12px;">架构流程图</div>
<svg viewBox="0 0 320 380" width="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#0A84FF" stop-opacity="0.12"/><stop offset="100%" stop-color="#34C759" stop-opacity="0.08"/></linearGradient>
</defs>
<rect x="0" y="0" width="320" height="380" rx="16" fill="url(#g1)"/>
<rect x="20" y="24" width="280" height="56" rx="12" fill="rgba(255,255,255,0.85)"/>
<text x="160" y="52" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.6)">数据源层</text>
<text x="160" y="70" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.48)">多库 · 多表 · 字段归一</text>
<path d="M160 80 L160 100" stroke="rgba(0,0,0,0.3)" stroke-width="2"/>
<rect x="20" y="104" width="280" height="56" rx="12" fill="rgba(255,255,255,0.85)"/>
<text x="160" y="132" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.6)">索引层</text>
<text x="160" y="150" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.48)">phone / name / 省市区 / 评分</text>
<path d="M160 160 L160 180" stroke="rgba(0,0,0,0.3)" stroke-width="2"/>
<rect x="20" y="184" width="280" height="56" rx="12" fill="rgba(255,255,255,0.85)"/>
<text x="160" y="212" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.6)">查询层</text>
<text x="160" y="230" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.48)">路由 → 命中 → 回填 → 输出</text>
<path d="M160 240 L160 260" stroke="rgba(0,0,0,0.3)" stroke-width="2"/>
<rect x="20" y="264" width="280" height="96" rx="12" fill="rgba(255,255,255,0.85)"/>
<text x="160" y="292" text-anchor="middle" font-size="14" font-weight="800" fill="rgba(0,0,0,0.6)">输出画像</text>
<text x="160" y="314" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.48)">姓名 · 地址 · 手机号(完整) · QQ</text>
<text x="160" y="332" text-anchor="middle" font-size="11" font-weight="600" fill="rgba(0,0,0,0.48)">来源 · 评分 · 标签</text>
<text x="160" y="358" text-anchor="middle" font-size="10" font-weight="600" fill="rgba(0,0,0,0.4)">脚本nas_mongo_create_indexes / query_*_nas</text>
</svg>
</div>
</div>
</div>
<!-- Slide 4 已执行动作 -->
<!-- Slide 4 详细执行流程图 -->
<div class="slide" id="slide-4" style="justify-content:center;">
<h2>Executed Actions</h2>
<h2>三、详细执行流程图(端到端)</h2>
<div class="row">
<div class="glass-strong" style="flex:1; padding: 30px 36px;">
<div class="list">
<p>① 新建脚本:<span>nas_mongo_create_indexes.py</span></p>
<p>② 覆盖字段:<span>手机号、姓名、地址、QQ、证件、评分等</span></p>
<p>③ 输出规范:<span>query_jd_xiamen_nas.py 改为完整手机号</span></p>
<p>④ 文档沉淀:<span>快速查询与索引优化手册已更新</span></p>
<div class="glass-strong" style="flex:1; padding: 20px 24px;">
<svg viewBox="0 0 720 420" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="ar" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="rgba(0,0,0,0.35)"/></marker>
</defs>
<rect x="0" y="0" width="720" height="420" rx="14" fill="rgba(255,255,255,0.2)"/>
<g font-family="Inter,sans-serif" font-weight="800" fill="rgba(0,0,0,0.62)">
<rect x="24" y="24" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="84" y="54" text-anchor="middle" font-size="12">1.输入条件</text>
<rect x="168" y="24" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="228" y="54" text-anchor="middle" font-size="12">2.字段标准化</text>
<rect x="312" y="24" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="372" y="54" text-anchor="middle" font-size="12">3.路由目标库</text>
<rect x="456" y="24" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="516" y="54" text-anchor="middle" font-size="12">4.索引命中</text>
<rect x="600" y="24" width="100" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="650" y="54" text-anchor="middle" font-size="12">5.结果校验</text>
<rect x="456" y="100" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="516" y="130" text-anchor="middle" font-size="12">6.多源回填</text>
<rect x="312" y="100" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="372" y="130" text-anchor="middle" font-size="12">7.画像合并</text>
<rect x="168" y="100" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="228" y="130" text-anchor="middle" font-size="12">8.完整输出</text>
<rect x="24" y="100" width="120" height="48" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="84" y="130" text-anchor="middle" font-size="12">9.结构化报告</text>
<rect x="24" y="200" width="200" height="72" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="124" y="232" text-anchor="middle" font-size="13" font-weight="800">输出:姓名/地址/手机号/QQ</text>
<text x="124" y="254" text-anchor="middle" font-size="11" fill="rgba(0,0,0,0.5)">来源标记 · 评分 · 标签</text>
<rect x="256" y="200" width="220" height="72" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="366" y="232" text-anchor="middle" font-size="13" font-weight="800">性能监控与日志</text>
<text x="366" y="254" text-anchor="middle" font-size="11" fill="rgba(0,0,0,0.5)">耗时 · 命中率 · 失败重试</text>
<rect x="508" y="200" width="192" height="72" rx="10" fill="rgba(255,255,255,0.88)"/>
<text x="604" y="232" text-anchor="middle" font-size="13" font-weight="800">索引持续优化</text>
<text x="604" y="254" text-anchor="middle" font-size="11" fill="rgba(0,0,0,0.5)">新增字段 → 增量索引</text>
</g>
<g stroke="rgba(0,0,0,0.35)" stroke-width="2" fill="none" marker-end="url(#ar)">
<path d="M144 48 L168 48"/><path d="M288 48 L312 48"/><path d="M432 48 L456 48"/><path d="M576 48 L600 48"/>
<path d="M650 72 L650 100"/><path d="M516 148 L516 176" stroke-dasharray="4 4"/>
<path d="M372 148 L372 176" stroke-dasharray="4 4"/><path d="M228 148 L228 176" stroke-dasharray="4 4"/>
<path d="M84 148 L84 200"/><path d="M224 236 L256 236"/><path d="M476 236 L508 236"/>
</g>
<g font-size="10" font-weight="700" fill="rgba(0,0,0,0.45)">
<text x="516" y="168" text-anchor="middle">命中失败→回退同义字段</text>
<text x="366" y="292" text-anchor="middle">闭环反馈到索引脚本</text>
</g>
</svg>
<div class="glass" style="margin-top: 12px; padding: 14px 18px;">
<div class="small">关键策略:先命中后补全 · 失败自动回退 · 输出固定模板,形成可复制的自动化检索流水线。</div>
</div>
</div>
<div class="glass col" style="width: 360px; padding: 22px;">
<div class="card">
<div class="t">Main Script</div>
<div class="v">Index Automation</div>
</div>
<div class="card">
<div class="t">Query Policy</div>
<div class="v">Full Phone Output</div>
</div>
<div class="card">
<div class="t">Scope</div>
<div class="v">用户资产数字化画像</div>
</div>
<div class="glass col" style="width: 280px; padding: 18px;">
<div class="card"><div class="t">核心脚本</div><div class="v">索引自动化创建</div></div>
<div class="card"><div class="t">输出策略</div><div class="v">手机号完整不打码</div></div>
<div class="card"><div class="t">覆盖范围</div><div class="v">用户资产数字化画像</div></div>
</div>
</div>
</div>
<!-- Slide 5 效果与价值 -->
<div class="slide" id="slide-5" style="justify-content:center;">
<h2>Result & Value</h2>
<h2>四、效果展示与价值输出</h2>
<div class="row">
<div class="glass-strong col" style="flex:1; padding: 30px 36px;">
<div class="list">
<p>① 查询效率:<span>从“全库碰运气”变成“索引定向命中”</span></p>
<p>② 输出质量:<span>关键标识完整,便于画像聚合与复核</span></p>
<p>③ 复用能力:<span>脚本化流程,可重复用于后续批量检索</span></p>
<p>④ 运维稳定:<span>大表索引后台构建,降低业务影响</span></p>
<p>① 查询效率:<span>从“全库碰运气”变成“索引定向命中”,响应明显缩短</span></p>
<p>② 输出质量:<span>手机号完整输出,姓名/地址/QQ/评分可联动,便于复核</span></p>
<p>③ 复用能力:<span>脚本化流程,可重复用于后续批量检索与团队协作</span></p>
<p>④ 运维稳定:<span>大表索引后台构建,降低业务影响,可持续扩展</span></p>
</div>
<div class="glass" style="padding: 16px 20px;">
<div class="small">结论:神射手已具备“快查 + 全量字段 + 可复用”的基础能力。</div>
<div class="small">阶段结论:神射手已具备“快查 + 全量字段 + 可复用”的用户画像快速检索能力。</div>
</div>
</div>
<div class="glass" style="width: 360px; padding: 22px;">
<div class="card"><div class="t">Speed</div><div class="v">快速命中</div></div>
<div class="card"><div class="t">Completeness</div><div class="v">手机号不打码</div></div>
<div class="card"><div class="t">Standardization</div><div class="v">统一查询规范</div></div>
<div class="card"><div class="t">速度</div><div class="v">快速命中</div></div>
<div class="card"><div class="t">完整度</div><div class="v">手机号完整不打码</div></div>
<div class="card"><div class="t">标准化</div><div class="v">统一查询与输出规范</div></div>
<div class="glass" style="margin-top: 12px; padding: 14px;">
<svg viewBox="0 0 320 100" width="100%" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="320" height="100" rx="10" fill="rgba(255,255,255,0.5)"/>
<text x="160" y="28" text-anchor="middle" font-size="12" font-weight="800" fill="rgba(0,0,0,0.55)">能力示意</text>
<rect x="20" y="40" width="280" height="12" rx="6" fill="rgba(0,0,0,0.08)"/>
<rect x="20" y="40" width="240" height="12" rx="6" fill="rgba(10,132,255,0.25)"/>
<text x="310" y="51" font-size="10" font-weight="700" fill="rgba(0,0,0,0.5)"></text>
<rect x="20" y="58" width="280" height="12" rx="6" fill="rgba(0,0,0,0.08)"/>
<rect x="20" y="58" width="260" height="12" rx="6" fill="rgba(52,199,89,0.25)"/>
<text x="310" y="69" font-size="10" font-weight="700" fill="rgba(0,0,0,0.5)"></text>
<text x="20" y="88" font-size="10" font-weight="600" fill="rgba(0,0,0,0.48)">查询效率 ↑ · 画像完整度 ↑</text>
</svg>
</div>
</div>
</div>
</div>
@@ -265,17 +362,17 @@
<div class="slide" id="slide-6" style="justify-content:center; align-items:center;">
<div class="glass-strong" style="width: 980px; padding: 48px 58px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 12px;">
<span class="kicker">Next Step</span>
<span class="kicker">五、下一步优化计划</span>
<span class="pill">🚀 持续优化</span>
</div>
<h1 style="font-size: 44px; margin-bottom: 14px;">下一步三件事</h1>
<h1 style="font-size: 44px; margin-bottom: 14px;">下一阶段三项行动</h1>
<div class="list" style="margin-bottom: 16px;">
<p>① 监控索引构建进度<span>按库逐步验证命中率与耗时</span></p>
<p>增加查询模板:<span>手机号、姓名、证件号三类标准入口</span></p>
<p>画像报告化:<span>输出固定结构,直接用于业务决策</span></p>
<p>索引进度监控:<span>按库追踪构建状态与查询耗时,持续调优</span></p>
<p>② 查询模板标准化<span>手机号、姓名、证件号三类标准入口统一输出</span></p>
<p>③ 画像产品化:<span>报告输出接入业务流程,做到即查即用</span></p>
</div>
<div class="glass" style="padding: 16px 20px;">
<div class="small">目标:让“找人、识别、聚合画像”成为低门槛、可规模化的标准动作。</div>
<div class="small">最终目标:把神射手从“查询工具”升级为“稳定可运营的用户画像中台能力”,让“找人、识别、聚合画像”成为低门槛、可规模化的标准动作。</div>
</div>
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 207 KiB

View File

@@ -170,7 +170,15 @@
| refresh_token | `ur-6Wu3DdR8h4TGJErCFjTarE5lhbzk5kirpO0aiN6000SA` |
| 说明 | 飞书用户,授权时间 2026-01-29过期后需重新授权或刷新 |
**飞书任务优先命令行+API+TOKEN**:妙记/会议等流程与一键命令见 `运营中枢/参考资料/飞书任务_命令行与API优先_经验总结.md`。应用凭证APP_ID/APP_SECRET在智能纪要脚本内置或环境变量。
### 卡若AI 脚本如何取飞书 Token
| 方式 | 用途 | 位置/用法 |
|:---|:---|:---|
| **用户 access_token** | Wiki/文档/日历等需「用户身份」的接口 | ① 环境变量 `FEISHU_TOKEN="u-xxx"`(或 `t-xxx` tenant_token<br>② 本文件 § 六 上表 `access_token`<br>`02_卡人/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json`(由 `auto_log.py` 授权后写入) |
| **应用 tenant_access_token** | 开放平台接口(妙记元数据、部分管理接口) | 由 `FEISHU_APP_ID` + `FEISHU_APP_SECRET` 调用 `POST /auth/v3/tenant_access_token/internal` 获取APP 凭证在飞书管理脚本内置(如 `feishu_wiki_download_images.py`)或环境变量 `FEISHU_APP_ID` / `FEISHU_APP_SECRET` |
| **妙记导出(正文)** | 妙记文字/导出接口 | 开放平台**不提供**转写正文;需 **Cookie**(妙记列表 list 请求头)写入 `智能纪要/脚本/cookie_minutes.txt`,或 Playwright 页面内登录后导出 |
**飞书任务优先命令行+API+TOKEN**:妙记/会议等流程与一键命令见 `运营中枢/参考资料/飞书任务_命令行与API优先_经验总结.md`
### 飞书项目(玩值电竞 · 账号金融 · 存客宝)

View File

@@ -163,3 +163,4 @@
| 2026-02-25 20:42:38 | 🔄 卡若AI 同步 2026-02-25 20:42 | 更新:运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-25 20:50:40 | 🔄 卡若AI 同步 2026-02-25 20:50 | 更新:总索引与入口、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-26 00:31:13 | 🔄 卡若AI 同步 2026-02-26 00:31 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-02-26 00:43:29 | 🔄 卡若AI 同步 2026-02-26 00:43 | 更新:运营中枢工作台 | 排除 >20MB: 14 个 |

View File

@@ -166,3 +166,4 @@
| 2026-02-25 20:42:38 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 20:42 | 更新:运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-25 20:50:40 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 20:50 | 更新:总索引与入口、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-26 00:31:13 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-26 00:31 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-26 00:43:29 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-26 00:43 | 更新:运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |