Skill: xiangxueweb/videoclaw-pro (version 1.0.0)

--- name: videoclaw-pro model: kimi/kimi-k2.5 description: | 视频剪辑脚本执行助手（增强版）- 根据飞书提示词库和直播素材文字档生成剪辑建议脚本。 支持飞书文档（docx）和知识库（wiki）链接，自动解析权限问题。 ⚠️ 重要：本 skill 所有文档读取必须通过 Python CLI 脚本，不使用内置 feishu_doc 工具！ 触发指令格式：「剪辑脚本 [视频类型] [期数/标题] 规则链接：[飞书链接] 素材链接：[飞书链接]」 tools: - exec --- # VideoClaw Pro - 视频剪辑脚本执行助手（增强版） 你的职责是根据飞书内部的提示词库和直播素材文字档，执行生成剪辑建议脚本。 ## 核心原则 ⚠️ **本 skill 不使用 OpenClaw 内置的 `feishu_doc` 工具！** 所有文档读写必须通过 `exec` 调用 Python CLI 脚本。 如果模型自动调用了内置 `feishu_doc` 工具，剪辑脚本生成会失败（因为内置工具不支持 wiki 链接）。 ## 与原版 videoclaw 的区别 1. **支持 wiki 链接**：可自动从 wiki 链接中提取背后的文档 token 2. **权限问题友好提示**：当遇到 404/权限问题时，提示用户需要在飞书中分享文档给机器人 3. **完整独立 skill**：所有功能整合在一个 skill 目录中 4. **强制使用 Python CLI**：通过 exec 调用，不走内置 feishu_doc 工具 ## 工作流程 1. **提取用户指令中的信息**： - 视频类型（如：直播切片、产品评测、教程等） - 期数/标题 - 规则文档链接（提示词库飞书链接，支持 docx 和 wiki） - 素材文档链接（直播素材文字档飞书链接，支持 docx 和 wiki） 2. **读取飞书文档（必须用 Python CLI）**： ``` python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" read "文档URL" ``` - 支持 docx 和 wiki 两种链接格式 - Python 脚本会自动处理 wiki → docx 的转换 3. **生成剪辑建议脚本**： - 严格按照规则文档中的提示词设定 - 处理素材原文，生成结构化的剪辑建议脚本 - 脚本应包含：时间轴建议、画面切换点、重点内容标注、字幕建议等 4. **写入飞书文档（必须用 Python CLI）**： ``` python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" write "文档标题" "内容" ``` - 文件名为：`[视频类型]_[期数/标题]_脚本` - 返回新文档链接给用户 ## 工具调用方式 ### 读取规则文档（提示词库） ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" read "规则文档URL" ``` ### 读取素材文档 ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" read "素材文档URL" ``` ### 创建并写入脚本文档 ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" write "文档标题" "生成的脚本内容" ``` ## 完整执行示例 当用户输入： ``` 剪辑脚本 直播切片 第12期 规则链接：https://feishu.cn/wiki/AbCdEfGh 素材链接：https://feishu.cn/wiki/XyZaBcDe ``` 执行步骤： 1. 提取信息： - 视频类型：直播切片 - 期数：第12期 - 规则链接：支持 wiki 和 docx 格式 - 素材链接：支持 wiki 和 docx 格式 2. 读取规则文档（必须用 Python CLI，自动处理 wiki → docx 转换）： ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" read "https://feishu.cn/wiki/AbCdEfGh" ``` 3. 读取素材文档（必须用 Python CLI）： ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" read "https://feishu.cn/wiki/XyZaBcDe" ``` 4. 根据提示词规则处理素材，生成剪辑脚本（Markdown格式） 5. 写入飞书文档（必须用 Python CLI）： ```bash python "C:\Users\yangj\.openclaw\workspace\skills\videoclaw-pro\videoclaw_cli.py" write "直播切片_第12期_脚本" "# 直播切片_第12期_剪辑脚本..." ``` 6. 返回生成的文档链接给用户 ## ⚠️ 禁止行为 - **禁止**使用内置 `feishu_doc` 工具读取文档（不支持 wiki，会报 1770002 错误） - **禁止**将 wiki token 直接当作 docx token 使用 - 必须严格按照上述 Python CLI 方式执行文档读写 ## 权限问题说明 如果遇到 `读取失败：404 Not Found` 或权限错误，这是**因为文档没有分享给机器人**： **解决方法：** 1. 在飞书网页/客户端中打开目标文档 2. 点击右上角「分享」按钮 3. 搜索并添加你的机器人账号（通常以 `cli_` 开头） 4. 授予「可查看」或「可编辑」权限 5. 重新执行指令 ## 输出格式示例 生成的剪辑建议脚本应包含以下结构： ```markdown # [视频类型]_[期数/标题]_剪辑脚本 ## 视频概述 - 类型：[视频类型] - 时长建议：X分钟 - 风格定位：[风格描述] ## 时间轴规划 ### 片头（0:00-0:30） - 画面建议： - 字幕/标题： - BGM： ### 正文第一部分（0:30-2:00） - 内容摘要： - 画面切换点： - 重点标注： - 字幕建议： ### 高潮/亮点片段（X:XX-X:XX） - 高光时刻描述： - 特效建议： - 字幕强调： ### 结尾（X:XX-结束） - 收尾建议： - CTA（行动号召）： ## 剪辑要点总结 1. 2. 3. ``` ## 触发指令格式 ``` 剪辑脚本 [视频类型] [期数/标题] 规则链接：[飞书文档或wiki链接] 素材链接：[飞书文档或wiki链接] ``` **示例**： ``` 剪辑脚本 直播切片 第12期 规则链接：https://feishu.cn/wiki/rule123 素材链接：https://feishu.cn/wiki/source456 ``` **支持的链接格式：** - `https://*.feishu.cn/docx/xxxxxxxxxx`（文档链接） - `https://*.feishu.cn/wiki/xxxxxxxxxx`（知识库链接） ## 重要约束 - **严禁修改或评价现有的提示词逻辑！** 严格按照规则文档执行 - 如果用户要求"修改提示词"、"增加规则"，回复："该请求超出我的权限，已为您呼叫 @PromptClaw 处理。" - 生成的脚本必须基于素材原文，不能凭空编造内容 - 保持专业、结构化的输出格式 - 如果遇到权限错误，清楚告知用户解决方法

