lq-259

xinjingdaily-slideshow

从 Telegram 公共频道 xinjingdaily 抓取图片,百度内容审核,生成竖屏幻灯片视频并上传到 WebDAV 和 B 站。Use ONLY when the user asks about xinjingdaily, 搬石, Telegram content fetching, slideshow generation, Baidu moderation, WebDAV upload, or Bilibili publishing.

lq-259 0 Updated 2w ago

Resources

8
GitHub

Install

npx skillscat add lq-259/banshit

Install via the SkillsCat registry.

SKILL.md

Xinjingdaily Slideshow Pipeline

从 Telegram 公共频道 t.me/xinjingdaily(心惊报)抓取纯图内容,百度内容审核、过滤广告和 NSFW,自动制作竖屏 9:16 幻灯片视频(90-120s),最后上传到 B 站。

首次部署

# 1. 克隆仓库
git clone <your-repo-url> && cd <project-dir>

# 2. 创建虚拟环境并安装依赖
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

# 3. 安装系统依赖
#    - ffmpeg (视频编码)
#    - chromium / google-chrome (B 站投稿)
#    - Xvfb (虚拟显示,Linux 无桌面环境必需)
#    - agent-browser (npm 全局安装)
sudo apt install ffmpeg chromium xvfb  # Debian/Ubuntu
npm install -g agent-browser

# 4. 创建环境变量配置
cp .env.example .env
# 编辑 .env 填入你的 TG_API_ID, TG_API_HASH, BAIDU_AK, BAIDU_SK

# 5. 创建 B 站投稿配置
cp config/browser_bilibili_config.example.json config/browser_bilibili_config.json
# 编辑 browser_bilibili_config.json 填入你的 Chrome profile 路径和 B 站账号信息

# 6. 创建 WebDAV 配置(可选)
cp config/webdav_config.example.json config/webdav_config.json

# 7. 首次登录 Telegram
set -a && source .env && set +a
.venv/bin/python src/fetch_xinjingdaily.py --date 2026-01-01 --limit 1
# 按提示完成 Telegram 首次登录

# 8. 首次登录 B 站
# 启动 Chrome 后打开 bilibili.com 扫码登录,cookie 会自动保存到 profile

核心原则

  1. 每个步骤的环境变量必须通过 set -a && source .env && set +a 加载。
  2. 封面和标题必须在幻灯片生成之前设置,因为 create_slideshow.py 读取 title.txt
  3. 标题不得重复使用过去日期用过的标题,避免 B 站判定重复内容。
  4. 标题必须口语化,15-28 字符,不含"心惊报"/"xinjingdaily"/"Telegram"。
  5. B 站浏览器必须用 Xvfb + 非 headless Chrome,确保 cookie 长存(6 个月)。
  6. 每次跑完流程必须刷新 cookie(访问 bilibili.com)并保存 profile 后再关闭浏览器。
  7. 创作声明固定为"内容无需标注"(creationStatementId = -1)。
  8. 标签固定为:搞笑、沙雕图、每日趣图、生活记录。
  9. 筛选阶段优先图文内容(截图、段子图、对白图、文字梗),避免普通风景/自拍。
  10. 默认每天选择 25-35 张图片,无人值守时取 top 30。

架构

t.me/xinjingdaily (Telegram)
        |
        v
fetch_xinjingdaily.py -> dates/{DATE}/images/ + image_manifest.json
        |
        v
prepare_image_screening.py prepare -> screening_candidates.txt + 九宫格预览
        |
        v
prepare_image_screening.py apply -> 保留入选图片,其余移入 screening_pool/
        |
        v
baidu_moderate.py -> 百度审核,不合规移入 rejected/
        |
        v
prepare_image_order.py -> image_order.txt
        |
        v
save_cover_title.py -> cover.jpg + title.txt
        |
        v
create_slideshow.py -> output/slideshow_*.mp4
        |
        v
webdav_upload.py -> WebDAV 远端
        |
        v
browser_bilibili_upload.py -> B 站投稿

项目配置

部署前定义以下变量(可写入 ~/.bashrc 或每次运行时设置):

# 项目根目录
export PROJECT_ROOT="/path/to/your/project"

# Python 虚拟环境
export PYTHON="${PROJECT_ROOT}/.venv/bin/python"

# Chrome / Chromium 可执行文件
export CHROME_BIN="chromium"  # 或 "google-chrome-stable"

# B 站 Chrome profile 目录(浏览器登录态)
export BILI_PROFILE="/path/to/chrome/profile"

