feishu_fabu/feishu_bot.py
admins d1f440a8a0 feat: 新增生图格式和尺寸控制
新增功能:
- 支持图片格式控制:webp, jpg, png
- 新增#生图格式指令
- 新增尺寸快捷方式:正方形、横图、竖图、宽屏、手机、小图
- 使用 PIL 进行格式转换,webp 默认质量 85
- 优化文件体积:webp 比 PNG 小 14 倍(93KB vs 1.3MB)

测试结果:
-  webp 格式生成成功(1280*720 横图)
-  文件大小:93KB
-  格式验证通过:RIFF (little-endian) data
2026-05-14 01:25:23 +08:00

1004 lines
34 KiB
Python
Raw Permalink 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
# -*- coding: utf-8 -*-
"""
飞书机器人消息接收与处理脚本
接收飞书消息,解析内容,调用 WordPress 发布脚本
支持 AI 图片生成(含格式和尺寸控制)
"""
import os
import sys
import json
import time
import logging
from datetime import datetime
# 添加项目根目录到 Python 路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, BASE_DIR)
# 导入飞书 API 客户端
from modules.feishu_api import create_feishu_client
# 分类映射slug -> ID
CATEGORY_MAP = {
'ai-kepu': 12,
'ai-zixun': 11,
'geo': 16,
'ai': 9,
'jishu': 5,
'fenxiang': 10,
'wenzhang': 4,
'zaji': 8,
'suibi': 7,
'guanyu': 1
}
# 尺寸快捷方式
SIZE_PRESETS = {
'正方形': '1024*1024',
'横图': '1280*720',
'竖图': '720*1280',
'宽屏': '1440*720',
'手机': '720*1440',
'小图': '512*512'
}
# 配置日志
LOG_DIR = os.path.join(BASE_DIR, 'logs')
os.makedirs(LOG_DIR, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(os.path.join(LOG_DIR, 'feishu_bot.log'), encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger('feishu_bot')
class FeishuBot:
"""飞书机器人"""
def __init__(self):
"""初始化飞书机器人"""
self.config = self._load_config()
self.wp_script = os.path.join(BASE_DIR, 'scripts', 'wp_publish_text.py')
self.word_script = os.path.join(BASE_DIR, 'scripts', 'wp_publish.py')
self.image_gen_script = os.path.join(BASE_DIR, 'scripts', 'wp_generate_image.py')
# 初始化飞书 API 客户端
self.feishu_client = create_feishu_client(
app_id=self.config.get('app_id'),
app_secret=self.config.get('app_secret')
)
logger.info("🤖 飞书机器人初始化完成")
logger.info(f" WordPress 发布脚本:{self.wp_script}")
logger.info(f" Word 发布脚本:{self.word_script}")
logger.info(f" AI 生图脚本:{self.image_gen_script}")
logger.info(f" 飞书 API 客户端:已初始化")
def _load_config(self):
"""加载配置"""
try:
from feishu_config import (
FEISHU_APP_ID, FEISHU_APP_SECRET,
SERVER_HOST, SERVER_PORT, ALLOWED_USERS
)
return {
'app_id': FEISHU_APP_ID,
'app_secret': FEISHU_APP_SECRET,
'host': SERVER_HOST,
'port': SERVER_PORT,
'allowed_users': ALLOWED_USERS
}
except ImportError:
logger.warning("未找到 feishu_config.py使用默认配置")
return {
'app_id': '',
'app_secret': '',
'host': '0.0.0.0',
'port': 8080,
'allowed_users': []
}
def process_message(self, message):
"""
处理接收到的消息
Args:
message: 飞书消息对象
Returns:
str: 回复消息
"""
try:
# 解析消息内容
msg_type = message.get('msg_type', '')
content = message.get('content', '')
sender_id = message.get('sender', {}).get('sender_id', '')
message_id = message.get('message_id', '')
chat_id = message.get('chat_id', '')
logger.info(f"📨 收到消息 - 类型:{msg_type}, 发送者:{sender_id}, 消息 ID: {message_id}")
# 检查权限
if self.config['allowed_users'] and sender_id not in self.config['allowed_users']:
return "⚠️ 您没有权限使用此机器人"
# 处理不同类型的消息
if msg_type == 'text':
return self._handle_text_message(content, sender_id, message_id, chat_id)
elif msg_type == 'image':
return self._handle_image_message(content, sender_id, message_id, chat_id)
elif msg_type == 'file':
return self._handle_file_message(content, sender_id, message_id, chat_id)
elif msg_type == 'interactive':
return self._handle_interactive_message(content, sender_id, message_id, chat_id)
else:
return f"📝 暂不支持的消息类型:{msg_type}"
except Exception as e:
logger.error(f"❌ 处理消息失败:{str(e)}", exc_info=True)
return f"❌ 处理消息失败:{str(e)}"
def _handle_text_message(self, content, sender_id, message_id=None, chat_id=None):
"""
处理文字消息
Args:
content: 消息内容
sender_id: 发送者 ID
message_id: 消息 ID用于获取图片
chat_id: 聊天 ID
Returns:
str: 回复消息
"""
logger.info(f"📝 处理文字消息")
# 解析指令
instruction = self._parse_instruction(content)
# 获取消息中的图片(如果有)
images = []
if message_id:
images = self._get_message_images(message_id)
# 检查是否为生图指令
if instruction.get('action') == 'generate_image':
return self._generate_image(
prompt=instruction.get('prompt', ''),
count=instruction.get('image_count', 1),
model=instruction.get('image_model', None),
size=instruction.get('image_size', None),
style=instruction.get('image_style', None),
image_format=instruction.get('image_format', None)
)
# 检查是否为发布+生图指令
elif instruction.get('action') == 'publish_with_image':
return self._publish_with_generated_image(
text=instruction.get('text', ''),
title=instruction.get('title', ''),
category=instruction.get('category', ''),
tags=instruction.get('tags', ''),
status=instruction.get('status', 'publish'),
image_count=instruction.get('image_count', 1),
model=instruction.get('image_model', None),
size=instruction.get('image_size', None),
style=instruction.get('image_style', None),
image_format=instruction.get('image_format', None)
)
# 检查是否为发布指令
elif instruction.get('action') == 'publish':
return self._publish_article(
text=instruction.get('text', ''),
title=instruction.get('title', ''),
category=instruction.get('category', ''),
tags=instruction.get('tags', ''),
status=instruction.get('status', 'publish'),
images=images if images else None
)
elif instruction.get('action') == 'update':
return self._update_article(
target=instruction.get('target', ''),
text=instruction.get('text', ''),
title=instruction.get('title', ''),
status=instruction.get('status', 'publish')
)
elif instruction.get('action') == 'help':
return self._get_help_message()
elif instruction.get('action') == 'status':
return self._get_status_message()
else:
# 默认发布
return self._publish_article(text=content, images=images if images else None)
def _get_message_images(self, message_id):
"""
获取消息中的图片列表
Args:
message_id: 消息 ID
Returns:
list: 图片本地路径列表
"""
if not message_id:
return []
logger.info(f"🔍 获取消息图片 - Message ID: {message_id}")
try:
# 使用飞书 API 客户端获取图片
images = self.feishu_client.get_message_images(message_id)
if not images:
logger.info("消息中没有图片")
return []
# 下载所有图片
downloaded_images = []
for img in images:
image_key = img.get('image_key', '')
if image_key:
image_path = self._download_image(image_key)
if image_path:
downloaded_images.append(image_path)
logger.info(f"✅ 图片下载成功:{image_path}")
else:
logger.error(f"❌ 图片下载失败:{image_key}")
logger.info(f"📊 共下载 {len(downloaded_images)} 张图片")
return downloaded_images
except Exception as e:
logger.error(f"获取消息图片失败:{str(e)}")
return []
def _handle_image_message(self, content, sender_id, message_id=None, chat_id=None):
"""
处理图片消息
Args:
content: 消息内容
sender_id: 发送者 ID
message_id: 消息 ID用于获取图片
chat_id: 聊天 ID
Returns:
str: 回复消息
"""
logger.info(f"🖼️ 处理图片消息")
try:
image_key = json.loads(content).get('image_key', '')
# 下载图片
image_path = self._download_image(image_key)
if image_path:
# 发布带图片的文章(保留原始文字内容)
return self._publish_article(
text="图片文章", # 默认文字,如果有文字消息会保留
images=[image_path],
message_id=message_id,
chat_id=chat_id
)
else:
return "❌ 图片下载失败"
except Exception as e:
logger.error(f"处理图片消息失败:{str(e)}")
return f"❌ 处理图片消息失败:{str(e)}"
def _handle_file_message(self, content, sender_id, message_id=None, chat_id=None):
"""
处理文件消息
Args:
content: 消息内容
sender_id: 发送者 ID
message_id: 消息 ID
chat_id: 聊天 ID
Returns:
str: 回复消息
"""
logger.info(f"📁 处理文件消息")
try:
file_info = json.loads(content)
file_key = file_info.get('file_key', '')
file_name = file_info.get('file_name', '')
# 检查是否为 Word 文档
if not file_name.endswith('.docx'):
return "⚠️ 仅支持 .docx 格式的 Word 文档"
# 下载文件
file_path = self._download_file(file_key, file_name)
if file_path:
# 发布 Word 文档
return self._publish_word_document(file_path)
else:
return "❌ 文件下载失败"
except Exception as e:
logger.error(f"处理文件消息失败:{str(e)}")
return f"❌ 处理文件消息失败:{str(e)}"
def _handle_interactive_message(self, content, sender_id, message_id=None, chat_id=None):
"""
处理交互式消息
Args:
content: 消息内容
sender_id: 发送者 ID
message_id: 消息 ID
chat_id: 聊天 ID
Returns:
str: 回复消息
"""
logger.info(f"🔄 处理交互式消息")
try:
data = json.loads(content)
action = data.get('action', '')
if action == 'publish':
return self._publish_article(
text=data.get('text', ''),
title=data.get('title', ''),
category=data.get('category', '')
)
else:
return f"⚠️ 未知的交互动作:{action}"
except Exception as e:
logger.error(f"处理交互式消息失败:{str(e)}")
return f"❌ 处理交互式消息失败:{str(e)}"
def _parse_instruction(self, content):
"""
解析消息指令
Args:
content: 消息内容
Returns:
dict: 解析结果
"""
instruction = {
'action': 'publish', # 默认动作:发布
'text': '',
'title': '',
'category': '',
'tags': '',
'status': 'publish',
'prompt': '',
'image_count': 1,
'image_model': None,
'image_size': None,
'image_style': None,
'image_format': None
}
lines = content.strip().split('\n')
text_lines = []
for line in lines:
line = line.strip()
# 解析指令
if line.startswith('#标题'):
instruction['title'] = line.replace('#标题', '').strip()
elif line.startswith('#分类') or line.startswith('#category'):
instruction['category'] = line.replace('#分类', '').replace('#category', '').strip()
elif line.startswith('#标签') or line.startswith('#tag'):
instruction['tags'] = line.replace('#标签', '').replace('#tag', '').strip()
elif line.startswith('#状态') or line.startswith('#status'):
status = line.replace('#状态', '').replace('#status', '').strip().lower()
if status in ['publish', 'draft', 'pending', 'private']:
instruction['status'] = status
elif line.startswith('#发布'):
instruction['action'] = 'publish'
elif line.startswith('#草稿'):
instruction['status'] = 'draft'
instruction['action'] = 'publish'
elif line.startswith('#帮助') or line.startswith('#help'):
instruction['action'] = 'help'
elif line.startswith('#状态') or line.startswith('#status'):
instruction['action'] = 'status'
elif line.startswith('#更新') or line.startswith('#update'):
instruction['action'] = 'update'
instruction['target'] = line.replace('#更新', '').replace('#update', '').strip()
# AI 生图指令
elif line.startswith('#生图') or line.startswith('#生成图片'):
instruction['action'] = 'generate_image'
prompt_text = line.replace('#生图', '').replace('#生成图片', '').strip()
instruction['prompt'] = prompt_text
elif line.startswith('#生图数量') or line.startswith('#图片数量'):
try:
count = int(line.replace('#生图数量', '').replace('#图片数量', '').strip())
instruction['image_count'] = min(max(count, 1), 4)
except ValueError:
pass
elif line.startswith('#生图模型') or line.startswith('#图片模型'):
model = line.replace('#生图模型', '').replace('#图片模型', '').strip()
instruction['image_model'] = model
elif line.startswith('#生图尺寸') or line.startswith('#图片尺寸'):
size = line.replace('#生图尺寸', '').replace('#图片尺寸', '').strip()
# 支持快捷方式
if size in SIZE_PRESETS:
instruction['image_size'] = SIZE_PRESETS[size]
else:
instruction['image_size'] = size
elif line.startswith('#生图风格') or line.startswith('#图片风格'):
style = line.replace('#生图风格', '').replace('#图片风格', '').strip()
instruction['image_style'] = style
elif line.startswith('#生图格式') or line.startswith('#图片格式'):
fmt = line.replace('#生图格式', '').replace('#图片格式', '').strip().lower()
if fmt in ['jpg', 'png', 'webp']:
instruction['image_format'] = fmt
# 发布+生图指令
elif line.startswith('#发布并生图') or line.startswith('#发布+生图'):
instruction['action'] = 'publish_with_image'
else:
text_lines.append(line)
instruction['text'] = '\n'.join(text_lines)
return instruction
def _resolve_category(self, category_slug):
"""
将分类 slug 转换为 ID
Args:
category_slug: 分类 slug
Returns:
int: 分类 ID
"""
if not category_slug:
return None
# 先尝试直接匹配
slug = category_slug.lower().strip()
if slug in CATEGORY_MAP:
return CATEGORY_MAP[slug]
# 尝试模糊匹配
for key, value in CATEGORY_MAP.items():
if slug in key or key in slug:
return value
# 如果输入的是数字,直接返回
try:
return int(slug)
except ValueError:
pass
# 默认返回随笔分类
logger.warning(f"未找到分类:{category_slug},使用默认分类")
return CATEGORY_MAP.get('suibi', 7)
def _publish_article(self, text='', title='', category='', tags='', images=None, status='publish', message_id=None, chat_id=None):
"""
发布文章(直接调用 Python 函数,避免 shell 引号问题)
Args:
text: 文章正文
title: 文章标题
category: 分类
tags: 标签
images: 图片列表
status: 发布状态
message_id: 消息 ID
chat_id: 聊天 ID
Returns:
str: 回复消息
"""
if not text:
return "⚠️ 文章内容不能为空"
logger.info(f"📝 准备发布文章 - 标题:{title}, 分类:{category}")
try:
# 直接导入并调用 Python 函数(避免 subprocess 的 shell 转义问题)
from scripts.wp_publish_text import publish_text_with_images
# 解析分类(如果是 slug 需要转换为 ID
category_id = self._resolve_category(category) if category else None
# 调用发布函数
publish_result = publish_text_with_images(
text=text,
images=images,
instruction=f"#分类 {category}" if category else None,
status=status,
category_id=category_id,
title=title if title else None
)
# 处理结果
if publish_result.get('success'):
post_url = publish_result.get('post_url', '')
post_id = publish_result.get('post_id', '')
reply = "✅ 文章发布成功!\n"
reply += f"📝 标题:{publish_result.get('title', title or '自动提取')}\n"
reply += f"🔗 链接:{post_url}\n"
reply += f"📊 文章 ID{post_id}"
if publish_result.get('images_uploaded', 0) > 0:
reply += f"\n🖼️ 已上传图片:{publish_result['images_uploaded']}"
return reply
else:
error_msg = publish_result.get('error', '未知错误')
return f"❌ 发布失败:{error_msg}"
except Exception as e:
logger.error(f"发布文章失败:{str(e)}", exc_info=True)
return f"❌ 发布失败:{str(e)}"
def _publish_with_generated_image(self, text='', title='', category='', tags='', status='publish',
image_count=1, model=None, size=None, style=None, image_format=None):
"""
发布文章并自动生成配图
Args:
text: 文章正文
title: 文章标题
category: 分类
tags: 标签
status: 发布状态
image_count: 生成图片数量
model: 生图模型
size: 图片尺寸
style: 图片风格
image_format: 图片格式
Returns:
str: 回复消息
"""
if not text and not title:
return "⚠️ 请提供文章标题或内容"
logger.info(f"📝 准备发布文章(含 AI 配图)- 标题:{title}")
try:
# 加载配置获取 API Key
config = self._load_image_gen_config()
if not config.get('dashscope_api_key'):
return "⚠️ 未配置 DashScope API Key无法生成图片\n\n请在 config.py 中配置 dashscope_api_key"
# 生成图片
from scripts.wp_generate_image import generate_images_for_article
pl = logger
pl.info(f"🎨 开始生成 AI 配图...")
image_result = generate_images_for_article(
title=title or '自动提取',
content=text,
count=image_count,
api_key=config.get('dashscope_api_key'),
image_format=image_format
)
if not image_result.get('success'):
return f"⚠️ AI 配图生成失败:{image_result.get('error', '未知错误')}\n\n文章仍可发布,但不含配图。"
image_paths = image_result.get('paths', [])
pl.info(f"✅ 已生成 {len(image_paths)} 张配图")
# 发布文章(带图片)
from scripts.wp_publish_text import publish_text_with_images
category_id = self._resolve_category(category) if category else None
publish_result = publish_text_with_images(
text=text,
images=image_paths,
instruction=f"#分类 {category}" if category else None,
status=status,
category_id=category_id,
title=title if title else None
)
# 处理结果
if publish_result.get('success'):
post_url = publish_result.get('post_url', '')
post_id = publish_result.get('post_id', '')
reply = "✅ 文章发布成功(含 AI 配图)!\n"
reply += f"📝 标题:{publish_result.get('title', title or '自动提取')}\n"
reply += f"🔗 链接:{post_url}\n"
reply += f"📊 文章 ID{post_id}\n"
reply += f"🎨 AI 配图:{len(image_paths)}"
return reply
else:
error_msg = publish_result.get('error', '未知错误')
return f"❌ 发布失败:{error_msg}"
except Exception as e:
logger.error(f"发布文章(含 AI 配图)失败:{str(e)}", exc_info=True)
return f"❌ 发布失败:{str(e)}"
def _generate_image(self, prompt='', count=1, model=None, size=None, style=None, image_format=None):
"""
生成 AI 图片
Args:
prompt: 图片描述
count: 生成数量
model: 模型名称
size: 图片尺寸
style: 图片风格
image_format: 图片格式
Returns:
str: 回复消息
"""
if not prompt:
return "⚠️ 请提供图片描述\n\n示例:`#生图 一只可爱的猫咪在草地上晒太阳`"
logger.info(f"🎨 开始 AI 生图 - 描述:{prompt}")
try:
# 加载配置获取 API Key
config = self._load_image_gen_config()
if not config.get('dashscope_api_key'):
return "⚠️ 未配置 DashScope API Key无法生成图片\n\n请在 config.py 中配置 dashscope_api_key"
# 调用生图脚本
from scripts.wp_generate_image import generate_image
result = generate_image(
prompt=prompt,
api_key=config.get('dashscope_api_key'),
model=model,
size=size,
count=count,
style=style,
image_format=image_format
)
if result.get('success'):
paths = result.get('paths', [])
reply = f"✅ AI 生图完成!成功生成 {len(paths)} 张图片\n"
reply += f"📊 模型:{result.get('model', 'wanx-v1')}\n"
reply += f"📐 尺寸:{result.get('size', '1024*1024')}\n"
reply += f"🖼️ 格式:{result.get('format', 'webp')}\n\n"
reply += "📁 本地路径:\n"
for i, path in enumerate(paths):
reply += f" {i+1}. {path}\n"
return reply
else:
error_msg = result.get('error', '未知错误')
return f"❌ AI 生图失败:{error_msg}"
except Exception as e:
logger.error(f"AI 生图失败:{str(e)}", exc_info=True)
return f"❌ AI 生图失败:{str(e)}"
def _load_image_gen_config(self):
"""加载图片生成配置"""
config = {
'dashscope_api_key': '',
'image_model': 'wanx-v1',
'image_size': '1024*1024',
'image_format': 'webp',
'image_count': 1
}
config_file = os.path.join(BASE_DIR, 'config.py')
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:
logger.warning(f"加载配置文件失败:{str(e)}")
return config
def _update_article(self, target='', text='', title='', status='publish'):
"""
更新已有文章(追加内容模式)
Args:
target: 文章 ID 或标题关键词
text: 新增内容
title: 新标题(可选)
status: 发布状态
Returns:
str: 回复消息
"""
if not target:
return "⚠️ 请指定文章 ID 或标题,例如:\n`#更新 12345`\n`#更新 文章标题`"
if not text:
return "⚠️ 更新内容不能为空"
logger.info(f"🔄 准备更新文章 - 目标:{target}")
try:
from scripts.wp_publish_text import update_post_with_text
result = update_post_with_text(
target=target,
new_text=text,
new_title=title if title else None,
status=status
)
if result.get('success'):
reply = "✅ 文章更新成功!\n"
reply += f"📝 标题:{result.get('title')}\n"
reply += f"🔗 链接:{result.get('post_url')}\n"
reply += f"📊 文章 ID{result.get('post_id')}"
return reply
else:
return f"❌ 更新失败:{result.get('error')}"
except Exception as e:
logger.error(f"更新文章失败:{str(e)}", exc_info=True)
return f"❌ 更新失败:{str(e)}"
def _publish_word_document(self, file_path):
"""
发布 Word 文档(直接调用 Python 函数)
Args:
file_path: Word 文档路径
Returns:
str: 回复消息
"""
logger.info(f"📄 准备发布 Word 文档:{file_path}")
try:
# 直接导入并调用 Python 函数
from scripts.wp_publish import publish_word_document
# 调用发布函数
publish_result = publish_word_document(word_file_path=file_path)
# 处理结果
if publish_result.get('success'):
post_url = publish_result.get('post_url', '')
post_id = publish_result.get('post_id', '')
title = publish_result.get('title', '自动提取')
reply = "✅ Word 文档发布成功!\n"
reply += f"📝 标题:{title}\n"
reply += f"🔗 链接:{post_url}\n"
reply += f"📊 文章 ID{post_id}"
if publish_result.get('images_uploaded', 0) > 0:
reply += f"\n🖼️ 已上传图片:{publish_result['images_uploaded']}"
return reply
else:
error_msg = publish_result.get('error', '未知错误')
return f"❌ 发布失败:{error_msg}"
except Exception as e:
logger.error(f"发布 Word 文档失败:{str(e)}", exc_info=True)
return f"❌ 发布失败:{str(e)}"
def _get_help_message(self):
"""
获取帮助信息
Returns:
str: 帮助消息
"""
help_text = """🤖 **WordPress 发布助手 - 使用说明**
📝 **发布文字文章**
直接发送文字内容即可发布
📄 **发布 Word 文档**
发送 .docx 格式的 Word 文档
🖼️ **发布图片文章**
发送图片即可发布
🎨 **AI 图片生成**(支持格式控制)
使用 AI 生成文章配图或独立图片
---
**基础指令**
`#标题 文章标题` - 指定文章标题
`#分类 分类名` - 指定分类(如:#分类 ai
`#标签 标签名` - 指定标签
`#草稿` - 保存为草稿
`#发布` - 立即发布(默认)
`#更新 ID或标题` - 更新已有文章(追加内容)
---
**AI 生图指令**
`#生图 图片描述` - 生成指定描述的图片
`#生图数量 2` - 设置生成数量1-4
`#生图模型 wanx-v1` - 选择模型
`#生图尺寸 1024*1024` - 设置尺寸(支持快捷方式)
`#生图风格 写实` - 设置风格
`#生图格式 webp` - 设置格式webp, jpg, png
**尺寸快捷方式**
- 正方形 → 1024*1024
- 横图 → 1280*720
- 竖图 → 720*1280
- 宽屏 → 1440*720
- 手机 → 720*1440
- 小图 → 512*512
**发布+生图指令**
`#发布并生图` - 发布文章并自动生成配图
(与 #标题、#分类 等指令配合使用)
---
**示例**
```
#标题 AI 发展趋势
#分类 ai
人工智能正在改变世界...
```
**AI 生图示例**
```
#生图 一只可爱的猫咪在草地上晒太阳,高质量插画
#生图数量 2
#生图尺寸 横图
#生图格式 webp
```
**发布+生图示例**
```
#发布并生图
#标题 AI 未来展望
#分类 ai
人工智能正在改变世界...
```
**可用分类**
- ai - 人工智能
- ai-kepu - Ai 科普
- ai-zixun - Ai 资讯
- geo - GEO
- jishu - 技术资料
- fenxiang - 好物分享
- wenzhang - 文章分享
- zaji - 杂记
- suibi - 随笔(默认)
发送 `#帮助` 查看此消息"""
return help_text
def _get_status_message(self):
"""
获取系统状态
Returns:
str: 状态消息
"""
status_text = f"""📊 **系统状态**
- **时间**{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
- **状态**:✅ 运行中
- **WordPress**https://www.nanlou.net
- **发布脚本**:已就绪
- **AI 生图**:✅ 支持webp/jpg/png
发送 `#帮助` 查看使用说明"""
return status_text
def _download_image(self, image_key):
"""
下载飞书图片到本地
Args:
image_key: 图片 key
Returns:
str: 图片本地路径
"""
if not image_key:
logger.warning("图片 key 为空")
return None
# 使用飞书 API 客户端下载图片
save_dir = os.path.join(BASE_DIR, 'temp')
image_path = self.feishu_client.download_image(image_key, save_dir)
if image_path:
logger.info(f"✅ 图片下载成功:{image_path}")
return image_path
else:
logger.error("❌ 图片下载失败")
return None
def _download_file(self, file_key, file_name):
"""
下载飞书文件到本地
Args:
file_key: 文件 key
file_name: 文件名
Returns:
str: 文件本地路径
"""
if not file_key:
logger.warning("文件 key 为空")
return None
# 使用飞书 API 客户端下载文件
save_dir = os.path.join(BASE_DIR, 'temp')
file_path = self.feishu_client.download_file(file_key, file_name, save_dir)
if file_path:
logger.info(f"✅ 文件下载成功:{file_path}")
return file_path
else:
logger.error("❌ 文件下载失败")
return None
# 创建机器人实例
bot = FeishuBot()
def handle_message(message):
"""
处理消息入口函数
Args:
message: 飞书消息字典
Returns:
str: 回复消息
"""
return bot.process_message(message)
if __name__ == '__main__':
# 测试模式
print("🤖 飞书机器人测试模式")
print("=" * 50)
# 测试文字消息
test_message = {
'msg_type': 'text',
'content': '#标题 测试文章\n#分类 ai\n这是测试内容',
'sender': {'sender_id': 'test_user'}
}
reply = bot.process_message(test_message)
print(f"\n📨 回复:\n{reply}")