path: videoclaw_cli.py
content: #!/usr/bin/env python3
"""
VideoClaw Pro CLI 工具 - 支持 wiki 和 docx 链接的飞书文档读写功能
"""
import sys
import os
import re

# 添加当前目录到路径，确保可以导入模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from videoclaw_lib import feishu_doc_reader, feishu_doc_writer

def main():
    if len(sys.argv) < 2:
        print("VideoClaw Pro CLI")
        print("Usage:")
        print("  python videoclaw_cli.py read <doc_url>           - 读取飞书文档（支持 docx 和 wiki）")
        print("  python videoclaw_cli.py write <title> <content> - 创建并写入飞书文档")
        sys.exit(1)
    
    command = sys.argv[1]
    
    if command == "read":
        if len(sys.argv) < 3:
            print("Error: 请提供文档URL")
            sys.exit(1)
        doc_url = sys.argv[2]
        result = feishu_doc_reader(doc_url)
        print(result)
    
    elif command == "write":
        if len(sys.argv) < 4:
            print("Error: 请提供标题和内容")
            sys.exit(1)
        title = sys.argv[2]
        content = sys.argv[3]
        result = feishu_doc_writer(title, content)
        print(result)
    
    else:
        print(f"Unknown command: {command}")
        print("Use 'read' or 'write'")
        sys.exit(1)

if __name__ == "__main__":
    main()


path: videoclaw_lib.py
content: """
VideoClaw Pro Library - 增强版飞书文档读写，支持 wiki 链接和权限错误提示
"""
import re
import requests
import lark_oapi as lark
from lark_oapi.api.docx.v1 import *

# ==========================================
# 飞书应用凭证配置
# ==========================================
APP_ID = "cli_a95e1b5ed9381cc3"
APP_SECRET = [REDACTED_SECRET]

# 你的目标文件夹 Token，如果不填，默认会创建在机器人的云空间根目录
TARGET_FOLDER_TOKEN = ""

# 初始化飞书客户端
client = lark.Client.builder().app_id(APP_ID).app_secret(APP_SECRET).build()

# 缓存 access_token，避免频繁获取
_cached_access_token = None
_cached_token_expire = 0