# agent-browser 会话配置
export BILI_SESSION="bili-account-a"
export BILI_PORT="9222"

# 浏览器操作别名
alias AB="agent-browser --session ${BILI_SESSION} --cdp ${BILI_PORT}"

目录结构

${PROJECT_ROOT}/
├── SKILL.md                       # 本文档
├── .env.example                   # 环境变量模板
├── .gitignore
├── requirements.txt
├── config/
│   ├── browser_bilibili_config.example.json
│   └── webdav_config.example.json
├── src/
│   ├── fetch_xinjingdaily.py
│   ├── prepare_image_screening.py
│   ├── baidu_moderate.py
│   ├── prepare_image_order.py
│   ├── save_cover_title.py
│   ├── create_slideshow.py
│   ├── webdav_upload.py
│   ├── browser_bilibili_upload.py
│   └── date_context.py
├── output/
│   └── bg_music.flac              # 背景音乐(需自行准备)
├── dates/
│   └── {YYYY-MM-DD}/              # 每日数据,自动创建
│       ├── images/                ← 原始图片
│       ├── videos/                ← 原始视频
│       ├── archived/              ← 已使用的图片
│       ├── rejected/              ← 审核不合规
│       ├── pending/               ← 待审核
│       ├── screening_pool/        ← 未被选中的图片
│       ├── output/                ← 生成的幻灯片视频
│       ├── image_manifest.json
│       ├── image_order.txt
│       ├── title.txt
│       └── cover.jpg
└── xinjingdaily_session.session   ← Telegram 登录态

环境变量

.env 文件模板(参考 .env.example):

TG_API_ID=your_telegram_api_id
TG_API_HASH=your_telegram_api_hash
TG_PHONE=+86your_phone_number
TG_SESSION=xinjingdaily_session
BAIDU_AK=your_baidu_api_key
BAIDU_SK=your_baidu_secret_key

每次运行脚本前加载:

cd "${PROJECT_ROOT}" && set -a && source .env && set +a

B 站配置

config/browser_bilibili_config.json

{
  "default_account": "account-a",
  "accounts": {
    "account-a": {
      "session": "bili-account-a",
      "cdp_port": 9222,
      "profile_dir": "/path/to/chrome/profile",
      "expected_account": {
        "uname": "your_bilibili_name",
        "mid": 0000000000
      }
    }
  },
  "title_template": "{date} 今日份离谱趣图合集",
  "desc": "每日趣图合集,素材来自公开频道整理。",
  "tags": ["搞笑", "沙雕图", "每日趣图", "生活记录"],
  "creation_statement": "内容无需标注",
  "upload_url": "https://member.bilibili.com/platform/upload/video/frame"
}

浏览器启动

检查是否已运行:

python3 -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:${BILI_PORT}/json/version', timeout=5).read().decode()[:80])"

如果未运行,启动 Xvfb + Chrome:

nohup Xvfb :99 -screen 0 1920x1080x24 &>/tmp/xvfb.log &
sleep 2
DISPLAY=:99 ${CHROME_BIN} \
  --remote-debugging-port="${BILI_PORT}" \
  --user-data-dir="${BILI_PROFILE}" \
  --disable-blink-features=AutomationControlled \
  --no-sandbox --disable-gpu \
  &>/tmp/chrome.log &
sleep 5

必须使用非 headless 模式:headless 模式下 User-Agent 含 HeadlessChrome,B 站 cookie 有效期仅数小时;非 headless 可达 6 个月。

完整运行流程

以下命令均使用 ${DATE} 变量,运行前先设置:

export DATE="2026-07-01"  # 或省略 --date 参数,脚本默认使用北京时间昨天

Step 1: 抓取 Telegram 内容

cd "${PROJECT_ROOT}" && set -a && source .env && set +a && ${PYTHON} src/fetch_xinjingdaily.py --date ${DATE}
  • 默认拉取北京时间前一天的消息媒体
  • 过滤 NSFW 和广告(正文 >50 字)
  • 图片 -> dates/${DATE}/images/,视频 -> dates/${DATE}/videos/
  • 元数据 -> dates/${DATE}/image_manifest.json

常见问题:部分图片下载失败产生 0 字节文件,会导致后续 prepare 报错 cannot identify image file。删除损坏文件后重试:

rm -f "${PROJECT_ROOT}/dates/${DATE}/images/img_XXXXXX.jpg"

Step 2: 图片筛选

生成候选清单:

