#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WordPress 发布系统 - 文字 + 图片发布脚本
处理从飞书等渠道发送的文字和图片,自动发布到 WordPress
"""
import os
import sys
import json
import argparse
import base64
import hashlib
# 添加项目根目录到 Python 路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, BASE_DIR)
from modules.wp_logger import get_publish_logger, get_debug_logger
from modules.wp_image_handler import create_image_handler
from modules.wp_formatter import create_formatter
from modules.wp_api import create_wp_api
from modules.wp_category import create_category_matcher
# 配置文件路径
CONFIG_FILE = os.path.join(BASE_DIR, 'config.py')
def load_config():
"""加载配置文件"""
config = {
'wp_url': 'https://www.nanlou.net',
'wp_user': 'shaowu',
'wp_password': 'zjzz gHYm 8Q3l KbZk y4CF 2DQi',
'default_category': 7,
'auto_match_category': True,
'optimize_images': True,
'image_max_width': 1200,
'image_quality': 85,
'post_status': 'publish'
}
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
exec(f.read(), config)
except Exception as e:
print(f"加载配置文件失败:{str(e)},使用默认配置")
return config
def publish_text_with_images(text, images=None, instruction=None, status=None,
category_id=None, tags=None, title=None):
"""
发布文字 + 图片到 WordPress
Args:
text: 文字内容
images: 图片列表,每个图片包含 data (base64 或文件路径), filename
instruction: 指令文本(可选)
status: 发布状态(可选)
category_id: 指定分类 ID(可选)
tags: 标签列表(可选)
title: 文章标题(可选,默认从内容提取)
Returns:
dict: 发布结果
"""
# 初始化日志
pl = get_publish_logger()
dl = get_debug_logger()
# 加载配置
config = load_config()
# 初始化各模块
wp_api = create_wp_api(config['wp_url'], config['wp_user'], config['wp_password'])
image_handler = create_image_handler(config['wp_url'], config['wp_user'], config['wp_password'])
formatter = create_formatter()
category_matcher = create_category_matcher(wp_api)
# 开始发布
pl.start_publish('文字 + 图片')
try:
# ========== 步骤 1:提取标题 ==========
if not title:
title = formatter.extract_title_from_content(text)
pl.info(f"📝 文章标题:{title}")
dl.log_step("提取标题", title)
# ========== 步骤 2:上传图片 ==========
uploaded_images = []
if images:
dl.log_step("上传图片", f"共 {len(images)} 张图片")
for i, img in enumerate(images):
try:
# 处理 base64 图片
if isinstance(img, str) and os.path.exists(img):
# 文件路径
img_path = img
elif isinstance(img, dict) and 'data' in img:
# 字典格式(包含 base64 数据)
img_data = img['data']
if isinstance(img_data, str):
# base64 编码
img_data = base64.b64decode(img_data)
filename = img.get('filename', f'image_{i+1}.jpg')
img_hash = hashlib.md5(img_data).hexdigest()[:8]
ext = os.path.splitext(filename)[1] or '.jpg'
filename = f"image_{i+1}_{img_hash}{ext}"
# 保存到临时文件
temp_dir = os.path.join(BASE_DIR, 'temp')
os.makedirs(temp_dir, exist_ok=True)
img_path = os.path.join(temp_dir, filename)
with open(img_path, 'wb') as f:
f.write(img_data)
else:
continue
# 上传图片
result = image_handler.upload_image(
img_path,
title=f"图片 {i+1}",
alt_text=f"文章配图 {i+1}"
)
uploaded_images.append(result)
# 清理临时文件
if 'img_path' in locals() and os.path.exists(img_path):
os.remove(img_path)
except Exception as e:
pl.error(f"图片 {i+1} 上传失败:{str(e)}")
dl.error(f"图片上传失败:{str(e)}", exc_info=True)
pl.info(f"📤 图片上传完成 - 成功 {len(uploaded_images)} 张")
# ========== 步骤 3:匹配分类 ==========
if category_id:
final_category_id = category_id
else:
final_category_id = category_matcher.match(
instruction=instruction,
title=title,
content=text,
auto_match=config.get('auto_match_category', True)
)
# ========== 步骤 4:格式化 HTML ==========
dl.log_step("格式化 HTML 内容")
# 先格式化文字内容
content_html = formatter.format_text_content(text)
# 插入图片
if uploaded_images:
# 将图片插入到内容中(每段之间)
paragraphs = content_html.split('
')
new_html = []
img_index = 0
for para in paragraphs:
para = para.strip()
if not para:
continue
new_html.append(para + '')
# 在段落间插入图片
if img_index < len(uploaded_images):
img = uploaded_images[img_index]
if 'url' in img:
img_html = f'
'
new_html.append(img_html)
img_index += 1
content_html = '\n\n'.join(new_html)
# 生成摘要
excerpt = formatter.generate_excerpt(content_html)
# ========== 步骤 5:发布文章 ==========
dl.log_step("发布文章")
# 确定发布状态
post_status = status or config.get('post_status', 'publish')
# 构建发布数据
publish_data = {
'title': title,
'content': content_html,
'status': post_status,
'categories': [final_category_id],
'excerpt': excerpt
}
if tags:
publish_data['tags'] = tags
# 如果有上传的图片,设置第一张为特色图片
if uploaded_images and uploaded_images[0].get('id'):
publish_data['featured_media'] = uploaded_images[0]['id']
dl.debug(f"设置特色图片 ID: {uploaded_images[0]['id']}")
# 调用 API 发布
result = wp_api.create_post(**publish_data)
# ========== 步骤 6:输出结果 ==========
if result.get('success'):
pl.end_publish(
True,
post_id=result.get('id'),
post_url=result.get('url')
)
return {
'success': True,
'post_id': result.get('id'),
'post_url': result.get('url'),
'title': title,
'category_id': final_category_id,
'images_uploaded': len(uploaded_images)
}
else:
pl.end_publish(False, error_msg=result.get('error'))
return {
'success': False,
'error': result.get('error')
}
except Exception as e:
pl.end_publish(False, error_msg=str(e))
dl.error(f"发布异常:{str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}
def update_post_with_text(target, new_text, new_title=None, status='publish',
category_id=None, tags=None):
"""
更新已有文章(追加内容模式)
Args:
target: 文章 ID(数字)或标题关键词(字符串)
new_text: 新增的正文内容
new_title: 新标题(可选)
status: 发布状态
category_id: 分类 ID(可选)
tags: 标签列表(可选)
Returns:
dict: 更新结果
"""
pl = get_publish_logger()
dl = get_debug_logger()
config = load_config()
wp_api = create_wp_api(config['wp_url'], config['wp_user'], config['wp_password'])
formatter = create_formatter()
pl.start_publish('更新文章', target)
try:
# ========== 步骤 1:查找文章 ==========
post_data = None
post_id = None
if target.isdigit():
# 按 ID 精确查找
post_id = int(target)
result = wp_api.get_post(post_id)
if result.get('success'):
post_data = result.get('data')
else:
pl.error(f"未找到文章 ID: {post_id}")
return {'success': False, 'error': f'未找到文章 ID: {post_id}'}
else:
# 按标题搜索
search_results = wp_api.search_posts(target, per_page=1)
if not search_results:
pl.error(f"未找到匹配标题的文章: {target}")
return {'success': False, 'error': f'未找到匹配标题的文章: {target}'}
post_data = search_results[0]
post_id = post_data.get('id')
pl.info(f"🔍 搜索匹配到文章 ID: {post_id}")
# ========== 步骤 2:获取原文内容 ==========
old_content = post_data.get('content', {}).get('rendered', '')
old_title = post_data.get('title', {}).get('rendered', '')
pl.info(f"📖 原文标题:{old_title},原文长度:{len(old_content)} 字符")
# ========== 步骤 3:格式化新内容 ==========
new_html = formatter.format_text_content(new_text)
# ========== 步骤 4:合并内容(追加模式)==========
merged_content = old_content.rstrip() + "\n\n" + new_html.lstrip()
# ========== 步骤 5:构建更新数据 ==========
final_title = new_title if new_title else old_title
update_result = wp_api.update_post(
post_id=post_id,
title=final_title,
content=merged_content,
status=status,
categories=[category_id] if category_id else None,
tags=tags
)
# ========== 步骤 6:返回结果 ==========
if update_result.get('success'):
post_url = update_result.get('data', {}).get('link', '')
pl.end_publish(True, post_id=post_id, post_url=post_url)
return {
'success': True,
'post_id': post_id,
'post_url': post_url,
'title': final_title,
'original_length': len(old_content),
'merged_length': len(merged_content)
}
else:
pl.end_publish(False, error_msg=update_result.get('error'))
return {'success': False, 'error': update_result.get('error')}
except Exception as e:
pl.end_publish(False, error_msg=str(e))
dl.error(f"更新异常:{str(e)}", exc_info=True)
return {'success': False, 'error': str(e)}
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(description='WordPress 文字 + 图片发布工具')
parser.add_argument('text', help='文字内容')
parser.add_argument('--images', '-i', nargs='+', help='图片文件路径')
parser.add_argument('--title', '-t', help='文章标题')
parser.add_argument('--instruction', '-c', help='发布指令(如:#分类 技术)')
parser.add_argument('--status', '-s', choices=['publish', 'draft', 'pending', 'private'],
default=None, help='发布状态')
parser.add_argument('--category', '-C', type=int, help='指定分类 ID')
parser.add_argument('--tags', '-T', help='标签 ID 列表(逗号分隔)')
args = parser.parse_args()
# 解析标签
tags = None
if args.tags:
tags = [int(t.strip()) for t in args.tags.split(',') if t.strip()]
# 执行发布
result = publish_text_with_images(
text=args.text,
images=args.images,
instruction=args.instruction,
status=args.status,
category_id=args.category,
tags=tags,
title=args.title
)
# 输出 JSON 结果
print("\n" + json.dumps(result, ensure_ascii=False, indent=2))
# 返回状态码
sys.exit(0 if result.get('success') else 1)
if __name__ == '__main__':
main()