def _get_access_token() -> str:
    """获取飞书访问令牌，带缓存"""
    global _cached_access_token, _cached_token_expire
    import time
    
    # 如果缓存未过期，直接返回
    if _cached_access_token and time.time() < _cached_token_expire:
        return _cached_access_token
    
    resp = requests.post(
        'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal',
        json={'app_id': APP_ID, 'app_secret': APP_SECRET}
    )
    json_data = resp.json()
    
    if json_data.get('code') != 0:
        raise Exception(f"获取 access_token 失败: {json_data.get('msg')}")
    
    _cached_access_token = json_data.get('tenant_access_token')
    # 提前5分钟过期，避免临界问题
    _cached_token_expire = time.time() + json_data.get('expire', 7200) - 300
    
    return _cached_access_token


def _is_wiki_url(url: str) -> bool:
    """判断是否为 wiki 链接"""
    return '/wiki/' in url


def _extract_wiki_node_token(url: str) -> str:
    """从 wiki 链接中提取 node_token"""
    match = re.search(r'/wiki/([a-zA-Z0-9]+)', url)
    if match:
        return match.group(1)
    raise ValueError(f"无法从链接 {url} 中提取 wiki node_token")


def _resolve_wiki_to_docx(wiki_url: str) -> str:
    """
    将 wiki 链接解析为 docx 文档 token 或 file token
    通过查询 wiki node 获取背后的文档信息
    """
    node_token = _extract_wiki_node_token(wiki_url)
    print(f"[Skill] 正在解析 wiki 链接，node_token: {node_token}")
    
    try:
        # 直接调用飞书 API 获取 wiki node 信息
        access_token = _get_access_token()
        headers = {'Authorization': f'Bearer {access_token}'}
        resp = requests.get(
            f'https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?[REDACTED_SECRET]',
            headers=headers
        )
        
        if resp.status_code != 200:
            raise Exception(f"Wiki API 调用失败: HTTP {resp.status_code}")
        
        json_data = resp.json()
        if json_data.get('code') != 0:
            raise Exception(f"Wiki API 调用失败: {json_data.get('msg')}")
        
        data = json_data.get('data', {})
        node = data.get('node', {})
        obj_token = node.get('obj_token')
        obj_type = node.get('obj_type')
        title = node.get('title', 'unknown')
        
        if not obj_token:
            raise Exception(f"无法从 wiki node 响应中提取文档 token")
        
        print(f"[Skill] Wiki 解析成功，obj_token: {obj_token}, 类型: {obj_type}, 标题: {title}")
        
        # 返回一个元组，包含 (obj_token, obj_type, title)
        return (obj_token, obj_type, title)
        
    except Exception as e:
        if "Wiki API" in str(e) or "wiki node" in str(e):
            raise
        raise Exception(f"解析 wiki 链接失败: {str(e)}")


def _extract_document_id(doc_url: str) -> str:
    """
    从飞书文档链接中提取 document_id
    支持 docx 和 wiki 两种格式
    
    对于 wiki 链接，返回的是 (obj_token, obj_type, title) 元组
    """
    # 如果是 wiki 链接，先解析为 docx token
    if _is_wiki_url(doc_url):
        result = _resolve_wiki_to_docx(doc_url)
        # result 是一个元组 (obj_token, obj_type, title)
        if isinstance(result, tuple):
            return result
        return result
    
    # 匹配 docx 类型的链接: https://*.feishu.cn/docx/xxxxxxxxxxxxxx
    match = re.search(r'/docx/([a-zA-Z0-9]+)', doc_url)
    if match:
        return (match.group(1), 'docx', None)
    
    raise ValueError(
        f"无法从链接 {doc_url} 中提取 document_id。"
        f"支持的格式：\n"
        f"  - 文档链接：https://*.feishu.cn/docx/xxxxxxxxxx\n"
        f"  - 知识库链接：https://*.feishu.cn/wiki/xxxxxxxxxx"
    )


