🔄 卡若AI 同步 2026-02-26 16:41 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个
84
01_卡资(金)/金仓_存储备份/微信管理/脚本/post_moments.sh
Executable 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 重新校准。"
|
||||
42
01_卡资(金)/金仓_存储备份/微信管理/脚本/朋友圈命令行发布说明.md
Normal 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`
|
||||
67
02_卡人(水)/水桥_平台对接/Soul创业实验/SKILL.md
Normal 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文章上传 合并至此 |
|
||||
56
02_卡人(水)/水桥_平台对接/Soul创业实验/上传/README.md
Normal 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`
|
||||
90
02_卡人(水)/水桥_平台对接/Soul创业实验/写作/写作规范.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 《一场soul的创业实验》第9章写作规范
|
||||
|
||||
> 本规范归属 **Soul创业实验** Skill → 子类「写作」。写第9章单场文章时以本文件 + 主 SKILL 内写作要点为准。
|
||||
|
||||
---
|
||||
|
||||
## 一、人称与专有名词(强制)
|
||||
|
||||
| 项目 | 规范 |
|
||||
|:---|:---|
|
||||
| **「我」** | **整篇文章最多出现一次**。建议不用或仅一处;用「这边」「直接」「就」等替代。成稿后全文搜索「我」,超过 1 处必须改写。 |
|
||||
| **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 |
|
||||
|
||||
---
|
||||
|
||||
## 二、基础要求
|
||||
|
||||
| 项目 | 规范 |
|
||||
|:---|:---|
|
||||
| 字数 | 每小节 2000~5000 字 |
|
||||
| 来源 | 以真实聊天内容为基础,不改原意 |
|
||||
| 数据 | 以聊天内提到的数值为主,不编造(场观、人数、时长、营收等) |
|
||||
| 配图 | 不需要 |
|
||||
|
||||
---
|
||||
|
||||
## 三、主题与结构
|
||||
|
||||
- **一个观点**:每节只表达一个核心观点,主题清晰
|
||||
- **有数据**:用具体数值验证(场观、人数、时长、营收等)
|
||||
- **与目录一致**:小节名称、内容与全书结构、其他小节风格统一
|
||||
- **时间**:以文档/聊天记录时间为准
|
||||
|
||||
---
|
||||
|
||||
## 四、开篇设计
|
||||
|
||||
- 一句有冲突、世俗的金句(不抒情、不空泛)
|
||||
- 以具体事件、场景、对话切入
|
||||
- 日期 + 时间 + 关键数据(第 X 场,时长,进房人数)
|
||||
|
||||
---
|
||||
|
||||
## 五、内容结构
|
||||
|
||||
- **推进方式**:时间线或事件线,逻辑清晰(如「有人问 → 回答」「3 号问 → …」)
|
||||
- **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题
|
||||
- **穿插**:细节、对话、观点分析
|
||||
- **多用对话**:增强真实感(「X 号问」「有人问」「直接回答」「这边说」等)
|
||||
|
||||
---
|
||||
|
||||
## 六、写作技巧
|
||||
|
||||
- 第一人称叙述时少用「我」,见第一节
|
||||
- 短句,大白话,口语化
|
||||
- 每句话后空一行,段间空行
|
||||
- 善用对比、反转;适当自嘲或幽默
|
||||
|
||||
---
|
||||
|
||||
## 七、结尾
|
||||
|
||||
- 总结观点,升华主题
|
||||
- 留有余味或行动号召
|
||||
|
||||
---
|
||||
|
||||
## 八、联系管理 / 做切片 / 做副业(隐晦植入)
|
||||
|
||||
在文中偶尔、自然地暗示「联系管理」「做切片」「做副业」,约 2~4 处,融入叙述,不单独成段喊话。示例:
|
||||
- 想做切片、做副业的,可以私聊管理,有电脑、会剪辑的都能问问。
|
||||
- 想参与副业、做切片的,联系管理就行。
|
||||
- 想做切片的可以找管理了解一下流程。
|
||||
- 有事想对接、想做副业或切片的,找管理就行。
|
||||
|
||||
---
|
||||
|
||||
## 九、项目分享的隐晦提问(融入对话)
|
||||
|
||||
若写派对内「项目分享」类内容,可把以下维度融入对话,不列清单:目标人群?生意逻辑?核心是什么?进群可联系管理?每月营收多少?
|
||||
|
||||
---
|
||||
|
||||
## 十、格式规范(统一)
|
||||
|
||||
- 每句话后空一行
|
||||
- 段间空行
|
||||
- 对话、细节、观点分行,避免大段堆砌
|
||||
- 用 `---` 做段落分隔(与全书一致)
|
||||
@@ -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` | 登录获取 Token,Body: `{"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`。
|
||||
|
||||
5
02_卡人(水)/水桥_平台对接/Soul文章写作/SKILL.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
name: Soul文章写作
|
||||
redirect: 02_卡人(水)/水桥_平台对接/Soul创业实验
|
||||
---
|
||||
已合并至 **Soul创业实验** → 子类「写作」。见 `Soul创业实验/写作/写作规范.md`。
|
||||
151
02_卡人(水)/水桥_平台对接/智能纪要/脚本/feishu_minutes_one_url.py
Normal 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())
|
||||
BIN
02_卡人(水)/水桥_平台对接/飞书管理/参考资料/昨日今日完成度对比.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
79
02_卡人(水)/水桥_平台对接/飞书管理/参考资料/飞书docx插入图片_API说明.md
Normal 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
|
||||
@@ -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"
|
||||
}
|
||||
438
02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_download_images.py
Normal 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
|
||||
|
||||
依赖:requests(pip 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()
|
||||
BIN
02_卡人(水)/水桥_平台对接/飞书管理/脚本/temp_images/昨日今日完成度对比.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
268
02_卡人(水)/水桥_平台对接/飞书管理/脚本/write_today_log_with_image.py
Normal 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()
|
||||
33
02_卡人(水)/水桥_平台对接/飞书管理/脚本/飞书Wiki图片下载说明.md
Normal 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>`。
|
||||
@@ -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>
|
||||
|
||||
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 251 KiB |
|
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 207 KiB |
@@ -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`。
|
||||
|
||||
### 飞书项目(玩值电竞 · 账号金融 · 存客宝)
|
||||
|
||||
|
||||
@@ -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 个 |
|
||||
|
||||
@@ -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) |
|
||||
|
||||