import { invoke } from "@tauri-apps/api/core";
* UAPI (uapis.cn) 前端封装。
*
* 当前支持「随机图片」接口:
* GET https://uapis.cn/api/v1/random/image
*
* 调用链:前端 → Tauri 命令 `get_random_image`(Rust 后端发起 HTTP 请求,
* 携带 Authorization: Bearer <KEY>,规避 WebView 跨域限制)→ base64。
*/
export const RANDOM_IMAGE_CATEGORIES = [
"acg",
"landscape",
"anime",
"pc_wallpaper",
"mobile_wallpaper",
"general_anime",
"ai_drawing",
"bq",
"furry",
] as const;
export const RANDOM_IMAGE_CATEGORY_LABELS: Record<string, string> = {
acg: "二次元动漫",
landscape: "风景图",
anime: "混合动漫",
pc_wallpaper: "电脑壁纸",
mobile_wallpaper: "手机壁纸",
general_anime: "动漫图",
ai_drawing: "AI绘画",
bq: "表情包/趣图",
furry: "福瑞",
};
export const RANDOM_IMAGE_TYPES = [
"pc",
"mb",
"eciyuan",
"ikun",
"4k",
"s4k",
"z4k",
"szs8k",
"xiongmao",
"maomao",
"waiguoren",
] as const;
export const RANDOM_IMAGE_TYPE_LABELS: Record<string, string> = {
pc: "电脑",
mb: "手机",
eciyuan: "二次元",
ikun: "iKun",
"4k": "4K",
s4k: "横屏4K",
z4k: "竖屏4K",
szs8k: "竖屏8K",
xiongmao: "熊猫头",
maomao: "猫猫",
waiguoren: "外国人",
};
export const RANDOM_IMAGE_TYPE_MAP: Record<string, readonly string[]> = {
acg: ["pc", "mb"],
bq: ["xiongmao", "waiguoren", "maomao", "ikun", "eciyuan"],
furry: ["z4k", "szs8k", "s4k", "4k"],
};
export const RANDOM_IMAGE_TYPELESS_CATEGORIES: readonly string[] = [
"landscape",
"anime",
"pc_wallpaper",
"mobile_wallpaper",
"general_anime",
"ai_drawing",
];
export type RandomImageCategory = (typeof RANDOM_IMAGE_CATEGORIES)[number];
export type RandomImageType = (typeof RANDOM_IMAGE_TYPES)[number];
* 解析后端返回的错误字符串(格式 `CODE|message`),映射为可直接展示的提示。
* 未知 CODE 时回退展示原始 message。
*/
export function parseUapiError(raw: string): string {
const code = raw.split("|")[0] ?? "";
const message = raw.includes("|") ? raw.split("|").slice(1).join("|") : raw;
switch (code) {
case "NOT_FOUND":
return "未找到该类别的图片,请换个分类或稍后再试";
case "INTERNAL_SERVER_ERROR":
return "服务器内部错误,请稍后重试";
case "INVALID_CATEGORY":
return message;
case "INVALID_TYPE":
return message;
case "NETWORK_ERROR":
return "网络请求失败,请检查网络连接";
case "READ_ERROR":
return "读取图片数据失败,请重试";
case "CLIENT_ERROR":
return message;
case "EMPTY_RESPONSE":
return "接口返回了空内容,请重试";
case "EMPTY_DATA":
return "图片数据为空,无法保存";
case "INVALID_PATH":
return message;
case "DECODE_ERROR":
return message;
case "WRITE_ERROR":
return message;
case "401":
return "API 密钥无效或已过期,请在设置中检查";
case "402":
return "积分或余额不足,无法获取图片";
case "403":
return "访问被拒绝,请检查 API 密钥与接口权限";
case "429":
return "请求过于频繁,请稍后再试";
default:
return message || `请求失败(${code})`;
}
}
* 获取一张随机图片,返回图片二进制的 base64 字符串。
*
* @param category 可选主类别(acg / landscape / anime / ...),不传则全局随机
* @param type 可选子类别(pc / mb / z4k / ...),仅部分主类别支持
* @returns base64 字符串(image/jpeg)
*
* 密钥来源:由后端从环境变量 UAPI_API_KEY 读取(设置页已不提供密钥配置)。
*/
export async function fetchRandomImageRaw(
category?: RandomImageCategory | string,
type?: RandomImageType | string
): Promise<string> {
let base64: string;
try {
base64 = await invoke<string>("get_random_image", {
category: category ?? null,
imageType: type ?? null,
apiKey: null,
});
} catch (e) {
const raw = typeof e === "string" ? e : e instanceof Error ? e.message : String(e);
throw new Error(parseUapiError(raw));
}
return base64;
}
* 获取一张随机图片,返回可直接用于 <img> 的 Blob。
*/
export async function fetchRandomImage(
category?: RandomImageCategory | string,
type?: RandomImageType | string
): Promise<Blob> {
return base64ToBlob(await fetchRandomImageRaw(category, type));
}
* 将随机图片的 base64 数据保存到指定路径(配合 `save()` 对话框使用)。
*/
export async function saveRandomImage(base64: string, path: string): Promise<void> {
try {
await invoke("save_random_image_bytes", { base64Data: base64, path });
} catch (e) {
const raw = typeof e === "string" ? e : e instanceof Error ? e.message : String(e);
throw new Error(parseUapiError(raw));
}
}
export function base64ToBlob(base64: string, mimeType = "image/jpeg"): Blob {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return new Blob([bytes], { type: mimeType });
}