jianzhihuixiang/skills/tencent-docs/setup.sh
小虾米 f8894dd3f3 fix: 修复上一章/下一章导航对小数章节(如107.5)的支持
- updateNavButtons: 改用数组索引查找,而非简单的+1/-1
- updateSidebarHighlight: 使用parseFloat比较章节ID
- updateMobileTOCHighlight: 使用parseFloat比较章节ID
- 更新版本号强制刷新缓存
2026-03-27 17:42:41 +08:00

614 lines
23 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#
# Setup script for 腾讯文档 MCP Skill (内部 OpenClaw 版本) 一体化配置与授权脚本
#
# 功能:
# 1. 检查 mcporter 是否已配置 tencent-docs含 Authorization 可用)
# 2. 未配置或 Token 失效时,展示授权链接同时后台异步轮询 Token
# 3. 无需用户回复"已授权"Token 获取后自动写入 mcporter 并继续
# 4. 对超时、过期、错误等场景给出友好提示
#
# 用法(供 AI Agent 调用):
# 第一步:检查状态(立即返回,不阻塞)
# bash ./setup.sh tdoc_check_and_start_auth
# 输出:
# READY → 服务已就绪,直接执行用户任务,无需第二步
# AUTH_REQUIRED:<url> → 立即向用户展示授权链接,然后执行第二步
# ERROR:* → 告知用户对应错误
#
# 第二步:等待授权完成(仅 AUTH_REQUIRED 时执行,阻塞最多约 135s
# bash ./setup.sh tdoc_wait_auth
# 输出:
# TOKEN_READY:* → 授权成功,继续执行用户任务
# AUTH_TIMEOUT → 告知用户:授权超时,请重新发起请求
# ERROR:expired → 告知用户:授权码已过期,请重新发起请求
# ERROR:token_invalid → 告知用户Token 已失效,请重新授权
# ERROR:* → 告知用户对应错误
#
# 直接执行(排查问题):
# bash ./setup.sh
#
# ── 全局配置 ──────────────────────────────────────────────────────────────────
_TDOC_API_BASE="${TDOC_API_BASE_URL:-https://docs.qq.com}"
_TDOC_AUTH_BASE="${TDOC_AUTH_BASE_URL:-https://docs.qq.com/scenario/open-claw.html}"
_TDOC_MCP_URL="https://docs.qq.com/openapi/mcp"
_TDOC_SERVICE_NAME="tencent-docs"
# 轮询参数:每 10s 一次,最多 12 次(约 120s
_TDOC_POLL_INTERVAL=10
_TDOC_POLL_MAX=12
# 临时文件
_TDOC_CODE_FILE="${TMPDIR:-/tmp}/.tdoc_auth_code"
_TDOC_TOKEN_FILE="${TMPDIR:-/tmp}/.tdoc_auth_token"
_TDOC_ERR_FILE="${TMPDIR:-/tmp}/.tdoc_auth_token.err"
_TDOC_PID_FILE="${TMPDIR:-/tmp}/.tdoc_auth_pid"
_TDOC_URL_FILE="${TMPDIR:-/tmp}/.tdoc_auth_url"
# mcporter 配置文件路径(动态获取,见 _tdoc_get_mcporter_cfg_path
_TDOC_MCPORTER_CFG=""
# ── 清理函数 ──────────────────────────────────────────────────────────────────
_tdoc_cleanup() {
rm -f "$_TDOC_CODE_FILE" "$_TDOC_TOKEN_FILE" "$_TDOC_ERR_FILE" "$_TDOC_PID_FILE" "$_TDOC_URL_FILE"
}
# ── 动态获取 mcporter 配置文件路径 ────────────────────────────────────────────
# 策略:
# 1. 先注册一个无 token 的临时占位服务(幂等,不影响已有配置)
# 2. 执行 mcporter config list从输出中解析该服务的 Source 路径
# 3. 提取配置文件路径并缓存到 _TDOC_MCPORTER_CFG
# 输出:配置文件绝对路径(失败时输出空字符串)
_tdoc_get_mcporter_cfg_path() {
# 若已缓存则直接返回
if [[ -n "$_TDOC_MCPORTER_CFG" && -f "$_TDOC_MCPORTER_CFG" ]]; then
echo "$_TDOC_MCPORTER_CFG"
return 0
fi
# 1. 注册一个无 token 的占位服务(--scope home 写入 home 级配置)
# 若服务已存在config add 会更新;此处仅用于触发配置文件创建
mcporter config add "$_TDOC_SERVICE_NAME" "$_TDOC_MCP_URL" \
--transport http \
--scope home \
>/dev/null 2>&1 || true
# 2. 从 mcporter config list 输出中解析配置路径
# 优先匹配该服务的 Source 行,例如:
# Source: local (/root/.mcporter/mcporter.json)
# 其次匹配 "System config:" 行作为兜底
local cfg_path=""
# 尝试从 Source 行提取
cfg_path=$(mcporter config list 2>/dev/null \
| grep -A 3 "^${_TDOC_SERVICE_NAME}" \
| grep "Source:" \
| sed 's/.*(\(.*\))/\1/' \
| head -1)
# 兜底:从 "System config:" 行提取
if [[ -z "$cfg_path" ]]; then
cfg_path=$(mcporter config list 2>/dev/null \
| grep "^System config:" \
| sed 's/System config:[[:space:]]*//' \
| sed 's/[[:space:]]*(missing).*//' \
| head -1)
fi
# 再兜底:从 "Project config:" 行提取(排除 missing
if [[ -z "$cfg_path" ]]; then
cfg_path=$(mcporter config list 2>/dev/null \
| grep "^Project config:" \
| grep -v "(missing)" \
| sed 's/Project config:[[:space:]]*//' \
| head -1)
fi
if [[ -n "$cfg_path" ]]; then
_TDOC_MCPORTER_CFG="$cfg_path"
echo "$cfg_path"
return 0
fi
# 最后兜底
echo "${HOME}/.mcporter/mcporter.json"
return 0
}
# ── 检查 mcporter 是否已安装 ──────────────────────────────────────────────────
_tdoc_check_mcporter() {
if ! command -v mcporter &> /dev/null; then
echo "⚠️ 未找到 mcporter正在安装..."
if command -v npm &>/dev/null; then
npm install -g mcporter 2>&1 | tail -3
echo "✅ mcporter 安装完成"
else
echo "ERROR:no_npm"
return 1
fi
fi
return 0
}
# 从 mcporter 配置文件读取当前 Authorization Token
# 输出token 字符串(空则表示未配置)
_tdoc_get_token_from_config() {
# 动态获取配置路径
local cfg
cfg=$(_tdoc_get_mcporter_cfg_path)
if [[ -z "$cfg" || ! -f "$cfg" ]]; then
echo ""
return
fi
# 纯 Shell 实现:从 JSON 中提取 Authorization 字段
# 匹配 "tencent-docs" 服务块内的 "Authorization": "..." 值
local token
token=$(grep -A 20 "\"$_TDOC_SERVICE_NAME\"" "$cfg" \
| grep '"Authorization"' \
| head -1 \
| sed 's/.*"Authorization"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
echo "${token:-}"
}
# ── 将 Token 写入 mcporter 配置 ───────────────────────────────────────────────
# 用法_tdoc_save_token <token>
_tdoc_save_token() {
# 添加 MCP 配置
echo "🔧 配置 mcporter..."
local token="$1"
[[ -z "$token" ]] && return 1
# 使用传入的 token 写入 mcporter 配置
mcporter config add "$_TDOC_SERVICE_NAME" "$_TDOC_MCP_URL" \
--header "Authorization=$token" \
--transport http \
--scope home
echo ""
echo "✅ 配置完成!"
echo ""
echo "🧪 验证配置..."
if mcporter list 2>&1 | grep -q "$_TDOC_SERVICE_NAME"; then
echo "✅ 配置验证成功!"
echo ""
mcporter list | grep -A 1 "$_TDOC_SERVICE_NAME" || true
else
echo "⚠️ 配置验证失败,请检查网络或 Token 是否有效"
echo ""
echo "如有问题,请访问 ${_TDOC_API_BASE}/scenario/open-claw.html?nlc=1 获取 Token"
fi
echo ""
echo "─────────────────────────────────────"
echo "🎉 设置完成!"
echo ""
echo "📖 使用方法:"
echo " mcporter call${_TDOC_SERVICE_NAME}.create_smartcanvas_by_markdown"
echo ""
echo "🏠 腾讯文档主页:${_TDOC_API_BASE}/home"
echo ""
echo "📖 更多信息请查看 SKILL.md"
echo ""
return 0
}
# ── 检查 tencent-docs 服务状态 ────────────────────────────────────────────────
# 返回值:
# 0 = 服务正常可用(有 Tokenlist 成功)
# 1 = 服务未注册
# 2 = Token 为空或未配置
_tdoc_check_service() {
# 检查 mcporter 配置中是否有 tencent-docs
if ! mcporter list 2>/dev/null | grep -q "$_TDOC_SERVICE_NAME"; then
return 1
fi
# 检查 Token 是否配置
local token
token=$(_tdoc_get_token_from_config)
if [[ -z "$token" ]]; then
return 2
fi
return 0
}
# ── 生成授权链接 ──────────────────────────────────────────────────────────────
# 输出auth_url 字符串
_tdoc_generate_auth_url() {
local code
code=$(openssl rand -hex 8 2>/dev/null || \
cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' 2>/dev/null | head -c 16 || \
date +%s%N 2>/dev/null | sha256sum 2>/dev/null | head -c 16 || \
echo "$(date +%s)$$")
echo "$code" > "$_TDOC_CODE_FILE"
echo "${_TDOC_AUTH_BASE}?nlc=1&authType=1&code=${code}&mcp_source=desktop"
}
# ── 后台异步轮询 Token ────────────────────────────────────────────────────────
# 调用前必须已写入 $_TDOC_CODE_FILE
# 成功:写入 $_TDOC_TOKEN_FILE
# 失败:写入 $_TDOC_ERR_FILE内容expired / timeout / err_<code>
_tdoc_start_bg_poll() {
rm -f "$_TDOC_TOKEN_FILE" "$_TDOC_ERR_FILE"
local code_file="$_TDOC_CODE_FILE"
local token_file="$_TDOC_TOKEN_FILE"
local err_file="$_TDOC_ERR_FILE"
local pid_file="$_TDOC_PID_FILE"
local api_base="$_TDOC_API_BASE"
local poll_interval="$_TDOC_POLL_INTERVAL"
local poll_max="$_TDOC_POLL_MAX"
(
# 等待 code 文件就绪(最多 10s
local waited=0
while [[ ! -f "$code_file" && $waited -lt 10 ]]; do
sleep 1; waited=$((waited + 1))
done
if [[ ! -f "$code_file" ]]; then
echo "code_timeout" > "$err_file"
exit 1
fi
local code
code=$(cat "$code_file")
local url="${api_base}/oauth/v2/mcp/token/get?code=${code}"
for ((i=1; i<=poll_max; i++)); do
local response
response=$(curl -s -f -L "$url" 2>/dev/null) || {
sleep "$poll_interval"
continue
}
# 提取 token
local token
token=$(echo "$response" | jq -r '.data.token // empty' 2>/dev/null || echo "")
if [[ -n "$token" ]]; then
echo "$token" > "$token_file"
rm -f "$code_file" "$pid_file"
exit 0
fi
# 提取错误码
local ret
ret=$(echo "$response" | jq -r '.ret // empty' 2>/dev/null || echo "")
# 11510 = 用户还未授权,继续等待
if [[ "$ret" == "11510" ]]; then
sleep "$poll_interval"
continue
fi
local expired
expired=$(echo "$response" | jq -r '.data.expired // empty' 2>/dev/null || echo "")
if [[ "$expired" == "true" ]]; then
echo "expired" > "$err_file"
rm -f "$code_file" "$pid_file"
exit 1
fi
# 其他错误场景区分
case "$ret" in
"14151"|"14152")
# 授权码已过期或失效
echo "expired" > "$err_file"
rm -f "$code_file" "$pid_file"
exit 1
;;
"400006")
# Token 鉴权失败
echo "err_400006" > "$err_file"
rm -f "$code_file" "$pid_file"
exit 1
;;
*)
# 其他错误:记录但继续等待,给用户更多时间
sleep "$poll_interval"
continue
;;
esac
done
# 轮询超时
echo "timeout" > "$err_file"
rm -f "$code_file" "$pid_file"
) &
local bg_pid=$!
echo "$bg_pid" > "$_TDOC_PID_FILE"
echo "BG_POLL_STARTED:$bg_pid"
}
# ── 前台等待 Token 就绪 ────────────────────────────────────────
# 用法_tdoc_wait_token [max_seconds]
# 输出:
# TOKEN_READY:<token> 授权成功
# AUTH_TIMEOUT 超时
# ERROR:<reason> 错误
_tdoc_wait_token() {
local max_wait=$((_TDOC_POLL_MAX * _TDOC_POLL_INTERVAL + 15))
local elapsed=0
while [[ $elapsed -lt $max_wait ]]; do
# Token 就绪
if [[ -f "$_TDOC_TOKEN_FILE" ]]; then
local token
token=$(cat "$_TDOC_TOKEN_FILE")
if [[ -n "$token" ]]; then
echo "TOKEN_READY"
return 0
fi
fi
# 出现错误
if [[ -f "$_TDOC_ERR_FILE" ]]; then
local err
err=$(cat "$_TDOC_ERR_FILE")
echo "ERROR:$err"
return 1
fi
sleep 1
elapsed=$((elapsed + 1))
done
echo "AUTH_TIMEOUT"
return 2
}
# ── 执行授权流程(第一阶段):生成链接并启动后台轮询 ─────────────────────────
# 输出:
# AUTH_REQUIRED:<url> 立即输出到 stdout同时写入 $_TDOC_URL_FILE
# 调用方拿到 AUTH_REQUIRED 后应立即展示给用户,然后调用 _tdoc_wait_auth_result
_tdoc_do_auth_start() {
_tdoc_cleanup
# 生成授权链接(同时写入 code 文件)
local auth_url
auth_url=$(_tdoc_generate_auth_url)
# 将 URL 写入文件,供后续阶段读取
echo "$auth_url" > "$_TDOC_URL_FILE"
# ★ 立即启动后台轮询(与用户操作并行进行)
_tdoc_start_bg_poll > /dev/null
# ★ 立即输出授权链接(调用方可立即展示给用户)
echo "AUTH_REQUIRED:$auth_url"
return 0
}
# ── 执行授权流程(第二阶段):等待 Token 并写入配置 ──────────────────────────
# 调用方在展示授权链接后调用此函数,等待用户完成授权
# 输出:
# TOKEN_READY:<token> 授权成功
# AUTH_TIMEOUT 超时
# ERROR:* 错误
_tdoc_wait_auth_result() {
local result
result=$(_tdoc_wait_token)
case "$result" in
TOKEN_READY)
local token=$(cat "$_TDOC_TOKEN_FILE")
if _tdoc_save_token "$token"; then
_tdoc_cleanup
echo "TOKEN_READY:$token"
return 0
else
_tdoc_cleanup
echo "ERROR:save_token_failed"
return 1
fi
;;
AUTH_TIMEOUT)
_tdoc_cleanup
echo "AUTH_TIMEOUT"
return 2
;;
ERROR:expired)
_tdoc_cleanup
echo "ERROR:expired - 授权码已过期,请重新发起请求"
return 1
;;
ERROR:err_400006)
_tdoc_cleanup
echo "ERROR:token_invalid - Token 鉴权失败,请重新授权"
return 1
;;
ERROR:*)
_tdoc_cleanup
echo "ERROR:unknown - 授权异常,请联系腾讯文档客服"
return 1
;;
esac
}
# ── 主入口函数 A检查状态 / 生成授权链接(立即返回,不阻塞)────────────────
#
# AI Agent 第一步调用此函数,命令执行完毕后立即拿到输出:
# READY 服务已就绪,直接执行用户任务,无需第二步
# AUTH_REQUIRED:<url> 需要授权:立即展示链接给用户,然后执行第二步
# ERROR:* 错误信息
#
tdoc_check_and_start_auth() {
_tdoc_check_mcporter || {
echo "ERROR:mcporter_not_found - 请先安装 Node.js 和 npm 后重试"
return 1
}
_tdoc_check_service
local status=$?
case $status in
0)
echo "READY"
return 0
;;
1|2)
_tdoc_do_auth_start
return 0
;;
esac
}
# ── 主入口函数 B等待授权完成阻塞最多约 135s────────────────────────────
#
# AI Agent 在展示授权链接后调用此函数,等待用户完成授权:
# TOKEN_READY:<token> 授权成功Token 已写入配置,直接执行用户任务
# AUTH_TIMEOUT 超时,告知用户重新发起请求
# ERROR:* 错误信息
#
tdoc_wait_auth() {
_tdoc_wait_auth_result
return $?
}
# ── 直接执行时的交互式安装流程 ───────────────────────────────────────────────
_tdoc_interactive_setup() {
echo ""
echo "╔══════════════════════════════════════════════╗"
echo "║ 腾讯文档 MCP Skill 配置向导 ║"
echo "╚══════════════════════════════════════════════╝"
echo ""
# 检查 mcporter
echo "🔍 检查 mcporter..."
if ! _tdoc_check_mcporter; then
echo "❌ mcporter 安装失败,请先安装 Node.js (https://nodejs.org) 后重试"
exit 1
fi
echo "✅ mcporter 已就绪"
echo ""
# 检查服务状态
echo "🔍 检查 tencent-docs 服务配置..."
_tdoc_check_service
local status=$?
case $status in
0)
echo "✅ tencent-docs 服务已配置且运行正常!"
echo ""
echo "🎉 无需重新配置,您可以直接使用腾讯文档功能。"
echo ""
echo "📖 使用示例:"
echo " mcporter call tencent-docs manage.recent_online_file --args '{\"num\":10}'"
return 0
;;
1|2)
echo "⚠️ Token 未配置,需要授权..."
;;
esac
echo ""
echo "🔐 需要完成腾讯文档授权"
echo ""
# 清理旧状态
_tdoc_cleanup
# 生成授权链接(同时写入 code 文件)
local auth_url
auth_url=$(_tdoc_generate_auth_url)
echo "┌─────────────────────────────────────────────────────────┐"
echo "│ 请在浏览器中打开以下链接完成授权: │"
echo "│ │"
printf "│ %s\n" "$auth_url"
echo "│ │"
echo "│ ⚠️ 请使用 QQ 或微信 扫码 / 登录授权 │"
echo "└─────────────────────────────────────────────────────────┘"
echo ""
echo "⏳ 正在等待您完成授权,无需任何额外操作..."
echo " (最多等待 $(( (_TDOC_POLL_MAX * _TDOC_POLL_INTERVAL) + 15 )) 秒)"
echo ""
# ★ 立即启动后台轮询(与用户操作并行)
_tdoc_start_bg_poll > /dev/null
# ★ 前台等待 Token自动检测无需用户回复
local result
result=$(_tdoc_wait_token)
case "$result" in
TOKEN_READY)
local token=$(cat "$_TDOC_TOKEN_FILE")
echo ""
echo "✅ 授权成功!正在保存配置..."
if _tdoc_save_token "$token"; then
_tdoc_cleanup
echo "✅ Token 已写入 mcporter 配置"
echo ""
echo "🎉 配置完成!现在可以直接使用腾讯文档功能了。"
echo ""
echo "📖 使用示例:"
echo " mcporter call ${_TDOC_SERVICE_NAME} manage.recent_online_file --args '{\"num\":10}'"
echo ""
echo "🏠 腾讯文档主页:${_TDOC_API_BASE}/home"
else
echo "⚠️ Token 写入配置失败"
echo " 请手动运行mcporter config add ${_TDOC_SERVICE_NAME} ${_TDOC_MCP_URL} --header \"Authorization=${token}\" --transport http --scope home"
fi
;;
AUTH_TIMEOUT)
echo ""
echo "⏳ 授权超时(未在时限内完成授权)"
echo " 请重新运行bash ./setup.sh"
exit 1
;;
ERROR:expired)
echo ""
echo "❌ 授权码已过期请重新运行bash ./setup.sh"
exit 1
;;
ERROR:err_400006)
echo ""
echo "❌ Token 鉴权失败请重新运行bash ./setup.sh"
exit 1
;;
ERROR:*)
echo ""
echo "❌ 授权失败:$result"
echo " 如问题持续,请联系腾讯文档客服:${_TDOC_API_BASE}/home/feedback"
exit 1
;;
esac
return 0
}
# ── 脚本入口 ──────────────────────────────────────────────────────────────────
# 直接执行时:
# bash ./setup.sh tdoc_check_and_start_auth → 第一步:检查状态 / 生成授权链接
# bash ./setup.sh tdoc_wait_auth → 第二步:等待授权完成
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
if [[ -n "$1" ]]; then
# 参数分发:将第一个参数作为函数名执行
case "$1" in
tdoc_check_and_start_auth|tdoc_wait_auth)
"$1"
exit $?
;;
setup)
echo "🚀 腾讯文档 MCP Skill 人工配置向导"
echo ""
_tdoc_interactive_setup
;;
*)
echo "ERROR:unknown_command - 未知命令: $1"
echo "可用命令: tdoc_check_and_start_auth, tdoc_wait_auth, setup"
exit 1
;;
esac
else
echo "用法:"
echo " bash ./setup.sh tdoc_check_and_start_auth # 第一步:检查状态 / 生成授权链接"
echo " bash ./setup.sh tdoc_wait_auth # 第二步:等待授权完成"
fi
fi