Files
xiaoli-cli/plugins/operit/beeimg_image_uploader.ts
T
xiaoli-cli bot dfe21298b9 release: v5.4.2 - terminal模拟器 + Operit桥接
新增插件:
- terminal: 持久交互式终端,纯标准库,全平台,13个测试
- operit_bridge: Operit AI工具格式桥接,47个脚本288个工具,12个测试

其他:
- plugins/operit/: 47个Operit官方脚本
- 更新plugin_registry.json/plugins_config.py
- 更新about.txt/CHANGELOG.md至v5.4.2
2026-06-23 08:50:15 +00:00

191 lines
7.4 KiB
TypeScript
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.
/* METADATA
{
"name": "beeimg_image_uploader_v2",
"display_name": {
"zh": "Beeimg 图片上传器 V2",
"en": "Beeimg Image Uploader V2"
},
"description": {
"zh": "BeeIMG(https://beeimg.com/)工具将本地图片上传到图床并返回图片url,配合图片生成工具实现图生图(图生图务必开启)。",
"en": "Upload a local image to BeeIMG (https://beeimg.com/) and return a public image URL. Useful for image-to-image workflows (make sure img2img is enabled)."
},
"env": [
"BEEIMG_API_KEY"
],
"category": "Media",
"tools": [
{
"name": "upload_image",
"description": {
"zh": "使用 multipart 上传将本地图片上传到 BeeIMG 图床并返回图片 url。",
"en": "Upload a local image to BeeIMG via multipart upload and return the image URL."
},
"parameters": [
{ "name": "file_path", "description": { "zh": "要上传的图片文件绝对路径 (建议使用 /sdcard/ 开头的完整路径)。", "en": "Absolute path of the image file to upload (recommended: full path starting with /sdcard/)." }, "type": "string", "required": true },
{ "name": "album_id", "description": { "zh": "相册ID (可选)。", "en": "Album ID (optional)." }, "type": "string", "required": false },
{ "name": "privacy", "description": { "zh": "隐私设置,'public' 或 'private' (可选)。", "en": "Privacy setting: 'public' or 'private' (optional)." }, "type": "string", "required": false }
]
}
]
}*/
const beeimgUploader = (function () {
// API配置
const API_ENDPOINT = "https://beeimg.com/api/upload/file/json/";
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
}
function getErrorStack(error: unknown): string | undefined {
if (error instanceof Error) return error.stack;
return undefined;
}
function getApiKey(): string {
return getEnv("BEEIMG_API_KEY") || "";
}
function guessMimeTypeFromPath(filePath: string): string {
const lower = filePath.toLowerCase();
if (lower.endsWith(".png")) return "image/png";
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
if (lower.endsWith(".webp")) return "image/webp";
if (lower.endsWith(".gif")) return "image/gif";
return "application/octet-stream";
}
function safeJsonParseLoose(text: string): unknown {
const trimmed = (text || "").trim();
if (!trimmed) return null;
try {
return JSON.parse(trimmed) as unknown;
} catch (e) {
const start = trimmed.indexOf("{");
const end = trimmed.lastIndexOf("}");
if (start !== -1 && end !== -1 && end > start) {
return JSON.parse(trimmed.substring(start, end + 1)) as unknown;
}
throw e;
}
}
interface UploadImageParams {
file_path: string;
album_id?: string;
privacy?: string;
}
interface UploadImageResult {
url: string;
thumbnail_url?: string;
page_url?: string;
details: string;
}
async function upload_image(params: UploadImageParams): Promise<UploadImageResult> {
const file_path = params?.file_path;
const album_id = params?.album_id;
const privacy = params?.privacy;
if (!file_path || String(file_path).trim().length === 0) {
throw new Error("参数 file_path 不能为空。");
}
// 1. 检查文件是否存在
const fileExists = await Tools.Files.exists(file_path);
if (!fileExists.exists) {
throw new Error(`文件未找到: ${file_path} (请尝试使用 /sdcard/ 开头的绝对路径)`);
}
// 2. 准备参数
const apiKey = getApiKey();
if (!apiKey) {
throw new Error("BEEIMG_API_KEY 未配置,请在环境变量中设置 BeeIMG 的 API Key。");
}
const form_data: Record<string, string> = {
apikey: apiKey
};
if (album_id) form_data.albumid = String(album_id);
if (privacy) form_data.privacy = String(privacy);
// 3. 发起 multipart 上传
console.log("正在执行上传...");
const resp = await Tools.Net.uploadFile({
url: API_ENDPOINT,
method: "POST",
headers: {
// 尽量模拟浏览器 UA,降低被风控概率
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
},
form_data,
files: [
{
field_name: "file",
file_path: file_path,
content_type: guessMimeTypeFromPath(file_path)
}
]
});
if (resp.statusCode < 200 || resp.statusCode >= 300) {
throw new Error(`上传请求失败 (HTTP ${resp.statusCode})。可能是网络问题或 IP 被屏蔽。\n响应: ${resp.content}`);
}
const responseText = (resp.content || "").trim();
if (!responseText) {
throw new Error("上传失败:服务器返回了空内容。请检查网络连接或尝试关闭 VPN。");
}
let jsonResponse: unknown;
try {
jsonResponse = safeJsonParseLoose(responseText);
} catch (e: unknown) {
throw new Error(`API 响应解析失败。服务器返回内容不是 JSON:\n${responseText.substring(0, 200)}...`);
}
const files = isRecord(jsonResponse) && isRecord(jsonResponse["files"]) ? (jsonResponse["files"] as Record<string, unknown>) : null;
const ok = !!files && (files["status"] === "Success" || files["code"] === "200" || files["code"] === 200);
const url = files ? files["url"] : undefined;
if (ok && (typeof url === "string" || typeof url === "number") && String(url).trim().length > 0) {
return {
url: String(url),
thumbnail_url: files && files["thumbnail_url"] ? String(files["thumbnail_url"]) : undefined,
page_url: files && files["view_url"] ? String(files["view_url"]) : undefined,
details: `上传成功! URL: ${String(url)}`
};
} else {
const errMsg = files
? (files["status"] || files["message"] || JSON.stringify(files))
: "未知错误";
throw new Error(`BeeIMG API 报错: ${errMsg}`);
}
}
async function wrap<TParams, TResult>(func: (params: TParams) => Promise<TResult>, params: TParams) {
try {
const result = await func(params);
complete({ success: true, message: "图片上传成功", data: result });
}
catch (error: unknown) {
console.error(`Error: ${getErrorMessage(error)}`);
complete({ success: false, message: getErrorMessage(error), error_stack: getErrorStack(error) });
}
}
return {
upload_image: (p: UploadImageParams) => wrap(upload_image, p)
};
})();
exports.upload_image = beeimgUploader.upload_image;