def _parse_blocks_to_text(blocks: list) -> str:
    """将飞书的 Block 结构简单解析为纯文本/Markdown"""
    result = []
    for block in blocks:
        block_type = block.block_type
        
        # 处理文本块 (Text)
        if block_type == 2 and block.text and block.text.elements:
            text_content = "".join([elem.text_run.content for elem in block.text.elements if elem.text_run])
            result.append(text_content)
        
        # 处理标题1 (Heading1)
        elif block_type == 3 and block.heading1 and block.heading1.elements:
            text_content = "".join([elem.text_run.content for elem in block.heading1.elements if elem.text_run])
            result.append(f"# {text_content}")
            
        # 处理标题2 (Heading2)
        elif block_type == 4 and block.heading2 and block.heading2.elements:
            text_content = "".join([elem.text_run.content for elem in block.heading2.elements if elem.text_run])
            result.append(f"## {text_content}")
            
        # 处理标题3 (Heading3)
        elif block_type == 5 and block.heading3 and block.heading3.elements:
            text_content = "".join([elem.text_run.content for elem in block.heading3.elements if elem.text_run])
            result.append(f"### {text_content}")
            
        # 处理代码块
        elif block_type == 11 and block.code:
            code_content = block.code.content or ""
            lang = block.code.language if hasattr(block.code, 'language') else ""
            result.append(f"```{lang}\n{code_content}\n```")
            
        # 处理引用
        elif block_type == 12 and block.quote:
            quote_content = "".join([
                elem.text_run.content 
                for elem in block.quote.elements 
                if elem.text_run
            ]) if block.quote.elements else ""
            result.append(f"> {quote_content}")
            
        # 处理有序列表
        elif block_type == 7:
            content = ""
            if hasattr(block, 'ordered') and block.ordered and block.ordered.elements:
                content = "".join([elem.text_run.content for elem in block.ordered.elements if elem.text_run])
            result.append(f"1. {content}")
            
        # 处理无序列表
        elif block_type == 6:
            content = ""
            if hasattr(block, 'bullet') and block.bullet and block.bullet.elements:
                content = "".join([elem.text_run.content for elem in block.bullet.elements if elem.text_run])
            result.append(f"- {content}")
            
        # 其他类型的 block 这里做了简化处理
    
    return "\n".join(result)


def feishu_doc_reader(doc_url: str) -> str:
    """
    读取指定飞书文档的内容，用于获取提示词模板和原始素材。
    
    支持两种链接格式：
    - 文档链接：https://*.feishu.cn/docx/xxxxxxxxxx
    - 知识库链接：https://*.feishu.cn/wiki/xxxxxxxxxx
    
    Args:
        doc_url (str): 飞书文档的完整URL链接
        
    Returns:
        str: 文档的纯文本或Markdown内容
    """
    import io
    import docx
    
    print(f"[Skill Execution] 正在读取飞书文档: {doc_url}")
    
    # 判断链接类型
    link_type = "wiki" if _is_wiki_url(doc_url) else "docx"
    print(f"[Skill] 检测到链接类型: {link_type}")
    
    try:
        # 1. 提取 document_id（自动处理 wiki → docx）
        result = _extract_document_id(doc_url)
        
        # result 可能是字符串（docx token）或元组 (obj_token, obj_type, title)
        if isinstance(result, tuple):
            document_id, doc_type, title = result
        else:
            document_id = result
            doc_type = 'docx'
            title = None
            
        print(f"[Skill] 最终文档 [REDACTED_SECRET], 类型: {doc_type}")
        
        # 2. 根据类型读取文档内容
        if doc_type == 'file':
            # 需要下载 docx 文件并解析
            access_token = _get_access_token()
            headers = {'Authorization': f'Bearer {access_token}'}
            resp = requests.get(
                f'https://open.feishu.cn/open-apis/drive/v1/files/{document_id}/download',
                headers=headers
            )
            
            if resp.status_code != 200:
                raise Exception(f"下载文件失败: HTTP {resp.status_code}")
            
            # 解析 docx 内容
            docx_file = docx.Document(io.BytesIO(resp.content))
            text_content = []
            for para in docx_file.paragraphs:
                if para.text.strip():
                    text_content.append(para.text)
            
            text_content = '\n'.join(text_content)
            print(f"[Skill] 成功读取文件文档，内容长度: {len(text_content)} 字符")
            return text_content
        else:
            # 使用 docx API 读取文档 block
            request: ListDocumentBlockRequest = ListDocumentBlockRequest.builder() \
                .document_id(document_id) \
                .page_size(500) \
                .build()
                
            # 3. 发起请求
            response: ListDocumentBlockResponse = client.docx.v1.document_block.list(request)
            
            # 4. 检查响应
            if not response.success():
                code = response.code
                msg = response.msg
                
                # 根据错误类型给出友好提示
                if code == 404 or "not found" in str(msg).lower():
                    error_hint = """
【权限提示】
读取文档失败 (404 Not Found)。

这通常是因为文档没有分享给机器人。请按以下步骤操作：
1. 在飞书网页/客户端中打开目标文档
2. 点击右上角「分享」按钮
3. 搜索并添加你的机器人账号（通常以 `cli_` 开头）
4. 授予「可查看」或「可编辑」权限
5. 重新执行指令
"""
                    print(f"[Error]{error_hint}")
                    return error_hint
                else:
                    error_msg = f"飞书API调用失败: Code: {code}, Msg: {msg}"
                    print(f"[Error] {error_msg}")
                    return f"读取失败：{error_msg}"
                
            # 5. 解析并返回文本
            blocks = response.data.items
            text_content = _parse_blocks_to_text(blocks)
            
            print(f"[Skill] 成功读取文档，内容长度: {len(text_content)} 字符")
            return text_content
        
    except ValueError as e:
        # 链接格式解析错误
        print(f"[Error] 链接格式错误: {str(e)}")
        return f"链接格式错误：{str(e)}"
    except Exception as e:
        error_str = str(e)
        print(f"[Error] 读取文档时发生异常: {error_str}")
        
        # 如果是权限相关的错误，给出友好提示
        if "404" in error_str or "permission" in error_str.lower():
            return """读取文档失败，可能是权限问题。

请确保：
1. 文档已分享给机器人账号
2. 机器人有足够的权限（至少「可查看」）

操作方法：在飞书文档中点击「分享」→ 添加机器人账号"""
        
        return f"读取文档时发生异常: {error_str}"


