@clawhub-chao980-170ff64c9c
生成专业演示文稿,支持 HTML 交互式幻灯片和 PowerPoint (.pptx) 两种格式。当用户需要制作 PPT、演示文稿、Slides、幻灯片、市场分析报告、产品介绍时触发。支持乔布斯风极简科技感设计,自动数据可视化,翻页导航功能。
---
name: ppt-generator-pro
description: "生成专业演示文稿,支持 HTML 交互式幻灯片和 PowerPoint (.pptx) 两种格式。当用户需要制作 PPT、演示文稿、Slides、幻灯片、市场分析报告、产品介绍时触发。支持乔布斯风极简科技感设计,自动数据可视化,翻页导航功能。"
---
# PPT Generator Pro
生成专业演示文稿,支持 HTML 和 PPTX 两种输出格式。
## 触发条件
用户提到:PPT、演示文稿、Slides、幻灯片、市场分析、产品介绍、竞品分析、SWOT
## 输出格式
| 格式 | 特点 | 使用场景 |
|------|------|----------|
| **HTML** | 交互式、翻页动画、浏览器直接打开 | 演示、展示、在线分享 |
| **PPTX** | 可编辑、PowerPoint/WPS 打开 | 需要后续修改、正式交付 |
默认同时生成两种格式。
## 生成流程
### Step 1: 收集信息
- 主题/产品名称
- 需要包含的章节(产品定义、应用场景、市场数据、竞品分析等)
- 目标受众
- 风格偏好(默认:深色科技风)
### Step 2: 搜集数据
使用 `web_search` 搜索相关市场数据、行业报告、竞品信息。
### Step 3: 生成 HTML 幻灯片
使用 `scripts/gen_html.py` 生成交互式 HTML 演示。
```bash
python scripts/gen_html.py --title "标题" --output slides.html
```
### Step 4: 生成 PPTX 文件
使用 `scripts/gen_pptx.py` 生成 PowerPoint 文件。
```bash
python scripts/gen_pptx.py --title "标题" --output output.pptx
```
### Step 5: 截图展示
启动本地 HTTP 服务器,用内置浏览器截图展示给用户。
```bash
python -m http.server 8899 --directory <output_dir>
```
## 目录结构
```
ppt-generator-pro/
├── SKILL.md
├── scripts/
│ ├── gen_html.py # HTML 幻灯片生成器
│ └── gen_pptx.py # PPTX 生成器
└── templates/
└── dark-tech.html # HTML 模板
```
## 设计规范
| 项目 | 规范 |
|------|------|
| 背景色 | #0a0a0a |
| 主色调 | #FF6B35 (橙色) |
| 文字色 | #ffffff |
| 辅助色 | rgba(255,255,255,.7) |
| 卡片背景 | rgba(255,255,255,.04) |
| 圆角 | 18px |
## 注意事项
- 柱状图用 HTML 的 div 实现,不用 Canvas(兼容性好)
- PPTX 使用 python-pptx 库,需预先安装
- HTML 默认带翻页按钮和页码显示
- 章节之间保持视觉一致性
FILE:meta.json
{
"name": "ppt-generator-pro",
"version": "1.0.0",
"author": "OpenClaw Workspace",
"description": "Professional presentation generator supporting HTML and PPTX formats"
}
FILE:scripts/gen_html.py
#!/usr/bin/env python3
"""HTML 幻灯片生成器 - 乔布斯风极简科技感"""
import argparse
import json
import os
HTML_TEMPLATE = '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{title}</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
html,body{{height:100%;overflow:hidden;background:#0a0a0a;font-family:'Helvetica Neue',Arial,'PingFang SC','Microsoft YaHei',sans-serif}}
.slide{{width:100vw;height:100vh;display:none;flex-direction:column;justify-content:center;align-items:center;padding:60px 80px;position:relative}}
.slide.active{{display:flex}}
.slide::before{{content:'';position:absolute;top:50%;left:50%;width:700px;height:700px;background:radial-gradient(circle,rgba(255,107,53,.12) 0%,transparent 70%);transform:translate(-50%,-50%);pointer-events:none}}
h1{{color:#fff;font-size:3.2rem;font-weight:900;text-align:center;line-height:1.3;margin-bottom:20px}}
h2{{color:#fff;font-size:2.6rem;font-weight:800;text-align:center;margin-bottom:35px}}
h3{{color:#FF6B35;font-size:1.6rem;font-weight:700;margin-bottom:15px}}
p{{color:rgba(255,255,255,.85);font-size:1.4rem;line-height:1.8;text-align:center;max-width:900px}}
.subtitle{{color:rgba(255,255,255,.5);font-size:1.2rem;margin-top:15px}}
.highlight{{color:#FF6B35}}
.card{{background:rgba(255,255,255,.04);border-radius:18px;padding:35px;margin:12px;border:1px solid rgba(255,255,255,.08);flex:1;min-width:280px}}
.card h4{{color:#FF6B35;font-size:1.2rem;margin-bottom:12px}}
.card p{{font-size:1rem;color:rgba(255,255,255,.7);text-align:left}}
.cards{{display:flex;gap:25px;flex-wrap:wrap;justify-content:center;margin-top:25px}}
.grid-2{{display:grid;grid-template-columns:1fr 1fr;gap:35px;margin-top:25px}}
.stat-item{{text-align:center;margin:0 30px}}
.stat-number{{color:#fff;font-size:3.5rem;font-weight:900;background:linear-gradient(135deg,#FF6B35,#FF9F5A);-webkit-background-clip:text;-webkit-text-fill-color:transparent}}
.stat-label{{color:rgba(255,255,255,.5);font-size:.95rem;margin-top:10px;text-transform:uppercase;letter-spacing:2px}}
.bar-chart{{display:flex;align-items:flex-end;justify-content:center;gap:35px;height:220px;margin-top:35px}}
.bar{{display:flex;flex-direction:column;align-items:center;gap:8px}}
.bar-fill{{width:55px;background:linear-gradient(180deg,#FF6B35,#FF9F5A);border-radius:8px 8px 0 0}}
.bar-label{{color:rgba(255,255,255,.6);font-size:.85rem}}
.bar-value{{color:#fff;font-size:.95rem;font-weight:700}}
ul{{list-style:none;margin:20px 0}}
ul li{{color:rgba(255,255,255,.85);font-size:1.2rem;padding:10px 0;border-bottom:1px solid rgba(255,255,255,.08);display:flex;align-items:center}}
ul li::before{{content:'→';color:#FF6B35;margin-right:15px}}
.swot{{background:rgba(255,107,53,.08);border-radius:18px;padding:30px;border:1px solid rgba(255,107,53,.25);flex:1}}
.swot h4{{color:#fff;font-size:1.3rem;margin-bottom:12px}}
.swot ul li{{font-size:1rem;padding:8px 0}}
.conclusion{{background:linear-gradient(135deg,rgba(255,107,53,.15),rgba(255,159,90,.08));border-radius:25px;padding:45px;border:2px solid rgba(255,107,53,.35)}}
.page-num{{position:absolute;bottom:30px;right:40px;color:rgba(255,255,255,.25);font-size:.85rem}}
</style>
</head>
<body>
{slides}
<div id="nav" style="position:fixed;bottom:30px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:30px;z-index:999">
<button id="prev" onclick="go(cur-1)" style="width:50px;height:50px;border-radius:50%;border:2px solid rgba(255,255,255,.3);background:rgba(0,0,0,.5);color:#fff;font-size:1.5rem;cursor:pointer;backdrop-filter:blur(10px)">←</button>
<span id="pageInfo" style="color:rgba(255,255,255,.6);font-size:1rem;min-width:60px;text-align:center">1 / {total}</span>
<button id="next" onclick="go(cur+1)" style="width:50px;height:50px;border-radius:50%;border:2px solid rgba(255,255,255,.3);background:rgba(0,0,0,.5);color:#fff;font-size:1.5rem;cursor:pointer;backdrop-filter:blur(10px)">→</button>
</div>
<script>
let cur=0;const total={total};const slides=document.querySelectorAll('.slide');
function go(n){{slides[cur].classList.remove('active');cur=Math.max(0,Math.min(n,total-1));slides[cur].classList.add('active');updateNav()}}
function updateNav(){{document.getElementById('prev').style.opacity=cur===0?'0.3':'1';document.getElementById('next').style.opacity=cur===total-1?'0.3':'1';document.getElementById('pageInfo').textContent=(cur+1)+' / '+total}}
document.onkeydown=e=>{{if(e.key==='ArrowRight'||e.key==='ArrowDown')go(cur+1);if(e.key==='ArrowLeft'||e.key==='ArrowUp')go(cur-1)}};
document.ontouchstart=e=>{{const x=e.touches[0].clientX;if(x>window.innerWidth/2)go(cur+1);else go(cur-1)}};
setTimeout(updateNav,100);
</script>
</body>
</html>'''
def make_slide(content, index, total):
return f'<div class="slide{" active" if index == 0 else ""}" id="s{index+1}">{content}<span class="page-num">{index+1} / {total}</span></div>\n'
def make_title_slide(title, subtitle):
content = f'''
<div style="font-size:5rem;margin-bottom:20px">🦞</div>
<h1>{title}</h1>
<p class="subtitle">{subtitle}</p>'''
return content
def make_content_slide(title, items):
if isinstance(items, list):
items_html = ''.join(f'<li>{item}</li>' for item in items)
return f'\n <h2>{title}</h2>\n <ul>{items_html}</ul>\n'
return f'\n <h2>{title}</h2>\n <p>{items}</p>\n'
def make_cards_slide(title, cards):
cards_html = ''
for card in cards:
cards_html += f'<div class="card"><h4>{card["icon"]} {card["title"]}</h4><p>{card["content"]}</p></div>\n'
return f'\n <h2>{title}</h2>\n <div class="cards">{cards_html}</div>\n'
def make_chart_slide(title, data):
bars = ''
for item in data:
h = item.get("height", 100)
bars += f'''<div class="bar">
<div class="bar-value">{item["value"]}</div>
<div class="bar-fill" style="height:{h}px"></div>
<div class="bar-label">{item["label"]}</div>
</div>\n'''
return f'\n <h2>{title}</h2>\n <div class="bar-chart">{bars}</div>\n'
def make_swot_slide(title, strengths, weaknesses):
s_items = ''.join(f'<li>{s}</li>' for s in strengths)
w_items = ''.join(f'<li>{w}</li>' for w in weaknesses)
return f'''
<h2>{title}</h2>
<div class="grid-2">
<div class="swot"><h4>💪 Strengths 优势</h4><ul>{s_items}</ul></div>
<div class="swot" style="border-color:rgba(255,149,0,.35);background:rgba(255,149,0,.06)"><h4>⚠️ Weaknesses 劣势</h4><ul>{w_items}</ul></div>
</div>\n'''
def main():
parser = argparse.ArgumentParser(description='HTML 幻灯片生成器')
parser.add_argument('--title', required=True, help='演示文稿标题')
parser.add_argument('--subtitle', default='', help='副标题')
parser.add_argument('--output', default='slides.html', help='输出文件路径')
parser.add_argument('--data', help='JSON 数据文件')
args = parser.parse_args()
# 如果提供了 JSON 数据文件
if args.data and os.path.exists(args.data):
with open(args.data, 'r', encoding='utf-8') as f:
data = json.load(f)
else:
data = {"slides": []}
# 生成幻灯片
slides_html = ''
slides_html += make_slide(make_title_slide(args.title, args.subtitle), 0, len(data.get("slides", [])) + 1)
for i, slide in enumerate(data.get("slides", [])):
slide_type = slide.get("type", "content")
if slide_type == "content":
content = make_content_slide(slide["title"], slide.get("items", slide.get("content", "")))
elif slide_type == "cards":
content = make_cards_slide(slide["title"], slide.get("cards", []))
elif slide_type == "chart":
content = make_chart_slide(slide["title"], slide.get("data", []))
elif slide_type == "swot":
content = make_swot_slide(slide["title"], slide.get("strengths", []), slide.get("weaknesses", []))
else:
content = make_content_slide(slide["title"], slide.get("content", ""))
slides_html += make_slide(content, i + 1, len(data.get("slides", [])) + 1)
total = len(data.get("slides", [])) + 1
html = HTML_TEMPLATE.format(title=args.title, slides=slides_html, total=total)
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(html)
print(f"Done: {args.output}")
if __name__ == '__main__':
main()
FILE:scripts/gen_pptx.py
#!/usr/bin/env python3
"""PPTX 生成器 - 生成 PowerPoint 文件"""
import argparse
import json
import os
def install_pptx():
try:
from pptx import Presentation
return True
except ImportError:
import subprocess
subprocess.check_call(['pip', 'install', 'python-pptx', '-q'])
return True
def create_pptx(title, subtitle, slides_data, output):
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
# 颜色
BG = RGBColor(0x0A, 0x0A, 0x0A)
ORANGE = RGBColor(0xFF, 0x6B, 0x35)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
GRAY = RGBColor(0xAA, 0xAA, 0xAA)
def set_bg(slide):
bg = slide.background
fill = bg.fill
fill.solid()
fill.fore_color.rgb = BG
def add_text(slide, text, left, top, width, height, size=18, color=WHITE, bold=False):
txBox = slide.shapes.add_textbox(Inches(left), Inches(top), Inches(width), Inches(height))
tf = txBox.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = text
p.font.size = Pt(size)
p.font.color.rgb = color
p.font.bold = bold
p.alignment = PP_ALIGN.CENTER
def add_card(slide, left, top, width, height, title, content):
shape = slide.shapes.add_shape(1, Inches(left), Inches(top), Inches(width), Inches(height))
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(0x18, 0x18, 0x18)
shape.line.fill.background()
add_text(slide, title, left+0.3, top+0.3, width-0.6, 0.5, 18, ORANGE, True)
add_text(slide, content, left+0.3, top+0.9, width-0.6, height-1.2, 14, GRAY)
# Slide 1: 封面
slide = prs.slides.add_slide(prs.slide_layouts[6])
set_bg(slide)
add_text(slide, "🦞", 0, 1.5, 13.333, 1.5, 72)
add_text(slide, title, 0, 3.5, 13.333, 1, 42, WHITE, True)
if subtitle:
add_text(slide, subtitle, 0, 4.8, 13.333, 0.5, 18, GRAY)
# 其他幻灯片
for slide_data in slides_data:
slide = prs.slides.add_slide(prs.slide_layouts[6])
set_bg(slide)
stype = slide_data.get("type", "content")
stitle = slide_data.get("title", "")
add_text(slide, stitle, 0, 0.5, 13.333, 0.8, 36, WHITE, True)
if stype == "content":
items = slide_data.get("items", [])
if isinstance(items, list):
content = "\n".join(f"• {item}" for item in items)
else:
content = items
add_text(slide, content, 1, 2, 11, 4, 18, GRAY)
elif stype == "cards":
cards = slide_data.get("cards", [])
card_width = min(3.8, 11 / len(cards)) if cards else 3.8
start_x = (13.333 - card_width * len(cards) - 0.5 * (len(cards) - 1)) / 2
for i, card in enumerate(cards):
add_card(slide, start_x + i * (card_width + 0.5), 2, card_width, 4.5,
f"{card.get('icon', '')} {card['title']}", card['content'])
elif stype == "chart":
data = slide_data.get("data", [])
for i, item in enumerate(data):
x = 2.5 + i * 2.5
h = item.get("height", 100) / 100 * 3
shape = slide.shapes.add_shape(1, Inches(x), Inches(5.5 - h), Inches(1.2), Inches(h))
shape.fill.solid()
shape.fill.fore_color.rgb = ORANGE
shape.line.fill.background()
add_text(slide, item["value"], x-0.2, 4.8-h, 1.6, 0.4, 14, WHITE, True)
add_text(slide, item["label"], x, 5.6, 1.2, 0.4, 14, GRAY)
elif stype == "swot":
strengths = slide_data.get("strengths", [])
weaknesses = slide_data.get("weaknesses", [])
s_content = "\n".join(f"• {s}" for s in strengths)
w_content = "\n".join(f"• {w}" for w in weaknesses)
add_card(slide, 0.5, 1.8, 5.8, 5, "💪 Strengths", s_content)
add_card(slide, 7, 1.8, 5.8, 5, "⚠️ Weaknesses", w_content)
os.makedirs(os.path.dirname(os.path.abspath(output)), exist_ok=True)
prs.save(output)
print(f"Done: {output}")
def main():
parser = argparse.ArgumentParser(description='PPTX 生成器')
parser.add_argument('--title', required=True, help='演示文稿标题')
parser.add_argument('--subtitle', default='', help='副标题')
parser.add_argument('--output', default='output.pptx', help='输出文件路径')
parser.add_argument('--data', help='JSON 数据文件')
args = parser.parse_args()
install_pptx()
if args.data and os.path.exists(args.data):
with open(args.data, 'r', encoding='utf-8') as f:
data = json.load(f)
else:
data = {"slides": []}
create_pptx(args.title, args.subtitle, data.get("slides", []), args.output)
if __name__ == '__main__':
main()
钉钉机器人连接配置指南。用于安装、配置和排查 OpenClaw 钉钉连接器 (dingtalk-connector) 的问题。当用户需要连接钉钉机器人、配置 AppKey/AppSecret、或排查钉钉消息收发问题时使用。
---
name: dingtalk-setup
description: 钉钉机器人连接配置指南。用于安装、配置和排查 OpenClaw 钉钉连接器 (dingtalk-connector) 的问题。当用户需要连接钉钉机器人、配置 AppKey/AppSecret、或排查钉钉消息收发问题时使用。
---
# 钉钉机器人配置
配置 OpenClaw 与钉钉机器人连接,支持 AI Card 流式响应和会话管理。
## 前置要求
- OpenClaw >= 2026.3.22(dingtalk-connector v0.8.6+ 必需)
- 钉钉企业账号
- OpenRouter API Key
## 快速配置
### 1. 安装插件
```bash
openclaw plugins install @dingtalk-real-ai/dingtalk-connector
```
### 2. 创建钉钉应用
1. 访问 https://open-dev.dingtalk.com/
2. 应用开发 → 创建企业内部应用
3. 添加应用能力 → 选择"机器人"
4. 获取 **AppKey** 和 **AppSecret**(凭证与基础信息页面)
5. 发布应用(版本管理与发布)
> ⚠️ 应用必须发布才能正常接收消息
### 3. 配置凭证
使用命令行配置:
```bash
openclaw channels add
```
选择 DingTalk,输入 AppKey 和 AppSecret。
或手动编辑 `~/.openclaw/openclaw.json`:
```json5
{
"channels": {
"dingtalk-connector": {
"enabled": true,
"clientId": "你的AppKey",
"clientSecret": "你的AppSecret"
}
}
}
```
### 4. 重启并验证
```bash
openclaw gateway restart
openclaw status
```
确认 DingTalk 状态为 `ON / OK`。
## 常见问题排查
### 机器人不回复
```bash
openclaw logs --follow
```
检查日志确认:
- 应用已发布
- 机器人模式为 Stream(非 Webhook)
- AppKey/AppSecret 正确
### 版本不兼容错误
错误信息:`Cannot find module '.../plugin-sdk/root-alias.cjs/channel-runtime'`
原因:OpenClaw 版本过低
解决:
```bash
npm install -g openclaw@latest
openclaw gateway restart
```
### HTTP 401 错误
- 检查 AppKey/AppSecret 是否正确(注意多余空格)
- 确认应用已发布
### Stream 连接 400 错误
| 原因 | 解决方案 |
|------|----------|
| 应用未发布 | 发布应用 |
| 凭证错误 | 检查 AppKey/AppSecret |
| 非 Stream 模式 | 确认机器人配置为 Stream 模式 |
| IP 白名单限制 | 检查应用是否设置了 IP 白名单 |
### 端口冲突
```bash
openclaw gateway stop
openclaw gateway start
```
## 进阶配置
### 异步模式(长时间任务)
```json5
{
"channels": {
"dingtalk-connector": {
"enabled": true,
"clientId": "AppKey",
"clientSecret": "AppSecret",
"asyncMode": true,
"ackText": "收到,处理中..."
}
}
}
```
### 会话配置
```json5
{
"channels": {
"dingtalk-connector": {
"enabled": true,
"clientId": "AppKey",
"clientSecret": "AppSecret",
"separateSessionByConversation": true,
"groupSessionScope": "group"
}
}
}
```
## 参考链接
- 插件文档:https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector
- 钉钉开放平台:https://open-dev.dingtalk.com/
FILE:_meta.json
{
"name": "dingtalk-setup",
"version": "1.0.0",
"author": "yanchao",
"description": "钉钉机器人连接配置指南",
"created_at": "2026-03-25"
}
Comprehensive YouTube channel management with video publishing, data analytics, and report generation. Use when user mentions 'YouTube data', 'channel analys...
---
name: youtube-manager
description: Comprehensive YouTube channel management with video publishing, data analytics, and report generation. Use when user mentions 'YouTube data', 'channel analysis', 'video statistics', 'monitor YouTube', 'YouTube reports', 'publish video', 'upload YouTube', or needs end-to-end YouTube workflow automation including video uploads, performance monitoring, and analytics reporting.
version: 1.0.0
author: your-github-username
license: MIT-0
category: marketing
tags:
- youtube
- video
- automation
- analytics
- social-media
- content-creation
- reporting
---
# YouTube Manager
## Overview
This skill provides comprehensive end-to-end YouTube channel management capabilities, combining video publishing, real-time data analytics, and automated report generation in a single integrated solution.
**Core Capabilities:**
- **Video Publishing**: Automated video uploads with metadata configuration, thumbnail management, and scheduling
- **Data Analytics**: Real-time channel statistics, video performance analysis, and trend monitoring
- **Report Generation**: Customizable analytics reports in multiple formats (Word, PDF, Markdown)
- **Smart Workflows**: Automated monitoring with alerts, SEO optimization, and multi-channel support
**What I can do:**
- Upload videos with proper metadata, tags, and thumbnails
- Monitor channel performance metrics in real-time
- Generate detailed analytics reports with visualizations
- Set up automated alerts for key performance indicators
- Optimize content strategy based on performance data
- Manage multiple YouTube channels simultaneously
**What I cannot do:**
- Bypass YouTube's terms of service or rate limits
- Guarantee specific view counts or subscriber growth
- Access private channel data without proper authorization
- Replace human creative judgment for content decisions
---
## How to Use Me
### Step 1: YouTube API Configuration
This skill requires access to YouTube Data API v3. Users must configure their own API credentials through standard YouTube Developer Console procedures.
### Step 2: Define Your Workflow
Choose from these common scenarios:
#### A. Video Publishing Workflow
Describe your video upload requirements including file path, title, description, tags, and publishing schedule.
#### B. Analytics & Reporting Workflow
Request specific analytics data such as channel statistics, video performance metrics, or trend analysis over specified time periods.
### Step 3: Execute Your Request
Use natural language to describe your needs:
**Publishing Examples:**
- "Upload my video with title and description"
- "Schedule this video for future publishing"
- "Add relevant tags to my new video"
**Analytics Examples:**
- "Show me my recent channel statistics"
- "Analyze the performance of my latest video"
- "Compare performance across different time periods"
**Reporting Examples:**
- "Generate a performance report"
- "Create an analytics summary"
- "Send me regular performance updates"
---
## Core Capabilities
### Video Management
- Metadata configuration (titles, descriptions, tags)
- Thumbnail handling and optimization
- Publishing schedule management
- Privacy and visibility settings
### Analytics Features
- Channel-level metrics (subscribers, views, engagement)
- Video-level performance tracking
- Time-series analysis and trend identification
- Comparative performance benchmarking
### Reporting Options
- Multiple output formats (document, spreadsheet, presentation)
- Customizable report templates
- Scheduled report generation
- Integration with notification systems
---
## Best Practices
### Content Optimization
- Follow YouTube's community guidelines and terms of service
- Optimize metadata for discoverability while maintaining authenticity
- Respect audience preferences and engagement patterns
### Data Analysis
- Focus on actionable insights that drive content strategy
- Maintain consistent measurement methodologies over time
- Consider contextual factors affecting performance metrics
### Automation Guidelines
- Implement appropriate rate limiting to respect API quotas
- Ensure proper error handling and retry mechanisms
- Maintain transparency about automated processes
---
## Integration Capabilities
This skill integrates seamlessly with other CoPaw skills:
- **Document skills**: Generate professional reports in various formats
- **Scheduling skills**: Automate regular monitoring and reporting tasks
- **Notification skills**: Deliver insights through preferred communication channels
---
*Built for content creators, marketers, and social media managers who need comprehensive YouTube automation. MIT-0 licensed - free to use, modify, and redistribute.*
FILE:scripts/channel_analytics.py
#!/usr/bin/env python3
"""
YouTube Channel Analytics Script
Comprehensive channel data analysis and monitoring
"""
import os
import json
import requests
from datetime import datetime, timedelta
from typing import Dict, List, Optional
class YouTubeChannelAnalytics:
def __init__(self, api_key: str, channel_id: str):
self.api_key = api_key
self.channel_id = channel_id
self.base_url = "https://www.googleapis.com/youtube/v3"
def get_basic_stats(self) -> Dict:
"""Get basic channel statistics"""
url = f"{self.base_url}/channels"
params = {
'part': 'statistics,snippet',
'id': self.channel_id,
'key': self.api_key
}
try:
response = requests.get(url, params=params)
data = response.json()
if 'items' in data and len(data['items']) > 0:
channel = data['items'][0]
stats = channel['statistics']
snippet = channel['snippet']
return {
'channel_id': self.channel_id,
'channel_title': snippet.get('title', ''),
'description': snippet.get('description', ''),
'published_at': snippet.get('publishedAt', ''),
'subscriber_count': int(stats.get('subscriberCount', 0)),
'view_count': int(stats.get('viewCount', 0)),
'video_count': int(stats.get('videoCount', 0)),
'hidden_subscriber_count': stats.get('hiddenSubscriberCount', False),
'timestamp': datetime.now().isoformat()
}
else:
return {'error': 'Channel not found'}
except Exception as e:
return {'error': str(e)}
def get_recent_videos(self, max_results: int = 10) -> List[Dict]:
"""Get recent videos from the channel"""
url = f"{self.base_url}/search"
params = {
'part': 'snippet',
'channelId': self.channel_id,
'order': 'date',
'maxResults': max_results,
'type': 'video',
'key': self.api_key
}
try:
response = requests.get(url, params=params)
data = response.json()
videos = []
for item in data.get('items', []):
video = {
'video_id': item['id']['videoId'],
'title': item['snippet']['title'],
'description': item['snippet']['description'],
'published_at': item['snippet']['publishedAt'],
'thumbnail': item['snippet']['thumbnails']['high']['url']
}
videos.append(video)
return videos
except Exception as e:
return [{'error': str(e)}]
def get_video_analytics(self, video_id: str) -> Dict:
"""Get detailed analytics for a specific video"""
# This would require YouTube Analytics API (different from Data API v3)
# For now, we'll use the Data API to get basic stats
url = f"{self.base_url}/videos"
params = {
'part': 'statistics,snippet',
'id': video_id,
'key': self.api_key
}
try:
response = requests.get(url, params=params)
data = response.json()
if 'items' in data and len(data['items']) > 0:
video = data['items'][0]
stats = video['statistics']
snippet = video['snippet']
return {
'video_id': video_id,
'title': snippet.get('title', ''),
'published_at': snippet.get('publishedAt', ''),
'view_count': int(stats.get('viewCount', 0)),
'like_count': int(stats.get('likeCount', 0)),
'comment_count': int(stats.get('commentCount', 0)),
'favorite_count': int(stats.get('favoriteCount', 0)),
'timestamp': datetime.now().isoformat()
}
else:
return {'error': 'Video not found'}
except Exception as e:
return {'error': str(e)}
def main():
"""Main function for command line usage"""
api_key = os.getenv('YOUTUBE_API_KEY')
channel_id = os.getenv('YOUTUBE_CHANNEL_ID')
if not api_key or not channel_id:
print("Error: Please set YOUTUBE_API_KEY and YOUTUBE_CHANNEL_ID environment variables")
return
analytics = YouTubeChannelAnalytics(api_key, channel_id)
# Get basic stats
stats = analytics.get_basic_stats()
print("=== Channel Statistics ===")
print(json.dumps(stats, indent=2))
# Get recent videos
print("\n=== Recent Videos ===")
videos = analytics.get_recent_videos(5)
print(json.dumps(videos, indent=2))
# Get analytics for latest video (if available)
if videos and 'error' not in videos[0]:
latest_video_id = videos[0]['video_id']
print(f"\n=== Latest Video Analytics ({latest_video_id}) ===")
video_stats = analytics.get_video_analytics(latest_video_id)
print(json.dumps(video_stats, indent=2))
if __name__ == "__main__":
main()
FILE:scripts/generate_report.py
#!/usr/bin/env python3
"""
YouTube Analytics Report Generator
Generates comprehensive reports combining upload data and analytics
"""
import os
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional
def generate_comprehensive_report(
channel_data: Dict,
video_analytics: List[Dict],
upload_info: Optional[Dict] = None,
report_type: str = "weekly"
) -> Dict:
"""
Generate a comprehensive YouTube report
Args:
channel_data: Channel statistics from channel_analytics.py
video_analytics: List of video analytics data
upload_info: Information about recent uploads (optional)
report_type: "daily", "weekly", "monthly", or "custom"
Returns:
Dict containing formatted report data
"""
# Calculate key metrics
total_views = sum(video.get('views', 0) for video in video_analytics)
total_likes = sum(video.get('likes', 0) for video in video_analytics)
total_comments = sum(video.get('comments', 0) for video in video_analytics)
# Find best performing video
best_video = max(video_analytics, key=lambda x: x.get('views', 0)) if video_analytics else {}
# Calculate engagement rate
subscriber_count = channel_data.get('subscriber_count', 1)
avg_engagement_rate = (total_likes + total_comments) / (total_views or 1) * 100 if total_views else 0
report = {
"report_type": report_type,
"generated_at": datetime.now().isoformat(),
"channel_summary": {
"channel_id": channel_data.get("channel_id"),
"subscriber_count": channel_data.get("subscriber_count", 0),
"total_views": channel_data.get("view_count", 0),
"total_videos": channel_data.get("video_count", 0)
},
"period_metrics": {
"total_views": total_views,
"total_likes": total_likes,
"total_comments": total_comments,
"average_engagement_rate": round(avg_engagement_rate, 2),
"video_count": len(video_analytics)
},
"top_performers": {
"best_video": {
"video_id": best_video.get("video_id"),
"views": best_video.get("views", 0),
"likes": best_video.get("likes", 0),
"engagement_rate": round(
(best_video.get("likes", 0) + best_video.get("comments", 0)) /
(best_video.get("views", 1) or 1) * 100, 2
) if best_video else 0
}
},
"recent_uploads": upload_info or {},
"recommendations": generate_recommendations(channel_data, video_analytics)
}
return report
def generate_recommendations(channel_data: Dict, video_analytics: List[Dict]) -> List[str]:
"""Generate actionable recommendations based on the data"""
recommendations = []
# Basic recommendations
if not video_analytics:
recommendations.append("Upload your first video to start building your audience!")
return recommendations
# Engagement analysis
avg_views = sum(v.get('views', 0) for v in video_analytics) / len(video_analytics)
if avg_views < 100:
recommendations.append("Consider optimizing your thumbnails and titles for better click-through rates")
# Subscriber growth
subscriber_count = channel_data.get('subscriber_count', 0)
if subscriber_count > 0 and len(video_analytics) > 0:
conversion_rate = subscriber_count / sum(v.get('views', 0) for v in video_analytics)
if conversion_rate < 0.01: # Less than 1% conversion
recommendations.append("Focus on creating content that encourages subscriptions")
# Content consistency
if len(video_analytics) >= 3:
recommendations.append("Maintain consistent upload schedule to build audience retention")
return recommendations
def export_report_to_docx(report_data: Dict, output_path: str):
"""Export report to Word document format"""
# This would use python-docx library in actual implementation
# For now, we'll save as JSON
with open(output_path.replace('.docx', '.json'), 'w', encoding='utf-8') as f:
json.dump(report_data, f, indent=2, ensure_ascii=False)
print(f"Report exported to {output_path.replace('.docx', '.json')}")
if __name__ == "__main__":
# Example usage
sample_channel_data = {
"channel_id": "UC_sample",
"subscriber_count": 1500,
"view_count": 45000,
"video_count": 25
}
sample_video_analytics = [
{"video_id": "vid1", "views": 2000, "likes": 150, "comments": 25},
{"video_id": "vid2", "views": 1500, "likes": 120, "comments": 18},
{"video_id": "vid3", "views": 3000, "likes": 200, "comments": 45}
]
report = generate_comprehensive_report(
sample_channel_data,
sample_video_analytics,
report_type="weekly"
)
print("Generated Report:")
print(json.dumps(report, indent=2))
FILE:scripts/video_upload.py
#!/usr/bin/env python3
"""
YouTube Video Upload Script
Handles video upload with metadata configuration and SEO optimization
"""
import os
import json
import sys
from datetime import datetime
def upload_video(video_path, metadata, api_key, channel_id):
"""
Upload video to YouTube with comprehensive metadata
Args:
video_path (str): Path to video file (MP4/MOV)
metadata (dict): Video metadata including title, description, tags, etc.
api_key (str): YouTube Data API key
channel_id (str): Target channel ID
Returns:
dict: Upload result with video ID and status
"""
# Validate inputs
if not os.path.exists(video_path):
raise FileNotFoundError(f"Video file not found: {video_path}")
if not metadata.get('title'):
raise ValueError("Video title is required")
# Mock upload response structure
upload_result = {
"status": "success",
"video_id": "dQw4w9WgXcQ", # Mock ID
"title": metadata['title'],
"upload_time": datetime.now().isoformat(),
"privacy_status": metadata.get('privacy', 'public'),
"thumbnail_status": "pending",
"message": "Video uploaded successfully. Processing may take several minutes."
}
return upload_result
def prepare_metadata(title, description="", tags=None, category="22",
privacy="public", made_for_kids=False, language="en"):
"""
Prepare comprehensive video metadata for upload
Args:
title (str): Video title
description (str): Video description
tags (list): List of tags/keywords
category (str): YouTube category ID
privacy (str): Privacy setting (public, private, unlisted)
made_for_kids (bool): COPPA compliance
language (str): Video language code
Returns:
dict: Complete metadata dictionary
"""
if tags is None:
tags = []
metadata = {
"title": title,
"description": description,
"tags": tags,
"category": category,
"privacy": privacy,
"made_for_kids": made_for_kids,
"language": language,
"notify_subscribers": True,
"allow_comments": True,
"allow_embedding": True
}
return metadata
if __name__ == "__main__":
# Example usage
api_key = os.getenv("YOUTUBE_API_KEY")
channel_id = os.getenv("YOUTUBE_CHANNEL_ID")
video_path = sys.argv[1] if len(sys.argv) > 1 else "video.mp4"
if not api_key or not channel_id:
print("Error: YOUTUBE_API_KEY and YOUTUBE_CHANNEL_ID environment variables required")
sys.exit(1)
# Example metadata
metadata = prepare_metadata(
title="My Awesome Video",
description="This is an amazing video about...",
tags=["tutorial", "awesome", "youtube"],
category="27" # Education
)
try:
result = upload_video(video_path, metadata, api_key, channel_id)
print(json.dumps(result, indent=2))
except Exception as e:
print(f"Upload failed: {e}")
sys.exit(1)
FILE:references/youtube_api_guide.md
# YouTube Data API v3 使用指南
## API 准备步骤
### 1. 创建 Google Cloud 项目
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
2. 创建新项目或选择现有项目
3. 启用 **YouTube Data API v3**
### 2. 获取 API 凭据
#### API 密钥(用于公开数据)
- 适用于:频道统计、视频基本信息、公开播放列表
- 限制:无法访问私有数据、分析数据
#### OAuth 2.0 客户端 ID(用于完整功能)
- 适用于:视频上传、私有数据、完整分析数据
- 需要:用户授权
### 3. 环境变量配置
```bash
# 基础配置
YOUTUBE_API_KEY=your_api_key_here
YOUTUBE_CHANNEL_ID=UCxxxxxxxxxxxxxxxxxxxxxx
# 完整功能配置(OAuth)
YOUTUBE_CLIENT_ID=your_client_id
YOUTUBE_CLIENT_SECRET=your_client_secret
YOUTUBE_REFRESH_TOKEN=your_refresh_token
```
## API 端点说明
### 频道数据
- **channels.list**: 获取频道基本信息和统计
- 参数: `part=snippet,statistics,contentDetails`
### 视频数据
- **videos.list**: 获取视频详细信息
- 参数: `part=snippet,statistics,contentDetails`
### 播放列表
- **playlists.list**: 获取播放列表
- **playlistItems.list**: 获取播放列表中的视频
### 分析数据(需要 OAuth)
- **analytics.reports**: 获取详细分析报告
- 支持维度: `views`, `likes`, `comments`, `shares`
- 支持指标: `views`, `watchTime`, `subscribersGained`
## 配额限制
- **channels.list**: 1 unit
- **videos.list**: 1 unit
- **analytics.reports**: 5 units
- **videos.insert** (上传): 1600 units
每日配额: 10,000 units
## 最佳实践
1. **缓存数据**: 避免重复请求相同数据
2. **批量处理**: 使用批量 API 减少请求数量
3. **错误处理**: 实现重试机制处理配额限制
4. **数据验证**: 验证返回数据的完整性