diff --git a/scripts/feishu/README.md b/scripts/feishu/README.md new file mode 100644 index 0000000..aeecff8 --- /dev/null +++ b/scripts/feishu/README.md @@ -0,0 +1,325 @@ +# 飞书模型介绍页自动更新运维指南 + +本目录提供飞书知识库文档抓取工具,用于从指定飞书 Wiki 节点读取模型信息,并自动生成项目内的模型介绍页模板。 + +默认流程为: + +1. 通过飞书 OAuth 获取 `user_access_token`。 +2. 使用 token 读取配置的飞书 Wiki 文档或电子表格。 +3. 将抓取结果转换为 Markdown。 +4. 从 Markdown 中提取第一张表格。 +5. 根据表格中的 `模型ID` 列生成 `docs/模型介绍页模板.md`。 + +## 文件说明 + +| 文件 | 作用 | +| ---- | ---- | +| `main.py` | 统一入口,负责加载配置、启动授权服务、抓取文档和同步模型介绍页模板 | +| `oauth_server.py` | 本地 OAuth 回调服务,用于获取 `user_access_token` | +| `fetch_wiki_doc.py` | 调用飞书 OpenAPI,读取 Wiki 节点背后的 docx 或 sheet 内容 | +| `.env.example` | 运维配置示例 | +| `.env` | 本地实际配置文件,不应提交到仓库 | +| `requirements.txt` | Python 依赖列表 | + +## 前置条件 + +- Python 3.10+。 +- 运维账号有目标飞书 Wiki 文档的访问权限。 +- 已创建飞书应用,并拿到 `App ID` 和 `App Secret`。 +- 飞书应用的 OAuth 重定向地址已配置为本地回调地址,例如: + +```text +http://localhost:9000/ +``` + +如果修改了 `FEISHU_REDIRECT_URI` 或启动端口,需要同步修改飞书开放平台中的重定向地址。 + +## 安装依赖 + +在仓库根目录执行: + +```bash +python -m pip install -r scripts/feishu/requirements.txt +``` + +也可以先创建虚拟环境: + +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -r scripts/feishu/requirements.txt +``` + +## 配置 `.env` + +复制示例配置: + +```bash +cp scripts/feishu/.env.example scripts/feishu/.env +``` + +然后编辑 `scripts/feishu/.env`。 + +### 必填配置 + +```env +FEISHU_APP_ID=cli_xxx +FEISHU_APP_SECRET=xxx +FEISHU_REDIRECT_URI=http://localhost:9000/ +``` + +说明: + +- `FEISHU_APP_ID`:飞书应用的 App ID。 +- `FEISHU_APP_SECRET`:飞书应用的 App Secret。 +- `FEISHU_REDIRECT_URI`:OAuth 回调地址,必须与飞书开放平台配置一致。 + +### 推荐配置 + +```env +FEISHU_WIKI_URL=https://pcn43kg7pnzs.feishu.cn/wiki/DG5cwq12EiuaQGk8UbtcaQKdnif +FEISHU_DOC_OUTPUT=docs/feishu_content.txt +FEISHU_SYNC_MODEL_TEMPLATE=true +FEISHU_MODEL_TEMPLATE_PATH=docs/模型介绍页模板.md +``` + +说明: + +- `FEISHU_WIKI_URL`:要抓取的飞书 Wiki 链接,也可以填写 Wiki token。 +- `FEISHU_DOC_OUTPUT`:保存原始抓取结果的路径。相对路径按仓库根目录解析。 +- `FEISHU_SYNC_MODEL_TEMPLATE`:是否在抓取后同步生成模型介绍页模板,默认 `true`。 +- `FEISHU_MODEL_TEMPLATE_PATH`:模型介绍页模板输出路径,默认 `docs/模型介绍页模板.md`。 + +### 可选配置 + +```env +FEISHU_OPEN_BROWSER=true +FEISHU_OAUTH_STATE=hotai-test +FEISHU_SAVE_USER_ACCESS_TOKEN=true +FEISHU_USER_ACCESS_TOKEN=u-xxx +FEISHU_FETCH_DOC_AFTER_AUTH=true +``` + +说明: + +- `FEISHU_OPEN_BROWSER`:启动授权服务后是否自动打开浏览器,默认 `true`。 +- `FEISHU_OAUTH_STATE`:OAuth state 校验值,默认 `hotai-test`。 +- `FEISHU_SAVE_USER_ACCESS_TOKEN`:授权成功后是否把 token 写回 `.env`,默认 `true`。 +- `FEISHU_USER_ACCESS_TOKEN`:已有用户 token。配置后可直接执行 `fetch`,无需每次重新授权。 +- `FEISHU_FETCH_DOC_AFTER_AUTH`:授权成功后是否立即抓取文档,默认 `true`。 + +## 首次授权并自动更新 + +首次运行建议使用 `serve` 模式: + +```bash +python scripts/feishu/main.py serve +``` + +脚本会启动本地服务并打开授权入口: + +```text +http://localhost:9000/auth +``` + +授权成功后,脚本会: + +1. 用 OAuth code 换取 `user_access_token`。 +2. 如果 `FEISHU_SAVE_USER_ACCESS_TOKEN=true`,把 token 写入 `scripts/feishu/.env`。 +3. 根据 `FEISHU_WIKI_URL` 抓取飞书文档。 +4. 如果配置了 `FEISHU_DOC_OUTPUT`,保存原始抓取结果。 +5. 如果 `FEISHU_SYNC_MODEL_TEMPLATE=true`,生成或覆盖模型介绍页模板。 + +成功时控制台会输出类似信息: + +```text +文档标题: xxx +文档类型: sheet +文档 token: xxx +模型介绍页模板已更新: /path/to/HotAI/docs/模型介绍页模板.md +文档内容已写入: /path/to/HotAI/docs/feishu_content.txt +``` + +## 使用已有 token 手动更新 + +如果 `.env` 中已经有 `FEISHU_USER_ACCESS_TOKEN`,可以跳过浏览器授权,直接抓取并更新: + +```bash +python scripts/feishu/main.py fetch +``` + +也可以临时指定 Wiki: + +```bash +python scripts/feishu/main.py fetch "https://example.feishu.cn/wiki/xxxx" +``` + +或临时指定输出文件: + +```bash +python scripts/feishu/main.py fetch --output docs/feishu_content.txt +``` + +注意:只要 `FEISHU_SYNC_MODEL_TEMPLATE` 未关闭,`fetch` 成功后也会同步更新模型介绍页模板。 + +## 模型表格格式要求 + +脚本会从抓取结果中提取第一张 Markdown 表格,并按表头中的 `模型ID` 列识别模型。 + +表格必须包含 `模型ID` 列,例如: + +```markdown +| 编号 | 模型ID | 模型名称 | 供应商 | 备注 | +| ---- | ---- | ---- | ---- | ---- | +| 1 | gpt-4o | GPT-4o | OpenAI | 多模态模型 | +| 2 | claude-3-5-sonnet | Claude 3.5 Sonnet | Anthropic | 文本模型 | +``` + +生成模型介绍页时: + +- 空行会被跳过。 +- `模型ID` 为空的行会被跳过。 +- `编号` 和 `模型ID` 不会出现在每个模型的小表格属性中。 +- 每个有效模型会生成一个 `## 模型ID` 小节。 +- 页面顶部会显示有效模型总数。 + +生成结果示意: + +```markdown +# 模型介绍 + +本平台提供的模型及其属性如下(共2款): + +## gpt-4o + +| 属性 | 内容 | +| ---- | ---- | +| 模型名称 | GPT-4o | +| 供应商 | OpenAI | +| 备注 | 多模态模型 | +``` + +## 常用命令 + +启动授权服务: + +```bash +python scripts/feishu/main.py serve +``` + +启动授权服务但不自动打开浏览器: + +```bash +python scripts/feishu/main.py serve --no-open-browser +``` + +指定本地监听端口: + +```bash +python scripts/feishu/main.py serve --port 9001 +``` + +直接使用已有 token 抓取: + +```bash +python scripts/feishu/main.py fetch +``` + +直接使用命令行 token 抓取: + +```bash +python scripts/feishu/main.py fetch --user-access-token "u-xxx" +``` + +指定 @ 用户显示语言: + +```bash +python scripts/feishu/main.py fetch --lang 1 +``` + +`--lang 0` 表示默认名称,`--lang 1` 表示英文名称。 + +## 验证更新结果 + +执行更新后,检查以下文件: + +```bash +ls -l docs/feishu_content.txt docs/模型介绍页模板.md +``` + +查看模型介绍页头部和前几个模型: + +```bash +sed -n '1,80p' docs/模型介绍页模板.md +``` + +如果本次更新正确,应看到: + +- 标题为 `# 模型介绍`。 +- 总数显示为 `共N款`。 +- 每个模型都有独立的 `## 模型ID` 小节。 +- 每个小节下方都有 `属性 / 内容` 表格。 + +## 故障排查 + +### 1. 缺少必要环境变量 + +报错示例: + +```text +缺少必要环境变量: FEISHU_APP_ID +``` + +处理方式: + +- 检查 `scripts/feishu/.env` 是否存在。 +- 检查 `FEISHU_APP_ID`、`FEISHU_APP_SECRET`、`FEISHU_REDIRECT_URI` 是否已配置。 +- 确认命令是在仓库根目录执行,或脚本路径正确。 + +### 2. OAuth 回调失败 + +常见原因: + +- 飞书开放平台配置的重定向地址与 `FEISHU_REDIRECT_URI` 不一致。 +- 本地端口已被占用。 +- 浏览器访问的回调地址不是 `http://localhost:9000/`。 + +处理方式: + +- 确认飞书开放平台回调地址和 `.env` 中完全一致。 +- 如果端口冲突,改用 `--port 9001`,并同步修改 `FEISHU_REDIRECT_URI` 与飞书应用配置。 + +### 3. 抓取失败或返回权限错误 + +常见原因: + +- 飞书应用未开通读取文档、读取电子表格或 Wiki 相关权限。 +- 运维账号没有目标 Wiki 的访问权限。 +- `FEISHU_USER_ACCESS_TOKEN` 已过期或被撤销。 + +处理方式: + +- 检查飞书开放平台应用权限。 +- 用浏览器确认运维账号可以打开 `FEISHU_WIKI_URL`。 +- 重新执行 `python scripts/feishu/main.py serve` 完成授权并刷新 token。 + +### 4. 未生成模型介绍页 + +检查项: + +- `FEISHU_SYNC_MODEL_TEMPLATE` 是否为 `false`。 +- `FEISHU_MODEL_TEMPLATE_PATH` 是否可写。 +- 飞书文档第一张表是否包含 `模型ID` 表头。 +- 表格行是否存在非空 `模型ID`。 + +如果表格无法解析,脚本仍会生成模板头部,但模型总数可能为 0,并保留原始 Markdown 内容。 + +### 5. 生成内容不完整 + +脚本只会提取抓取结果中的第一张 Markdown 表格。若飞书文档中有多个表格,请将模型清单放在第一张表,或调整飞书文档结构后重新执行更新。 + +## 安全注意事项 + +- 不要提交 `scripts/feishu/.env`。 +- 不要在日志、截图或工单中泄露 `FEISHU_APP_SECRET` 和 `FEISHU_USER_ACCESS_TOKEN`。 +- `FEISHU_USER_ACCESS_TOKEN` 代表授权用户身份,建议使用专门的运维账号授权。 diff --git a/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc b/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc index 720b825..0ea36b9 100644 Binary files a/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc and b/scripts/feishu/__pycache__/oauth_server.cpython-311.pyc differ diff --git a/scripts/feishu/main.py b/scripts/feishu/main.py index 06b072b..39fc992 100644 --- a/scripts/feishu/main.py +++ b/scripts/feishu/main.py @@ -10,9 +10,10 @@ import argparse import os import subprocess -from threading import Timer -from pathlib import Path import webbrowser +from dataclasses import dataclass +from pathlib import Path +from threading import Timer from dotenv import load_dotenv @@ -24,7 +25,17 @@ REPO_ROOT = SCRIPT_DIR.parents[1] DEFAULT_WIKI_URL = "https://pcn43kg7pnzs.feishu.cn/wiki/DG5cwq12EiuaQGk8UbtcaQKdnif" DEFAULT_MODEL_TEMPLATE_PATH = "docs/模型介绍页模板.md" -MODEL_TEMPLATE_HEADER = "# 模型介绍\n\n本平台提供的模型及其属性如下:" + +MODEL_ID_COLUMN = "模型ID" +MODEL_TEMPLATE_IGNORED_COLUMNS = {"编号", MODEL_ID_COLUMN} +MODEL_TEMPLATE_HEADER = "# 模型介绍\n\n本平台提供的模型及其属性如下(共{count}款):" + + +@dataclass(frozen=True) +class ModelTable: + header: list[str] + model_id_index: int + rows: list[list[str]] def load_local_env(): @@ -40,7 +51,50 @@ def env_bool(name, default=False): return value.lower() in {"1", "true", "yes", "on"} -def is_wsl(): +def require_env(name): + """读取必填环境变量,缺失时立即给出明确错误。""" + value = os.getenv(name) + if not value: + raise RuntimeError(f"缺少必要环境变量: {name}") + return value + + +def repo_path(path): + """把相对路径解析到仓库根目录下。""" + resolved = Path(path) + if not resolved.is_absolute(): + resolved = REPO_ROOT / resolved + return resolved + + +def upsert_env_value(path, key, value): + """新增或更新 .env 中的单个 key,保留其它配置行不变。""" + lines = [] + found = False + if path.exists(): + lines = path.read_text(encoding="utf-8").splitlines() + + updated_lines = [] + for line in lines: + if line.startswith(f"{key}="): + updated_lines.append(f"{key}={value}") + found = True + else: + updated_lines.append(line) + + if not found: + updated_lines.append(f"{key}={value}") + + path.write_text("\n".join(updated_lines) + "\n", encoding="utf-8") + + +def save_user_access_token(access_token): + """把 OAuth 新获取的 user_access_token 写回 .env,便于后续 fetch 复用。""" + upsert_env_value(SCRIPT_DIR / ".env", "FEISHU_USER_ACCESS_TOKEN", access_token) + print("user_access_token 已写入 scripts/feishu/.env") + + +def running_in_wsl(): """检测当前进程是否运行在 WSL 中,用于选择合适的浏览器打开方式。""" try: os_release = Path("/proc/sys/kernel/osrelease").read_text(encoding="utf-8") @@ -49,9 +103,9 @@ def is_wsl(): return "microsoft" in os_release.lower() or "wsl" in os_release.lower() -def open_browser(url): +def open_url_in_browser(url): """打开授权入口;WSL 下优先调用 Windows 默认浏览器。""" - if is_wsl(): + if running_in_wsl(): try: subprocess.Popen( ["cmd.exe", "/c", "start", "", url], @@ -65,42 +119,76 @@ def open_browser(url): return webbrowser.open(url) -def require_env(name): - """读取必填环境变量,缺失时立即给出明确错误。""" - value = os.getenv(name) - if not value: - raise RuntimeError(f"缺少必要环境变量: {name}") - return value - - -def write_content(output_path, content): - """写入抓取结果;相对路径统一按仓库根目录解析。""" - path = Path(output_path) - if not path.is_absolute(): - path = REPO_ROOT / path - +def write_fetched_content(output_path, content): + """写入抓取结果,并按配置同步模型介绍页模板。""" + path = repo_path(output_path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") - template_path = sync_model_template(content) + + template_path = sync_model_template_from_content(content) if template_path: print(f"模型介绍页模板已更新: {template_path}") return path -def write_model_template(template_path, markdown_table): - """把 Markdown 表格写入模型介绍模板,同时固定保留模板前言。""" - path = Path(template_path) - if not path.is_absolute(): - path = REPO_ROOT / path +def sync_model_template_from_content(content): + """按配置把本次抓取结果同步写入模型介绍页模板。""" + if not env_bool("FEISHU_SYNC_MODEL_TEMPLATE", True): + return None + + template_path = os.getenv("FEISHU_MODEL_TEMPLATE_PATH", DEFAULT_MODEL_TEMPLATE_PATH) + markdown_table = extract_first_markdown_table(content) + return write_model_template_file(template_path, markdown_table) + +def write_model_template_file(template_path, markdown_table): + """把 Markdown 表格渲染为模型介绍模板文件。""" + path = repo_path(template_path) path.parent.mkdir(parents=True, exist_ok=True) - path.write_text( - f"{MODEL_TEMPLATE_HEADER}\n\n{markdown_table.strip()}\n", - encoding="utf-8", - ) + path.write_text(render_model_template(markdown_table), encoding="utf-8") return path +def render_model_template(markdown_table): + """渲染完整模型介绍页,包括模型总数和分模型小表格。""" + model_table = parse_model_table(markdown_table) + if not model_table: + header = MODEL_TEMPLATE_HEADER.format(count=0) + return f"{header}\n\n{markdown_table.strip()}\n" + + sections = [] + model_count = 0 + for row in model_table.rows: + if not any(cell.strip() for cell in row): + continue + if model_table.model_id_index >= len(row): + continue + + model_id = row[model_table.model_id_index].strip() + if not model_id: + continue + + model_count += 1 + lines = [ + f"## {model_id}", + "", + "| 属性 | 内容 |", + "| ---- | ---- |", + ] + for index, name in enumerate(model_table.header): + name = name.strip() + if not name or name in MODEL_TEMPLATE_IGNORED_COLUMNS: + continue + value = row[index].strip() if index < len(row) else "" + lines.append(f"| {name} | {value} |") + + sections.append("\n".join(lines)) + + header = MODEL_TEMPLATE_HEADER.format(count=model_count) + body = "\n\n".join(sections) if sections else markdown_table.strip() + return f"{header}\n\n{body}\n" + + def extract_first_markdown_table(content): """从抓取结果中提取第一段 Markdown 表格,忽略工作表标题等说明文本。""" table_lines = [] @@ -115,64 +203,89 @@ def extract_first_markdown_table(content): return "\n".join(table_lines) if table_lines else content -def sync_model_template(content): - """按配置把本次抓取结果同步写入模型介绍页模板。""" - if not env_bool("FEISHU_SYNC_MODEL_TEMPLATE", True): +def parse_model_table(markdown_table): + """解析模型 Markdown 表格,返回表头、`模型ID` 列位置和数据行。""" + rows = [ + split_markdown_row(line) + for line in markdown_table.splitlines() + if line.startswith("|") + ] + if len(rows) < 2: return None - template_path = os.getenv("FEISHU_MODEL_TEMPLATE_PATH", DEFAULT_MODEL_TEMPLATE_PATH) - return write_model_template(template_path, extract_first_markdown_table(content)) - - -def upsert_env_value(path, key, value): - """新增或更新 .env 中的单个 key,保留其它配置行不变。""" - lines = [] - found = False - if path.exists(): - lines = path.read_text(encoding="utf-8").splitlines() + header = rows[0] + try: + model_id_index = header.index(MODEL_ID_COLUMN) + except ValueError: + return None - updated_lines = [] - for line in lines: - if line.startswith(f"{key}="): - updated_lines.append(f"{key}={value}") - found = True + body_rows = rows[1:] + if len(rows) > 1 and is_markdown_table_separator(rows[1]): + body_rows = rows[2:] + return ModelTable(header=header, model_id_index=model_id_index, rows=body_rows) + + +def split_markdown_row(line): + """按 Markdown 表格分隔符拆分一行,保留单元格内已转义的竖线。""" + text = line.strip() + if text.startswith("|"): + text = text[1:] + if text.endswith("|"): + text = text[:-1] + + cells = [] + cell = [] + backslash_count = 0 # 当前字符前连续反斜杠数量,用于判断竖线是否被转义。 + for char in text: + if char == "|" and backslash_count % 2 == 0: # 偶数个反斜杠表示当前竖线未被转义。 + cells.append("".join(cell).strip()) + cell = [] + backslash_count = 0 + continue + + cell.append(char) + if char == "\\": + backslash_count += 1 else: - updated_lines.append(line) + backslash_count = 0 - if not found: - updated_lines.append(f"{key}={value}") + cells.append("".join(cell).strip()) + return cells - path.write_text("\n".join(updated_lines) + "\n", encoding="utf-8") +def is_markdown_table_separator(cells): + """判断是否为 Markdown 表格中的分隔行。""" + if not cells: + return False -def save_user_access_token(access_token): - """把 OAuth 新获取的 user_access_token 写回 .env,便于后续 fetch 复用。""" - upsert_env_value(SCRIPT_DIR / ".env", "FEISHU_USER_ACCESS_TOKEN", access_token) - print("user_access_token 已写入 scripts/feishu/.env") + for cell in cells: + normalized = cell.replace(" ", "") + dashes = normalized.strip(":") + if len(dashes) < 3 or set(dashes) != {"-"}: + return False + return True -def build_config(args): +def build_oauth_server_config(args): """汇总命令行参数和环境变量,生成传给 OAuth 服务的配置对象。""" return { "app_id": require_env("FEISHU_APP_ID"), "app_secret": require_env("FEISHU_APP_SECRET"), "redirect_uri": require_env("FEISHU_REDIRECT_URI"), "oauth_state": os.getenv("FEISHU_OAUTH_STATE", "hotai-test"), - "save_user_access_token": save_user_access_token, + "save_user_access_token": save_user_access_token if env_bool("FEISHU_SAVE_USER_ACCESS_TOKEN", True) else None, "wiki_url": args.wiki or os.getenv("FEISHU_WIKI_URL", DEFAULT_WIKI_URL), "fetch_doc_after_auth": env_bool("FEISHU_FETCH_DOC_AFTER_AUTH", True), - "write_content": write_content, + "write_content": write_fetched_content, "doc_output": args.output or os.getenv("FEISHU_DOC_OUTPUT"), - "sync_model_template": sync_model_template, - "lang": args.lang - if env_bool("FEISHU_SAVE_USER_ACCESS_TOKEN", True) - else None, + "sync_model_template": sync_model_template_from_content, + "lang": args.lang, } def run_server(args): """启动本地 OAuth 服务,并按配置自动打开授权入口。""" - config = build_config(args) + config = build_oauth_server_config(args) app = create_app(config) auth_url = build_auth_url( @@ -190,7 +303,7 @@ def run_server(args): print(f"将自动打开浏览器访问: {local_auth_url}") print(f"若自动打开失败,可手动打开本地入口: {local_auth_url}") print(f"也可直接打开飞书授权链接: {auth_url}") - Timer(1.0, open_browser, args=(local_auth_url,)).start() + Timer(1.0, open_url_in_browser, args=(local_auth_url,)).start() else: print(f"请手动打开本地入口: {local_auth_url}") print(f"或直接打开飞书授权链接: {auth_url}") @@ -216,54 +329,60 @@ def run_fetch(args): print(f"obj_token: {node['obj_token']}") if output: - output_path = write_content(output, content) + output_path = write_fetched_content(output, content) print(f"内容已写入: {output_path}") else: - template_path = sync_model_template(content) + template_path = sync_model_template_from_content(content) if template_path: print(f"模型介绍页模板已更新: {template_path}") print() print(content) -def parse_args(): - """解析 CLI 子命令;未指定子命令时默认进入 serve 模式。""" - parser = argparse.ArgumentParser(description="飞书 OAuth 授权与知识库文档抓取工具。") - subparsers = parser.add_subparsers(dest="command") +def add_wiki_fetch_arguments(parser, *, positional_wiki): + """给 serve/fetch 子命令添加共用抓取参数。""" + if positional_wiki: + parser.add_argument( + "wiki", + nargs="?", + help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", + ) + else: + parser.add_argument( + "--wiki", + help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", + ) - server_parser = subparsers.add_parser( - "serve", - help="启动本地 OAuth 授权服务,授权成功后可自动抓取文档。", - ) - server_parser.add_argument( - "--wiki", - help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", - ) - server_parser.add_argument( + parser.add_argument( "--output", help="文档内容输出路径,默认读取 FEISHU_DOC_OUTPUT。", ) - server_parser.add_argument( + parser.add_argument( "--lang", type=int, default=0, help="@用户 的显示语言:0 为默认名称,1 为英文名称。", ) + + +def parse_args(): + """解析 CLI 子命令;未指定子命令时默认进入 serve 模式。""" + parser = argparse.ArgumentParser(description="飞书 OAuth 授权与知识库文档抓取工具。") + subparsers = parser.add_subparsers(dest="command") + + server_parser = subparsers.add_parser( + "serve", + help="启动本地 OAuth 授权服务,授权成功后可自动抓取文档。", + ) + add_wiki_fetch_arguments(server_parser, positional_wiki=False) server_parser.add_argument("--host", default="localhost", help="本地服务监听地址。") server_parser.add_argument("--port", type=int, default=9000, help="本地服务端口。") server_parser.add_argument("--debug", action="store_true", help="启用 Flask debug 模式。") server_parser.add_argument( "--open-browser", - dest="open_browser", - action="store_true", + action=argparse.BooleanOptionalAction, default=None, - help="启动服务后自动打开授权入口。", - ) - server_parser.add_argument( - "--no-open-browser", - dest="open_browser", - action="store_false", - help="启动服务后不自动打开授权入口。", + help="启动服务后是否自动打开授权入口。", ) server_parser.set_defaults(func=run_server) @@ -271,25 +390,11 @@ def parse_args(): "fetch", help="直接使用已有 user_access_token 抓取文档。", ) - fetch_parser.add_argument( - "wiki", - nargs="?", - help="飞书知识库 URL 或 wiki token,默认读取 FEISHU_WIKI_URL。", - ) + add_wiki_fetch_arguments(fetch_parser, positional_wiki=True) fetch_parser.add_argument( "--user-access-token", help="飞书 user_access_token,默认读取 FEISHU_USER_ACCESS_TOKEN。", ) - fetch_parser.add_argument( - "--output", - help="文档内容输出路径,默认读取 FEISHU_DOC_OUTPUT。", - ) - fetch_parser.add_argument( - "--lang", - type=int, - default=0, - help="@用户 的显示语言:0 为默认名称,1 为英文名称。", - ) fetch_parser.set_defaults(func=run_fetch) args = parser.parse_args() diff --git a/scripts/feishu/oauth_server.py b/scripts/feishu/oauth_server.py index b39de80..5dd6f06 100644 --- a/scripts/feishu/oauth_server.py +++ b/scripts/feishu/oauth_server.py @@ -93,7 +93,7 @@ def auth(): @app.route("/") def callback(): - # 飞书 OAuth 回调会携带 code 和 state,state 用于防止串改请求。 + # 飞书 OAuth 回调会携带 code 和 state,state 用于防止篡改请求。 code = request.args.get("code") state = request.args.get("state")