def feishu_doc_writer(title: str, content: str) -> str:
    """
    创建一个新的飞书文档并将生成的剪辑脚本内容写入，返回新文档链接。
    
    Args:
        title (str): 新文档的标题，例如：直播切片_第12期_脚本
        content (str): 大模型生成的剪辑脚本Markdown内容
        
    Returns:
        str: 新生成的飞书文档URL链接
    """
    print(f"[Skill Execution] 正在飞书创建新文档: {title}")
    
    try:
        # ==========================================
        # 1. 创建空的新文档
        # ==========================================
        create_req = CreateDocumentRequest.builder() \
            .request_body(CreateDocumentRequestBody.builder()
                          .title(title)
                          .folder_token(TARGET_FOLDER_TOKEN)
                          .build()) \
            .build()
            
        create_resp = client.docx.v1.document.create(create_req)
        if not create_resp.success():
            error_msg = f"创建文档失败: Code: {create_resp.code}, Msg: {create_resp.msg}"
            print(f"[Error] {error_msg}")
            return error_msg
            
        document_id = create_resp.data.document.document_id
        print(f"[Skill] 文档创建成功，document_id: {document_id}")
        
        # ==========================================
        # 2. 将内容写入文档
        # ==========================================
        # 构造插入文本的请求体
        block_elem = TextElement.builder() \
            .text_run(TextRun.builder().content(content).build()) \
            .build()
            
        block = Block.builder() \
            .block_type(2) \
            .text(Text.builder().elements([block_elem]).build()) \
            .build()
            
        insert_req = CreateDocumentBlockChildrenRequest.builder() \
            .document_id(document_id) \
            .block_id(document_id) \
            .request_body(CreateDocumentBlockChildrenRequestBody.builder()
                          .children([block])
                          .index(-1)
                          .build()) \
            .build()
            
        insert_resp = client.docx.v1.document_block_children.create(insert_req)
        if not insert_resp.success():
            error_msg = f"写入内容失败: Code: {insert_resp.code}, Msg: {insert_resp.msg}"
            print(f"[Error] {error_msg}")
            # 即使写入失败，也返回空文档的链接
            return f"内容写入失败 ({insert_resp.msg})。空文档链接：https://feishu.cn/docx/{document_id}"
            
        # ==========================================
        # 3. 拼接最终链接并返回
        # ==========================================
        final_url = f"https://feishu.cn/docx/{document_id}"
        print(f"[Skill Execution] 写入成功！新文档链接: {final_url}")
        
        return final_url
        
    except Exception as e:
        print(f"[Error] 写入文档时发生异常: {str(e)}")
        return f"写入文档时发生异常: {str(e)}"


# 注册为工具
__all__ = ["feishu_doc_reader", "feishu_doc_writer"]


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)