Files
yu-zhi-ran/scripts/publisher.py
T
lt 277b13eaae feat: 完成布局优化 - 操作列固定、批量按钮自适应、分类标签带数量
优化内容:
1. 表格布局:
   - 使用 calc(100vw - 160px) 确保表格不超出视口
   - 操作列 fixed='right' 固定在右侧,宽度 300px
   - 按钮 3 个后自动换行 (max-width: 200px)
   - 恢复合理列宽,不再过度压缩

2. 批量操作区域:
   - 容器改为 inline-block,宽度自适应按钮内容
   - 背景宽度与按钮总宽度匹配

3. 分类标签:
   - 显示数量 (如 '待处理 (20)')
   - 点击切换筛选,去掉误导的 'X' 图标

4. 删除功能:
   - 操作列增加删除按钮
   - 删除前弹出确认对话框

5. 系统日志:
   - 修复后端日志路径 (parents[4])
   - 404 时显示友好提示

6. 其他:
   - 左侧菜单宽度 160px
   - 所有功能保留 (登录、用户管理、批量操作等)
2026-04-27 11:32:17 +08:00

218 lines
8.1 KiB
Python
Executable File
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.
#!/usr/bin/env python3
"""
多平台内容发布脚本
将「待发布」的文章发布到各平台(知乎/公众号/小红书/B站/头条号)
支持单个 topic 生成发布包模式(--topic-id
"""
import json, datetime, logging, sys, subprocess, time
from pathlib import Path
from typing import Dict, List
import argparse
PROJECT_ROOT = Path('/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran')
sys.path.insert(0, str(PROJECT_ROOT))
DATA_DIR = PROJECT_ROOT / "automation" / "data"
TOPICS_FILE = DATA_DIR / "sustainability_topics.json"
RELEASES_DIR = DATA_DIR / "releases"
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOGS_DIR / f"publisher_{TODAY}.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 命令行参数
parser = argparse.ArgumentParser(description='发布管理脚本')
parser.add_argument('--topic-id', help='仅处理指定 topic ID')
args = parser.parse_args()
# 平台配置
PLATFORMS = {
"zhihu": {"name": "知乎", "enabled": True, "template": "zhihu.html"},
"wechat": {"name": "微信公众号", "enabled": True, "template": "wechat.html"}, # 需手动授权
"xiaohongshu": {"name": "小红书", "enabled": True, "template": "xiaohongshu.html"},
"bilibili": {"name": "B站", "enabled": False, "template": "bilibili.html"}, # 规划中
"toutiao": {"name": "头条号", "enabled": False, "template": "toutiao.html"} # 规划中
}
def load_topics() -> List[Dict]:
with open(TOPICS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_topics(topics: List[Dict]):
with open(TOPICS_FILE, 'w', encoding='utf-8') as f:
json.dump(topics, f, ensure_ascii=False, indent=2)
def get_ready_topics() -> List[Dict]:
topics = load_topics()
ready = [t for t in topics if t.get('status') == '待发布']
ready.sort(key=lambda t: t.get('ready_at', ''), reverse=True) # 优先最新
return ready, topics
def publish_to_xiaohongshu(html_path: Path, topic: Dict) -> bool:
"""小红书:复制HTML到发布目录(供手动发布)"""
logger.info(f"准备小红书发布: {topic['id']}")
try:
publish_base = PROJECT_ROOT / "content" / "published"
dest_dir = publish_base / topic['id'] / "手动发布" / "小红书"
dest_dir.mkdir(parents=True, exist_ok=True)
dest_html = dest_dir / "文章.html"
import shutil
shutil.copy2(html_path, dest_html)
logger.info(f"✅ 小红书发布包就绪: {dest_dir}")
return True, str(dest_dir)
except Exception as e:
logger.error(f"小红书发布准备失败: {e}")
return False, None
def publish_to_zhihu(html_path: Path, topic: Dict) -> bool:
"""知乎:复制HTML到发布目录"""
logger.info(f"准备知乎发布: {topic['id']}")
try:
publish_base = PROJECT_ROOT / "content" / "published"
dest_dir = publish_base / topic['id'] / "手动发布" / "知乎"
dest_dir.mkdir(parents=True, exist_ok=True)
dest_html = dest_dir / "文章.html"
import shutil
shutil.copy2(html_path, dest_html)
logger.info(f"✅ 知乎发布包就绪: {dest_dir}")
return True, str(dest_dir)
except Exception as e:
logger.error(f"知乎发布准备失败: {e}")
return False, None
def publish_to_wechat(html_path: Path, topic: Dict) -> bool:
"""微信公众号:复制HTML到发布目录"""
logger.info(f"准备微信公众号发布: {topic['id']}")
try:
publish_base = PROJECT_ROOT / "content" / "published"
dest_dir = publish_base / topic['id'] / "手动发布" / "微信公众号"
dest_dir.mkdir(parents=True, exist_ok=True)
dest_html = dest_dir / "文章.html"
import shutil
shutil.copy2(html_path, dest_html)
logger.info(f"✅ 微信公众号发布包就绪: {dest_dir}")
return True, str(dest_dir)
except Exception as e:
logger.error(f"微信公众号发布准备失败: {e}")
return False, None
def publish_to_platform(platform: str, html_path: Path, topic: Dict) -> (bool, str):
"""生成平台发布包(人工发布)"""
if platform == "xiaohongshu":
return publish_to_xiaohongshu(html_path, topic)
elif platform == "zhihu":
return publish_to_zhihu(html_path, topic)
elif platform == "wechat":
return publish_to_wechat(html_path, topic)
else:
logger.warning(f"平台 {platform} 暂未支持")
return False, None
def main():
logger.info("=== 多平台内容发布包生成开始 ===")
ready, all_topics = get_ready_topics()
if not ready:
logger.info("没有待发布内容")
sys.exit(0)
# 如果指定了 topic-id,只处理该选题
if args.topic_id:
ready = [t for t in ready if t['id'] == args.topic_id]
if not ready:
logger.info(f"未找到指定 topic ID: {args.topic_id}")
sys.exit(0)
results = []
for topic in ready:
tid = topic['id']
title = topic.get('title', '')[:50]
release_date = topic.get('ready_at', TODAY)
release_dir = RELEASES_DIR / release_date
platform_urls = topic.get('platform_urls', {})
for platform, config in PLATFORMS.items():
if not config['enabled']:
continue
# 检查是否已发布过
if platform in platform_urls and platform_urls[platform]:
logger.info(f"跳过已发布: {tid} - {platform}")
continue
html_file = release_dir / platform / f"{platform}_{tid}_{platform}.html"
if not html_file.exists():
logger.warning(f"HTML文件不存在: {html_file}")
continue
# 生成发布包(不自动发布)
success, info = publish_to_platform(platform, html_file, topic)
if success:
results.append((tid, platform, info))
logger.info(f"{tid} 发布包已准备: {platform}")
else:
logger.error(f"{tid} 发布包准备失败: {platform}")
# 汇总报告
summary_file = LOGS_DIR / f"publisher_summary_{TODAY}.json"
summary = {
"date": TODAY,
"total_ready": len(ready),
"packages_generated": len(results),
"details": [{"topic_id": r[0], "platform": r[1], "path": r[2]} for r in results]
}
with open(summary_file, 'w', encoding='utf-8') as f:
json.dump(summary, f, ensure_ascii=False, indent=2)
# === 发送日报通知 ===
try:
# 统计发布数据(按话题去重)
unique_tids = set(r[0] for r in results)
platforms_set = set(r[1] for r in results)
notify_data = {
"task": "daily_summary",
"date": TODAY,
"published_count": len(unique_tids),
"platforms": list(platforms_set),
"publish_dir": str(PROJECT_ROOT / "content" / "published")
}
notify_file = LOGS_DIR / f"publisher_notify_{TODAY}.json"
with open(notify_file, 'w', encoding='utf-8') as f:
json.dump(notify_data, f, ensure_ascii=False, indent=2)
# 调用 notifier
notifier_script = PROJECT_ROOT / "scripts" / "wecom_notifier.py"
if notifier_script.exists():
subprocess.run(
[sys.executable, str(notifier_script), str(notify_file)],
cwd=str(PROJECT_ROOT),
capture_output=True,
text=True,
timeout=30
)
else:
logger.warning("Notifier script not found, skipping notification")
except Exception as e:
logger.error(f"发送日报通知失败: {e}")
# === 通知结束 ===
logger.info(f"📦 发布包生成完成: {len(results)} 个平台发布包已就绪")
print(f"PUBLISH_PACKAGES_READY: {len(results)} packages generated")
sys.exit(0)
if __name__ == "__main__":
main()