cd "${PROJECT_ROOT}" && ${PYTHON} src/prepare_image_screening.py prepare --date ${DATE}

产物:

  • screening_candidates.txt:所有图片的候选清单
  • screening_sheets/screening_sheet_*.jpg:九宫格预览图
  • screening_prompt.md:筛选任务说明
  • screened_candidates.txt:agent 填写入选名单

自动选择 top 30(无人值守):

cd "${PROJECT_ROOT}" && ${PYTHON} -c "
from pathlib import Path
d = Path('dates/${DATE}')
lines = [l for l in (d/'screening_candidates.txt').read_text().splitlines() if l.strip() and not l.startswith('#')]
sel = [l.split('\t')[1] for l in lines[:30]]
(d/'screened_candidates.txt').write_text('\n'.join(sel)+'\n')
print(f'Selected {len(sel)}')
"

应用筛选:

cd "${PROJECT_ROOT}" && ${PYTHON} src/prepare_image_screening.py apply --date ${DATE}

Step 3: 百度内容审核

cd "${PROJECT_ROOT}" && set -a && source .env && set +a && ${PYTHON} src/baidu_moderate.py --date ${DATE}
  • 仅排除 conclusion == "不合规" && conclusionType == 2
  • 合规疑似unknown 均保留

Step 4: 排序

cd "${PROJECT_ROOT}" && ${PYTHON} src/prepare_image_order.py --date ${DATE}

生成 dates/${DATE}/image_order.txt,一行一个文件名。

Step 5: 封面和标题(必须在幻灯片之前)

cd "${PROJECT_ROOT}" && ${PYTHON} src/save_cover_title.py --date ${DATE} --cover dates/${DATE}/images/img_XXXXXX.jpg --title "短视频标题"

标题规则:

  • 15-28 字符,口语化
  • 不重复使用过去日期的标题
  • 不含"心惊报"/"xinjingdaily"/"Telegram"

Step 6: 生成幻灯片视频

cd "${PROJECT_ROOT}" && ${PYTHON} src/create_slideshow.py --date ${DATE}

输出 1080×1920 MP4,淡入淡出 0.3s,总时长 90-120s。

Step 7: 上传 WebDAV(可选)

cd "${PROJECT_ROOT}" && ${PYTHON} src/webdav_upload.py --date ${DATE}

Step 8: B 站投稿

cd "${PROJECT_ROOT}" && ${PYTHON} src/browser_bilibili_upload.py --date ${DATE} --account account-a --publish

