set_avatar_file
This commit is contained in:
parent
e3c6408802
commit
0e90b550a4
@ -27,7 +27,7 @@ bot-self-modifier/
|
||||
|
||||
支持以下功能:
|
||||
1. **系统提示词** - 读取和修改
|
||||
2. **Bot 基本信息** - 头像、描述、建议问题的读取和修改
|
||||
2. **Bot 基本信息** - 头像(通过本地图片文件上传图床)、描述、建议问题的读取和修改
|
||||
3. **MCP 服务器** - 列表查看、添加和删除
|
||||
4. **技能列表** - 读取、上传、启用、禁用、删除
|
||||
5. **环境变量** - 读取和修改
|
||||
@ -55,8 +55,8 @@ scripts/bot_modifier.py --action get_info
|
||||
# 修改 bot 标题名称
|
||||
scripts/bot_modifier.py --action set_name --value "智能客服助手"
|
||||
|
||||
# 设置头像(URL)
|
||||
scripts/bot_modifier.py --action set_avatar --value "https://example.com/avatar.png"
|
||||
# 设置头像(本地图片文件)—— 上传到图床获取 URL 后自动设置
|
||||
scripts/bot_modifier.py --action set_avatar_file --file /path/to/avatar.png
|
||||
|
||||
# 设置描述
|
||||
scripts/bot_modifier.py --action set_description --value "这是一个智能客服助手"
|
||||
@ -127,7 +127,7 @@ scripts/bot_modifier.py [OPTIONS]
|
||||
| `--mcp-type` | For add_mcp | MCP 服务器类型 (sse/streamable-http) | sse |
|
||||
| `--config` | For add_mcp | MCP 服务器配置 JSON | - |
|
||||
| `--mcp-id` | For delete_mcp | MCP 服务器 ID | - |
|
||||
| `--file` | For upload_skill | Skill zip 文件路径 | - |
|
||||
| `--file` | For upload_skill / set_avatar_file | 文件路径(技能 zip / 头像图片) | - |
|
||||
|
||||
**Available Actions:**
|
||||
|
||||
@ -137,7 +137,7 @@ scripts/bot_modifier.py [OPTIONS]
|
||||
| `set_prompt` | 修改系统提示词(需要 --value) |
|
||||
| `get_info` | 读取 bot 基本信息(名称、头像、描述、建议问题) |
|
||||
| `set_name` | 修改 bot 标题名称(需要 --value) |
|
||||
| `set_avatar` | 设置头像 URL(需要 --value) |
|
||||
| `set_avatar_file` | 上传本地图片作为头像(需要 --file),自动上传图床获取 URL 后设置 |
|
||||
| `set_description` | 设置描述(需要 --value) |
|
||||
| `set_suggestions` | 设置建议问题(需要 --value,JSON 数组) |
|
||||
| `list_mcp` | 列出 MCP 服务器 |
|
||||
|
||||
@ -5,6 +5,9 @@ Bot Self-Modifier Script
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import ftplib
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@ -13,6 +16,27 @@ import urllib.error
|
||||
import urllib.parse
|
||||
|
||||
|
||||
# ============ Avatar FTP upload config ============
|
||||
# Avatar files are uploaded to an FTP server and served via a public base URL.
|
||||
# Defaults can be overridden by environment variables.
|
||||
AVATAR_FTP_HOST = os.environ.get("AVATAR_FTP_HOST", "175.27.142.79")
|
||||
AVATAR_FTP_USER = os.environ.get("AVATAR_FTP_USER", "zhuchaowe")
|
||||
AVATAR_FTP_PASSWORD = os.environ.get("AVATAR_FTP_PASSWORD", "zhu305750624")
|
||||
AVATAR_BASE_URL = os.environ.get("AVATAR_BASE_URL", "https://engine.aitravelmaster.com/avatar")
|
||||
|
||||
|
||||
def upload_avatar_to_ftp(file_data, remote_name):
|
||||
"""Upload avatar bytes to the FTP server and return the public access URL."""
|
||||
try:
|
||||
with ftplib.FTP(AVATAR_FTP_HOST, timeout=30) as ftp:
|
||||
ftp.login(AVATAR_FTP_USER, AVATAR_FTP_PASSWORD)
|
||||
ftp.storbinary(f"STOR {remote_name}", io.BytesIO(file_data))
|
||||
except ftplib.all_errors as e:
|
||||
print(f"ERROR: FTP upload failed - {e}")
|
||||
sys.exit(1)
|
||||
return f"{AVATAR_BASE_URL.rstrip('/')}/{remote_name}"
|
||||
|
||||
|
||||
def get_config():
|
||||
"""获取配置,下面的MASTERKEY和ASSISTANT_ID是从环境变量自动获取的,不需要用户提供"""
|
||||
masterkey = os.environ.get("MASTERKEY", "master")
|
||||
@ -116,8 +140,8 @@ def get_info(bot_id, masterkey, api_base):
|
||||
return result
|
||||
|
||||
|
||||
def set_avatar(bot_id, masterkey, api_base, value):
|
||||
"""设置头像 URL"""
|
||||
def _set_avatar_url(bot_id, masterkey, api_base, value):
|
||||
"""Apply an avatar URL to the bot. Internal helper, only called by set_avatar_file."""
|
||||
url = f"{api_base}/api/v1/bots/{bot_id}/settings"
|
||||
headers = {"Authorization": f"Bearer {masterkey}"}
|
||||
data = {"avatar_url": value}
|
||||
@ -125,6 +149,30 @@ def set_avatar(bot_id, masterkey, api_base, value):
|
||||
print(f"OK: Avatar updated. {result.get('message', '')}")
|
||||
|
||||
|
||||
def set_avatar_file(bot_id, masterkey, api_base, file_path):
|
||||
"""Upload a local image file as the avatar.
|
||||
|
||||
The file is uploaded to the FTP server, then the resulting public URL is
|
||||
saved as the bot's avatar. The remote filename is derived from the content
|
||||
hash so identical images are reused and different images never collide.
|
||||
"""
|
||||
if not os.path.exists(file_path):
|
||||
print(f"ERROR: File not found: {file_path}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
file_data = f.read()
|
||||
|
||||
ext = os.path.splitext(file_path)[1].lower() or ".png"
|
||||
content_hash = hashlib.md5(file_data).hexdigest()[:12]
|
||||
remote_name = f"avatar_{content_hash}{ext}"
|
||||
|
||||
public_url = upload_avatar_to_ftp(file_data, remote_name)
|
||||
print(f"OK: Avatar file uploaded -> {public_url}")
|
||||
|
||||
_set_avatar_url(bot_id, masterkey, api_base, public_url)
|
||||
|
||||
|
||||
def set_name(bot_id, masterkey, api_base, value):
|
||||
"""设置 bot 标题名称"""
|
||||
url = f"{api_base}/api/v1/bots/{bot_id}"
|
||||
@ -369,7 +417,7 @@ def main():
|
||||
required=True,
|
||||
choices=[
|
||||
"get_prompt", "set_prompt",
|
||||
"get_info", "set_name", "set_avatar", "set_description", "set_suggestions",
|
||||
"get_info", "set_name", "set_avatar_file", "set_description", "set_suggestions",
|
||||
"list_mcp", "add_mcp", "delete_mcp",
|
||||
"list_skills", "upload_skill", "enable_skill", "disable_skill", "delete_skill",
|
||||
"get_env", "set_env",
|
||||
@ -381,7 +429,7 @@ def main():
|
||||
parser.add_argument("--mcp-type", default="sse", help="MCP server type (for add_mcp, default: sse)")
|
||||
parser.add_argument("--config", help="MCP server config JSON (for add_mcp)")
|
||||
parser.add_argument("--mcp-id", help="MCP server ID (for delete_mcp)")
|
||||
parser.add_argument("--file", help="Skill zip file path (for upload_skill)")
|
||||
parser.add_argument("--file", help="File path (skill zip for upload_skill, image for set_avatar_file)")
|
||||
|
||||
args = parser.parse_args()
|
||||
masterkey, api_base, bot_id = get_config()
|
||||
@ -391,7 +439,7 @@ def main():
|
||||
"set_prompt": lambda: set_prompt(bot_id, masterkey, api_base, args.value),
|
||||
"get_info": lambda: get_info(bot_id, masterkey, api_base),
|
||||
"set_name": lambda: set_name(bot_id, masterkey, api_base, args.value),
|
||||
"set_avatar": lambda: set_avatar(bot_id, masterkey, api_base, args.value),
|
||||
"set_avatar_file": lambda: set_avatar_file(bot_id, masterkey, api_base, args.file),
|
||||
"set_description": lambda: set_description(bot_id, masterkey, api_base, args.value),
|
||||
"set_suggestions": lambda: set_suggestions(bot_id, masterkey, api_base, args.value),
|
||||
"list_mcp": lambda: list_mcp(bot_id, masterkey, api_base),
|
||||
@ -410,7 +458,7 @@ def main():
|
||||
if args.action == "set_prompt" and not args.value:
|
||||
print("ERROR: --value is required for set_prompt")
|
||||
sys.exit(1)
|
||||
if args.action in ("set_avatar", "set_name", "set_description", "set_suggestions") and not args.value:
|
||||
if args.action in ("set_name", "set_description", "set_suggestions") and not args.value:
|
||||
print(f"ERROR: --value is required for {args.action}")
|
||||
sys.exit(1)
|
||||
if args.action == "add_mcp" and (not args.name or not args.config):
|
||||
@ -422,6 +470,9 @@ def main():
|
||||
if args.action == "upload_skill" and not args.file:
|
||||
print("ERROR: --file is required for upload_skill")
|
||||
sys.exit(1)
|
||||
if args.action == "set_avatar_file" and not args.file:
|
||||
print("ERROR: --file is required for set_avatar_file")
|
||||
sys.exit(1)
|
||||
if args.action in ("enable_skill", "disable_skill", "delete_skill") and not args.value:
|
||||
print(f"ERROR: --value is required for {args.action}")
|
||||
sys.exit(1)
|
||||
|
||||
@ -23,6 +23,26 @@ env:
|
||||
required: false
|
||||
default: http://localhost:8001
|
||||
description: API server base URL
|
||||
AVATAR_FTP_HOST:
|
||||
type: string
|
||||
required: false
|
||||
default: 175.27.142.79
|
||||
description: FTP host for avatar image uploads (set_avatar_file)
|
||||
AVATAR_FTP_USER:
|
||||
type: string
|
||||
required: false
|
||||
default: zhuchaowe
|
||||
description: FTP username for avatar uploads
|
||||
AVATAR_FTP_PASSWORD:
|
||||
type: string
|
||||
required: false
|
||||
default: zhu305750624
|
||||
description: FTP password for avatar uploads
|
||||
AVATAR_BASE_URL:
|
||||
type: string
|
||||
required: false
|
||||
default: https://engine.aitravelmaster.com/avatar
|
||||
description: Public base URL where uploaded avatars are served
|
||||
config:
|
||||
bot_id:
|
||||
type: string
|
||||
|
||||
91
upload.php
Normal file
91
upload.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// Simple file upload API. Saves uploaded files into the avatar directory.
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Only allow POST requests
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['code' => 405, 'message' => 'Method Not Allowed, use POST']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Form field name for the uploaded file
|
||||
$field = 'file';
|
||||
|
||||
// Validate that a file was actually uploaded
|
||||
if (!isset($_FILES[$field]) || $_FILES[$field]['error'] === UPLOAD_ERR_NO_FILE) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['code' => 400, 'message' => 'No file uploaded, field name should be "file"']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$file = $_FILES[$field];
|
||||
|
||||
// Check upload errors reported by PHP
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['code' => 400, 'message' => 'Upload failed, error code: ' . $file['error']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Limit file size to 5MB
|
||||
$maxSize = 5 * 1024 * 1024;
|
||||
if ($file['size'] > $maxSize) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['code' => 400, 'message' => 'File too large, max 5MB']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Allow only common image types (detected by actual content, not the client-provided type)
|
||||
$allowed = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
];
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
|
||||
if (!isset($allowed[$mime])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['code' => 400, 'message' => 'Unsupported file type: ' . $mime]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ensure the avatar directory exists
|
||||
$uploadDir = __DIR__ . '/avatar';
|
||||
if (!is_dir($uploadDir)) {
|
||||
if (!mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['code' => 500, 'message' => 'Failed to create avatar directory']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a safe, unique filename to avoid collisions and path traversal
|
||||
$ext = $allowed[$mime];
|
||||
$filename = bin2hex(random_bytes(16)) . '.' . $ext;
|
||||
$target = $uploadDir . '/' . $filename;
|
||||
|
||||
// Move the uploaded file into place
|
||||
if (!move_uploaded_file($file['tmp_name'], $target)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['code' => 500, 'message' => 'Failed to save file']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Build a public URL relative to the current script location
|
||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$basePath = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/\\');
|
||||
$url = $scheme . '://' . $host . $basePath . '/avatar/' . $filename;
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode([
|
||||
'code' => 0,
|
||||
'message' => 'success',
|
||||
'filename' => $filename,
|
||||
'url' => $url,
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
Loading…
Reference in New Issue
Block a user