move claude-marketplace to ai-proj-helper

This commit is contained in:
2026-03-12 21:42:30 +08:00
parent d7b6835e1d
commit 43585b8504
188 changed files with 39510 additions and 0 deletions

View File

@@ -0,0 +1,423 @@
#!/usr/bin/env python3
"""
创建新的飞书文档ai-proj 项目可见性手册
"""
import requests
import os
import time
from datetime import datetime, timedelta
from PIL import Image, ImageDraw, ImageFont
ZHIYUN_APP_ID = "cli_a9f29dca82b9dbef"
ZHIYUN_APP_SECRET = "sDfhjG7QT1S4gfHiMVYSygmPQPN1R2Ho"
BASE_URL = "https://open.feishu.cn/open-apis"
_token = None
_token_expires = None
def get_token():
global _token, _token_expires
if _token and _token_expires and datetime.now() < _token_expires:
return _token
url = f"{BASE_URL}/auth/v3/tenant_access_token/internal"
response = requests.post(url, json={
"app_id": ZHIYUN_APP_ID,
"app_secret": ZHIYUN_APP_SECRET
})
data = response.json()
if data.get("code") != 0:
raise Exception(f"获取 token 失败: {data}")
_token = data["tenant_access_token"]
_token_expires = datetime.now() + timedelta(seconds=data.get("expire", 7200) - 60)
return _token
def headers():
return {
"Authorization": f"Bearer {get_token()}",
"Content-Type": "application/json"
}
def create_document(title: str):
"""创建文档"""
url = f"{BASE_URL}/docx/v1/documents"
response = requests.post(url, headers=headers(), json={"title": title})
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建文档失败: {data}")
doc = data["data"]["document"]
document_id = doc["document_id"]
print(f"[OK] 文档创建成功: {document_id}")
return document_id
def set_document_permission(document_id: str):
"""设置文档权限为组织内可编辑"""
url = f"{BASE_URL}/drive/v1/permissions/{document_id}/public"
payload = {
"external_access_entity": "open",
"security_entity": "anyone_can_view",
"comment_entity": "anyone_can_view",
"share_entity": "anyone",
"link_share_entity": "tenant_editable",
"invite_external": False
}
response = requests.patch(url, headers=headers(), params={"type": "docx"}, json=payload)
result = response.json()
if result.get("code") == 0:
print("[OK] 权限设置成功: 组织内可编辑")
return True
else:
print(f"[WARN] 权限设置: {result.get('msg')}")
return False
def get_document_blocks(document_id: str):
"""获取文档所有块"""
url = f"{BASE_URL}/docx/v1/documents/{document_id}/blocks"
response = requests.get(url, headers=headers())
data = response.json()
if data.get("code") != 0:
raise Exception(f"获取块失败: {data}")
return data["data"].get("items", [])
def create_blocks(document_id: str, parent_id: str, blocks: list):
"""创建内容块"""
url = f"{BASE_URL}/docx/v1/documents/{document_id}/blocks/{parent_id}/children"
payload = {"children": blocks}
response = requests.post(url, headers=headers(), params={"document_revision_id": -1}, json=payload)
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建块失败: {data}")
return data["data"].get("children", [])
def create_image_block(document_id: str, parent_id: str):
"""创建空图片块"""
url = f"{BASE_URL}/docx/v1/documents/{document_id}/blocks/{parent_id}/children"
payload = {
"children": [{
"block_type": 27,
"image": {}
}]
}
response = requests.post(url, headers=headers(), params={"document_revision_id": -1}, json=payload)
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建图片块失败: {data}")
return data["data"]["children"][0]["block_id"]
def upload_image(file_path: str, block_id: str):
"""上传图片"""
url = f"{BASE_URL}/drive/v1/medias/upload_all"
file_name = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
with open(file_path, 'rb') as f:
files = {'file': (file_name, f, 'image/png')}
data = {
'file_name': file_name,
'parent_type': 'docx_image',
'parent_node': block_id,
'size': str(file_size)
}
response = requests.post(
url,
headers={"Authorization": f"Bearer {get_token()}"},
files=files,
data=data
)
result = response.json()
if result.get("code") != 0:
raise Exception(f"上传失败: {result}")
return result["data"]["file_token"]
def bind_image(document_id: str, block_id: str, file_token: str):
"""绑定图片到图片块"""
url = f"{BASE_URL}/docx/v1/documents/{document_id}/blocks/{block_id}"
payload = {"replace_image": {"token": file_token}}
response = requests.patch(url, headers=headers(), params={"document_revision_id": -1}, json=payload)
data = response.json()
if data.get("code") != 0:
raise Exception(f"绑定失败: {data}")
return True
def insert_image(document_id: str, file_path: str, description: str = ""):
"""插入图片的完整流程:创建块 -> 上传 -> 绑定"""
print(f" 插入图片: {description}")
# Step 1: 创建空图片块
block_id = create_image_block(document_id, document_id)
print(f" block_id: {block_id}")
# Step 2: 上传图片
file_token = upload_image(file_path, block_id)
print(f" file_token: {file_token}")
# Step 3: 绑定图片到块
bind_image(document_id, block_id, file_token)
print(f" 绑定成功!")
# 等待处理
time.sleep(0.5)
return block_id, file_token
def generate_visibility_diagram():
"""生成可见性示意图"""
output_path = "/tmp/visibility_diagram.png"
img = Image.new('RGB', (800, 400), color='#ffffff')
draw = ImageDraw.Draw(img)
# 尝试加载字体
try:
font_large = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 24)
font_medium = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 18)
font_small = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 14)
except:
font_large = ImageFont.load_default()
font_medium = ImageFont.load_default()
font_small = ImageFont.load_default()
# 标题背景
draw.rectangle([0, 0, 800, 60], fill='#1890ff')
draw.text((400, 30), "ai-proj 项目可见性示意图", fill='white', anchor='mm', font=font_large)
# 私有项目区域
draw.rectangle([40, 80, 380, 370], fill='#fff7e6', outline='#fa8c16', width=3)
draw.text((210, 110), "🔒 私有项目", fill='#fa8c16', anchor='mm', font=font_medium)
draw.text((210, 140), "(private)", fill='#d48806', anchor='mm', font=font_small)
draw.text((210, 180), "仅项目创建者可见", fill='#666666', anchor='mm', font=font_small)
# 私有项目图标
draw.ellipse([175, 210, 245, 280], fill='#fa8c16')
draw.text((210, 245), "👤", fill='white', anchor='mm', font=font_large)
draw.text((210, 310), "项目所有者", fill='#333333', anchor='mm', font=font_small)
draw.text((210, 335), "管理员", fill='#999999', anchor='mm', font=font_small)
# 企业项目区域
draw.rectangle([420, 80, 760, 370], fill='#e6f7ff', outline='#1890ff', width=3)
draw.text((590, 110), "👥 企业项目", fill='#1890ff', anchor='mm', font=font_medium)
draw.text((590, 140), "(enterprise)", fill='#096dd9', anchor='mm', font=font_small)
draw.text((590, 180), "企业内所有成员可见", fill='#666666', anchor='mm', font=font_small)
# 企业项目图标组
positions = [(520, 230), (590, 230), (660, 230), (555, 290), (625, 290)]
for x, y in positions:
draw.ellipse([x-25, y-25, x+25, y+25], fill='#1890ff')
draw.text((x, y), "👤", fill='white', anchor='mm', font=font_medium)
draw.text((590, 340), "企业所有成员", fill='#333333', anchor='mm', font=font_small)
# 中间箭头
draw.text((400, 225), "", fill='#333333', anchor='mm', font=font_large)
img.save(output_path)
print(f"[OK] 生成可见性示意图: {output_path}")
return output_path
def generate_ui_screenshot():
"""生成 UI 示意图"""
output_path = "/tmp/visibility_ui.png"
img = Image.new('RGB', (700, 250), color='#fafafa')
draw = ImageDraw.Draw(img)
try:
font_medium = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 16)
font_small = ImageFont.truetype("/System/Library/Fonts/PingFang.ttc", 12)
except:
font_medium = ImageFont.load_default()
font_small = ImageFont.load_default()
# 标题
draw.text((30, 25), "创建项目 - 可见性选择", fill='#333333', font=font_medium)
# 私有项目选项(选中状态)
draw.rectangle([30, 70, 320, 140], fill='#fff7e6', outline='#fa8c16', width=2)
draw.ellipse([45, 95, 65, 115], fill='#fa8c16')
draw.text((80, 90), "🔒 私有项目", fill='#fa8c16', font=font_medium)
draw.text((80, 115), "仅创建者可见", fill='#999999', font=font_small)
# 默认标签
draw.rectangle([250, 85, 310, 110], fill='#52c41a')
draw.text((280, 97), "默认", fill='white', anchor='mm', font=font_small)
# 企业项目选项(未选中状态)
draw.rectangle([350, 70, 640, 140], fill='#ffffff', outline='#d9d9d9', width=1)
draw.ellipse([365, 95, 385, 115], outline='#d9d9d9', width=2)
draw.text((400, 90), "👥 企业项目", fill='#666666', font=font_medium)
draw.text((400, 115), "企业内所有成员可见", fill='#999999', font=font_small)
# 底部说明
draw.rectangle([30, 170, 670, 220], fill='#f0f5ff', outline='#adc6ff', width=1)
draw.text((50, 185), "💡 提示:新创建的项目默认为私有项目。", fill='#1890ff', font=font_small)
draw.text((50, 205), "设置为企业项目后,项目需要先关联企业才能被企业成员访问。", fill='#666666', font=font_small)
img.save(output_path)
print(f"[OK] 生成 UI 示意图: {output_path}")
return output_path
# 文档内容块定义
def heading1(text):
return {"block_type": 3, "heading1": {"elements": [{"text_run": {"content": text}}]}}
def heading2(text):
return {"block_type": 4, "heading2": {"elements": [{"text_run": {"content": text}}]}}
def heading3(text):
return {"block_type": 5, "heading3": {"elements": [{"text_run": {"content": text}}]}}
def text_block(content):
return {"block_type": 2, "text": {"elements": [{"text_run": {"content": content}}]}}
def bullet(content):
return {"block_type": 12, "bullet": {"elements": [{"text_run": {"content": content}}]}}
def ordered(content):
return {"block_type": 13, "ordered": {"elements": [{"text_run": {"content": content}}]}}
def code_block(content, language=1):
return {"block_type": 14, "code": {"elements": [{"text_run": {"content": content}}], "language": language}}
def divider():
return {"block_type": 22, "divider": {}}
def main():
print("\n" + "#" * 60)
print("# 创建新文档ai-proj 项目可见性手册")
print("#" * 60)
# Step 1: 生成示意图
print("\n--- Step 1: 生成示意图 ---")
diagram_path = generate_visibility_diagram()
ui_path = generate_ui_screenshot()
# Step 2: 创建新文档
print("\n--- Step 2: 创建新文档 ---")
doc_id = create_document("ai-proj 项目可见性手册")
set_document_permission(doc_id)
# Step 3: 创建手册内容
print("\n--- Step 3: 创建手册内容 ---")
# 第一部分:标题和概述
blocks_part1 = [
heading1("ai-proj 项目可见性手册"),
text_block("本手册介绍 ai-proj 系统中项目可见性功能的使用方法。"),
divider(),
heading2("1. 功能概述"),
text_block("项目可见性用于控制谁可以查看和访问您的项目。ai-proj 支持两种可见性级别:"),
bullet("私有项目 (private):仅项目创建者和管理员可见"),
bullet("企业项目 (enterprise):企业内所有成员可见"),
text_block("新创建的项目默认为「私有项目」,保护您的隐私。"),
divider(),
heading2("2. 可见性对比"),
text_block("下图展示了两种可见性的区别:"),
]
create_blocks(doc_id, doc_id, blocks_part1)
print(" 第一部分内容创建完成")
# 插入可见性示意图
print("\n--- Step 4: 插入可见性示意图 ---")
insert_image(doc_id, diagram_path, "可见性示意图")
# 第二部分:设置方法
print("\n--- Step 5: 添加设置说明 ---")
blocks_part2 = [
divider(),
heading2("3. 设置项目可见性"),
heading3("3.1 创建项目时设置"),
text_block("在创建项目时,您可以选择项目的可见性:"),
ordered("点击「创建项目」按钮"),
ordered("在弹出的表单中找到「项目可见性」选项"),
ordered("选择「私有项目」或「企业项目」"),
ordered("填写其他信息后点击「确定」"),
text_block("UI 界面示意:"),
]
create_blocks(doc_id, doc_id, blocks_part2)
print(" 设置说明创建完成")
# 插入 UI 示意图
print("\n--- Step 6: 插入 UI 示意图 ---")
insert_image(doc_id, ui_path, "UI 示意图")
# 第三部分:修改方法和规则
print("\n--- Step 7: 添加剩余内容 ---")
blocks_part3 = [
heading3("3.2 修改已有项目"),
ordered("在项目列表中找到目标项目"),
ordered("点击项目卡片上的「编辑」按钮"),
ordered("在编辑页面修改「项目可见性」"),
ordered("点击「保存」"),
divider(),
heading2("4. 可见性规则"),
bullet("私有项目:仅所有者和超级管理员可以查看"),
bullet("企业项目:需要项目已关联企业,企业内所有成员可以查看"),
bullet("只有项目所有者或管理员可以修改可见性"),
bullet("设置为「企业项目」需要项目已关联企业"),
divider(),
heading2("5. API 参考"),
text_block("修改项目可见性的 API"),
code_block('''PATCH /api/v1/projects/:id/visibility
请求体:
{
"visibility": "enterprise"
}
可选值: "private" | "enterprise"'''),
divider(),
heading2("6. 注意事项"),
bullet("新项目默认为私有,请根据需要调整可见性"),
bullet("将私有项目改为企业项目后,企业所有成员都能看到"),
bullet("将企业项目改为私有后,其他成员将无法访问"),
bullet("项目成员权限不受可见性影响,被添加为成员的用户始终可以访问"),
divider(),
text_block(f"最后更新:{datetime.now().strftime('%Y-%m-%d %H:%M')}"),
]
create_blocks(doc_id, doc_id, blocks_part3)
print("\n" + "=" * 60)
print("文档创建完成!")
print(f"\n文档地址: https://zhiyuncai.feishu.cn/docx/{doc_id}")
print("=" * 60)
# 验证图片
print("\n--- 验证图片状态 ---")
time.sleep(2) # 等待服务器处理
blocks = get_document_blocks(doc_id)
image_count = 0
for block in blocks:
if block.get("block_type") == 27:
image_count += 1
image_data = block.get("image", {})
token = image_data.get("token", "")
width = image_data.get("width", 0)
height = image_data.get("height", 0)
status = "✅ 有效" if token else "❌ 空"
print(f"图片 #{image_count}: {status} (token: {token[:15]}..., 尺寸: {width}x{height})")
if image_count == 0:
print("⚠️ 警告:文档中没有图片块")
elif image_count == 2:
print(f"\n✅ 成功上传 {image_count} 张图片")
if __name__ == "__main__":
main()