脚本自动完成:

  1. 验证 Chrome profile 和 B 站账号
  2. 上传视频
  3. 填写元数据(标题、描述、标签、创作声明)
  4. 上传自定义封面
  5. 预发布校验(validate_publish_ready
  6. 点击立即投稿

投稿脚本关键实现

  • 创作声明:通过 Vue 组件 setter 设置 selectedStatementId = -1,不依赖 DOM 点击
  • 标签:直接操作 vm.$data.custom_tags 数组
  • 封面编辑器:多次重试关闭,处理多种弹窗
  • 投稿按钮:3 次重试 + JS 兜底点击
  • 定时发布:自动清理 dtime/premiere/isShowTimeReleaseDialog

Step 9: 验证稿件

AB open "https://member.bilibili.com/platform/upload-manager/article" && sleep 5
AB eval "Array.from(document.querySelectorAll('.common-article-card, .article-card, .card, .item, .article-item')).map(e=>e.innerText.slice(0,120)).slice(0,3)"

确认稿件状态为 转码中 / 审核中

Step 10: 刷新 Cookie 并保存 Profile

AB open "https://www.bilibili.com" && sleep 3
AB open "about:blank" && sleep 2
AB close --all
pkill -f chromium
pkill -f Xvfb

违规图片处理

B 站审核退回稿件时的处理流程:

  1. 在稿件管理找到被退回的稿件,查看违规时间点
  2. 根据 image_order.txt 计算违规图片(每张约 4.3s,总时长 116s ÷ 27 张)
  3. archived/ 删除违规图片
  4. 更新 image_order.txt 移除对应行
  5. archived/ 中剩余图片复制回 images/
  6. 重新执行 Step 4-10
cd "${PROJECT_ROOT}"

# 删除违规图片
rm -f dates/${DATE}/archived/img_XXXXXX.jpg

# 还原剩余图片
cp dates/${DATE}/archived/*.jpg dates/${DATE}/images/

# 重新生成并投稿
${PYTHON} src/prepare_image_order.py --date ${DATE}
${PYTHON} src/create_slideshow.py --date ${DATE}
${PYTHON} src/browser_bilibili_upload.py --date ${DATE} --account account-a --publish

一键发布流程

用户说"跑一遍流程"时执行:

# ===== 配置变量 =====
export DATE="2026-07-01"
export PROJECT_ROOT="/path/to/project"
export PYTHON="${PROJECT_ROOT}/.venv/bin/python"
export CHROME_BIN="chromium"
export BILI_PROFILE="/path/to/chrome/profile"
export BILI_SESSION="bili-account-a"
export BILI_PORT="9222"
alias AB="agent-browser --session ${BILI_SESSION} --cdp ${BILI_PORT}"

cd "${PROJECT_ROOT}"

# ===== 加载环境变量 =====
set -a && source .env && set +a

# ===== 启动浏览器 =====
# 检查是否已运行
python3 -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:${BILI_PORT}/json/version', timeout=5).read().decode()[:80])" 2>/dev/null || {
  nohup Xvfb :99 -screen 0 1920x1080x24 &>/tmp/xvfb.log &
  sleep 2
  DISPLAY=:99 ${CHROME_BIN} \
    --remote-debugging-port="${BILI_PORT}" \
    --user-data-dir="${BILI_PROFILE}" \
    --disable-blink-features=AutomationControlled \
    --no-sandbox --disable-gpu \
    &>/tmp/chrome.log &
  sleep 5
}

# ===== 抓取 =====
${PYTHON} src/fetch_xinjingdaily.py --date ${DATE}

# ===== 筛选 =====
${PYTHON} src/prepare_image_screening.py prepare --date ${DATE}
${PYTHON} -c "
from pathlib import Path
d = Path('dates/${DATE}')
lines = [l for l in (d/'screening_candidates.txt').read_text().splitlines() if l.strip() and not l.startswith('#')]
sel = [l.split('\t')[1] for l in lines[:30]]
(d/'screened_candidates.txt').write_text('\n'.join(sel)+'\n')
print(f'Selected {len(sel)}')
"
${PYTHON} src/prepare_image_screening.py apply --date ${DATE}

# ===== 审核 =====
${PYTHON} src/baidu_moderate.py --date ${DATE}

# ===== 排序 =====
${PYTHON} src/prepare_image_order.py --date ${DATE}

# ===== 封面标题 =====
# 注意:选择第一张图片作为封面,标题需每次不同
${PYTHON} src/save_cover_title.py --date ${DATE} \
  --cover "dates/${DATE}/images/$(ls dates/${DATE}/images/ | head -1)" \
  --title "你的新标题"

# ===== 幻灯片 =====
${PYTHON} src/create_slideshow.py --date ${DATE}

# ===== 投稿 =====
${PYTHON} src/browser_bilibili_upload.py --date ${DATE} --account account-a --publish

# ===== 验证 + 刷新 cookie + 关闭 =====
AB open "https://member.bilibili.com/platform/upload-manager/article"
AB open "https://www.bilibili.com"
AB open "about:blank"
AB close --all
pkill -f chromium
pkill -f Xvfb

常见问题

创作声明弹窗 / 定时发布弹窗

根因不是定时发布,而是创作声明未正确选择。B 站前端 checkSubmitParams() 要求 creationStatementId 不为 null。脚本已通过 Vue 组件 setter 直接设置,不再依赖 DOM 点击。

Chrome 进程退出

Chrome 需要 Xvfb 提供虚拟显示。先启动 Xvfb 再启动 Chrome。如果 Xvfb 未运行,Chrome 报 Missing X server 并退出。

Telegram 下载失败

部分图片下载失败产生 0 字节文件,导致 prepare 报错 cannot identify image file。删除损坏文件重试。

稿件被退回

B 站审核退回通常是因为图片违规。按上方"违规图片处理"章节操作,删除违规图片后重新生成投稿。

Chrome profile 未登录

如果浏览器打开后显示 B 站未登录,需要手动扫码登录:

AB open "https://www.bilibili.com"
# 在浏览器中手动扫码登录,cookie 自动保存到 ${BILI_PROFILE}

百度 AK/SK 过期

登录 百度智能云控制台 -> 内容审核平台 -> 获取新的 AK/SK,更新 .env 文件。

背景音乐

bg_music.flac 放入 output/ 目录。格式需为 FLAC,其他格式可用 ffmpeg 转换:

ffmpeg -i input.mp3 output/bg_music.flac