UNPKG

karin-plugin-timechange

Version:

Karin智能头像切换插件 - 根据时间自动在白天和晚上切换不同的头像

376 lines (372 loc) 14.7 kB
import { AvatarConfig } from "../chunk-G5C6VDKH.js"; // src/apps/avatar-api.ts import { karin, logger as logger2 } from "node-karin"; // src/utils/file-upload.ts import fs from "fs"; import path from "path"; import { logger } from "node-karin"; var FileUploadHandler = class { avatarConfig; constructor() { this.avatarConfig = new AvatarConfig(); } /** * 处理base64图片上传 * @param base64Data base64编码的图片数据 * @param type 头像类型 'day' | 'night' * @returns 保存的文件路径 */ async handleBase64Upload(base64Data, type) { try { if (!base64Data || !base64Data.startsWith("data:image/")) { throw new Error("\u65E0\u6548\u7684\u56FE\u7247\u6570\u636E\u683C\u5F0F"); } const matches = base64Data.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/); if (!matches) { throw new Error("\u65E0\u6CD5\u89E3\u6790\u56FE\u7247\u6570\u636E"); } const imageType = matches[1].toLowerCase(); const imageBuffer = Buffer.from(matches[2], "base64"); const allowedTypes = ["jpg", "jpeg", "png", "gif", "bmp", "webp"]; if (!allowedTypes.includes(imageType)) { throw new Error(`\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F: ${imageType}`); } const maxSize = 5 * 1024 * 1024; if (imageBuffer.length > maxSize) { throw new Error("\u56FE\u7247\u6587\u4EF6\u8FC7\u5927\uFF0C\u8BF7\u9009\u62E9\u5C0F\u4E8E5MB\u7684\u56FE\u7247"); } const resourcePath = this.avatarConfig.getResourcePath(); if (!fs.existsSync(resourcePath)) { fs.mkdirSync(resourcePath, { recursive: true }); } const fileName = `${type}-avatar.${imageType === "jpeg" ? "jpg" : imageType}`; const filePath = path.join(resourcePath, fileName); fs.writeFileSync(filePath, imageBuffer); if (type === "day") { this.avatarConfig.setDayAvatarPath(filePath); } else { this.avatarConfig.setNightAvatarPath(filePath); } logger.info(`[\u6587\u4EF6\u4E0A\u4F20] ${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u4E0A\u4F20\u6210\u529F: ${filePath}`); return filePath; } catch (error) { logger.error(`[\u6587\u4EF6\u4E0A\u4F20] \u4E0A\u4F20${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u5931\u8D25:`, error); throw error; } } /** * 处理文件路径设置 * @param filePath 文件路径 * @param type 头像类型 'day' | 'night' * @returns 是否设置成功 */ async handleFilePathSetting(filePath, type) { try { if (!filePath || !fs.existsSync(filePath)) { throw new Error("\u6587\u4EF6\u8DEF\u5F84\u4E0D\u5B58\u5728"); } const ext = path.extname(filePath).toLowerCase(); const allowedExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"]; if (!allowedExts.includes(ext)) { throw new Error("\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F"); } const stats = fs.statSync(filePath); const maxSize = 5 * 1024 * 1024; if (stats.size > maxSize) { throw new Error("\u56FE\u7247\u6587\u4EF6\u8FC7\u5927\uFF0C\u8BF7\u9009\u62E9\u5C0F\u4E8E5MB\u7684\u56FE\u7247"); } if (type === "day") { this.avatarConfig.setDayAvatarPath(filePath); } else { this.avatarConfig.setNightAvatarPath(filePath); } logger.info(`[\u6587\u4EF6\u8BBE\u7F6E] ${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u8DEF\u5F84\u8BBE\u7F6E\u6210\u529F: ${filePath}`); return true; } catch (error) { logger.error(`[\u6587\u4EF6\u8BBE\u7F6E] \u8BBE\u7F6E${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u8DEF\u5F84\u5931\u8D25:`, error); throw error; } } /** * 获取头像文件的base64数据(用于预览) * @param type 头像类型 'day' | 'night' * @returns base64数据或null */ getAvatarBase64(type) { try { const config = this.avatarConfig.getConfig(); const filePath = type === "day" ? config.dayAvatarPath : config.nightAvatarPath; if (!filePath || !fs.existsSync(filePath)) { return null; } const fileBuffer = fs.readFileSync(filePath); const ext = path.extname(filePath).toLowerCase().substring(1); const mimeType = this.getMimeType(ext); return `data:${mimeType};base64,${fileBuffer.toString("base64")}`; } catch (error) { logger.error(`[\u5934\u50CF\u9884\u89C8] \u83B7\u53D6${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u9884\u89C8\u5931\u8D25:`, error); return null; } } /** * 根据文件扩展名获取MIME类型 * @param ext 文件扩展名 * @returns MIME类型 */ getMimeType(ext) { const mimeTypes = { "jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "gif": "image/gif", "bmp": "image/bmp", "webp": "image/webp" }; return mimeTypes[ext] || "image/jpeg"; } /** * 删除头像文件 * @param type 头像类型 'day' | 'night' * @returns 是否删除成功 */ deleteAvatar(type) { try { const config = this.avatarConfig.getConfig(); const filePath = type === "day" ? config.dayAvatarPath : config.nightAvatarPath; if (!filePath) { return true; } if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); logger.info(`[\u6587\u4EF6\u5220\u9664] ${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u6587\u4EF6\u5DF2\u5220\u9664: ${filePath}`); } if (type === "day") { this.avatarConfig.setDayAvatarPath(""); } else { this.avatarConfig.setNightAvatarPath(""); } return true; } catch (error) { logger.error(`[\u6587\u4EF6\u5220\u9664] \u5220\u9664${type === "day" ? "\u767D\u5929" : "\u665A\u4E0A"}\u5934\u50CF\u5931\u8D25:`, error); return false; } } /** * 获取文件信息 * @param filePath 文件路径 * @returns 文件信息 */ getFileInfo(filePath) { try { if (!filePath || !fs.existsSync(filePath)) { return { exists: false }; } const stats = fs.statSync(filePath); const sizeInMB = (stats.size / (1024 * 1024)).toFixed(2); return { exists: true, size: stats.size, sizeText: `${sizeInMB}MB` }; } catch (error) { return { exists: false }; } } /** * 验证图片文件 * @param filePath 文件路径 * @returns 验证结果 */ validateImageFile(filePath) { try { if (!filePath) { return { valid: false, error: "\u6587\u4EF6\u8DEF\u5F84\u4E3A\u7A7A" }; } if (!fs.existsSync(filePath)) { return { valid: false, error: "\u6587\u4EF6\u4E0D\u5B58\u5728" }; } const ext = path.extname(filePath).toLowerCase(); const allowedExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"]; if (!allowedExts.includes(ext)) { return { valid: false, error: "\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F" }; } const stats = fs.statSync(filePath); const maxSize = 5 * 1024 * 1024; if (stats.size > maxSize) { return { valid: false, error: "\u6587\u4EF6\u8FC7\u5927\uFF0C\u8BF7\u9009\u62E9\u5C0F\u4E8E5MB\u7684\u56FE\u7247" }; } return { valid: true }; } catch (error) { return { valid: false, error: `\u9A8C\u8BC1\u5931\u8D25: ${error}` }; } } }; // src/apps/avatar-api.ts var fileUploadHandler = new FileUploadHandler(); var avatarConfig = new AvatarConfig(); var avatarUploadApi = karin.command("^(#)?\u4E0A\u4F20\u5934\u50CF\\s+(\u767D\u5929|\u665A\u4E0A)$", async (e) => { const timeType = e.msg.includes("\u767D\u5929") ? "day" : "night"; const timeText = timeType === "day" ? "\u767D\u5929" : "\u665A\u4E0A"; await e.reply(`\u8BF7\u53D1\u9001\u8981\u8BBE\u7F6E\u4E3A${timeText}\u5934\u50CF\u7684\u56FE\u7247`); const response = await e.waitInput(6e4); if (!response) { await e.reply("\u274C \u8D85\u65F6\u672A\u6536\u5230\u56DE\u590D\uFF0C\u4E0A\u4F20\u53D6\u6D88"); return true; } if (response.elements && response.elements.length > 0) { for (const element of response.elements) { if (element.type === "image") { try { if (element.url) { const imageResponse = await fetch(element.url); if (!imageResponse.ok) { throw new Error(`\u4E0B\u8F7D\u56FE\u7247\u5931\u8D25: ${imageResponse.status}`); } const arrayBuffer = await imageResponse.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const imageType = detectImageType(buffer); if (!imageType) { await e.reply("\u274C \u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F\uFF0C\u8BF7\u53D1\u9001 jpg\u3001png\u3001gif\u3001bmp \u6216 webp \u683C\u5F0F\u7684\u56FE\u7247"); return true; } const base64Data = `data:image/${imageType};base64,${buffer.toString("base64")}`; const filePath = await fileUploadHandler.handleBase64Upload(base64Data, timeType); await e.reply(`\u2705 ${timeText}\u5934\u50CF\u4E0A\u4F20\u6210\u529F\uFF01 \u{1F4C1} \u4FDD\u5B58\u8DEF\u5F84: ${filePath}`); const config = avatarConfig.getConfig(); if (config.autoSwitch) { await e.reply("\u{1F4A1} \u81EA\u52A8\u5207\u6362\u5DF2\u5F00\u542F\uFF0C\u63D2\u4EF6\u5C06\u5728\u9002\u5F53\u65F6\u95F4\u81EA\u52A8\u4F7F\u7528\u65B0\u5934\u50CF"); } } else { await e.reply("\u274C \u65E0\u6CD5\u83B7\u53D6\u56FE\u7247\u6570\u636E\uFF0C\u8BF7\u91CD\u8BD5"); } } catch (error) { logger2.error(`[\u5934\u50CF\u4E0A\u4F20] \u4E0A\u4F20${timeText}\u5934\u50CF\u5931\u8D25:`, error); await e.reply(`\u274C ${timeText}\u5934\u50CF\u4E0A\u4F20\u5931\u8D25: ${error}`); } return true; } } } await e.reply("\u274C \u8BF7\u53D1\u9001\u56FE\u7247\u6587\u4EF6"); return true; }, { name: "\u4E0A\u4F20\u5934\u50CF", perm: "master", authFailMsg: "\u53EA\u6709\u4E3B\u4EBA\u624D\u80FD\u4E0A\u4F20\u5934\u50CF\u54E6~" }); var avatarPreview = karin.command("^(#)?\u5934\u50CF\u9884\u89C8\\s+(\u767D\u5929|\u665A\u4E0A)$", async (e) => { const timeType = e.msg.includes("\u767D\u5929") ? "day" : "night"; const timeText = timeType === "day" ? "\u767D\u5929" : "\u665A\u4E0A"; try { const base64Data = fileUploadHandler.getAvatarBase64(timeType); if (!base64Data) { await e.reply(`\u274C \u672A\u627E\u5230${timeText}\u5934\u50CF\uFF0C\u8BF7\u5148\u4E0A\u4F20\u5934\u50CF`); return true; } await e.reply([ `\u{1F5BC}\uFE0F ${timeText}\u5934\u50CF\u9884\u89C8\uFF1A`, { type: "image", url: base64Data } ]); } catch (error) { logger2.error(`[\u5934\u50CF\u9884\u89C8] \u83B7\u53D6${timeText}\u5934\u50CF\u9884\u89C8\u5931\u8D25:`, error); await e.reply(`\u274C \u83B7\u53D6${timeText}\u5934\u50CF\u9884\u89C8\u5931\u8D25: ${error}`); } return true; }, { name: "\u5934\u50CF\u9884\u89C8", perm: "master", authFailMsg: "\u53EA\u6709\u4E3B\u4EBA\u624D\u80FD\u67E5\u770B\u5934\u50CF\u9884\u89C8\u54E6~" }); var deleteAvatar = karin.command("^(#)?\u5220\u9664\u5934\u50CF\\s+(\u767D\u5929|\u665A\u4E0A)$", async (e) => { const timeType = e.msg.includes("\u767D\u5929") ? "day" : "night"; const timeText = timeType === "day" ? "\u767D\u5929" : "\u665A\u4E0A"; try { const success = fileUploadHandler.deleteAvatar(timeType); if (success) { await e.reply(`\u2705 ${timeText}\u5934\u50CF\u5DF2\u5220\u9664`); } else { await e.reply(`\u274C \u5220\u9664${timeText}\u5934\u50CF\u5931\u8D25`); } } catch (error) { logger2.error(`[\u5934\u50CF\u5220\u9664] \u5220\u9664${timeText}\u5934\u50CF\u5931\u8D25:`, error); await e.reply(`\u274C \u5220\u9664${timeText}\u5934\u50CF\u5931\u8D25: ${error}`); } return true; }, { name: "\u5220\u9664\u5934\u50CF", perm: "master", authFailMsg: "\u53EA\u6709\u4E3B\u4EBA\u624D\u80FD\u5220\u9664\u5934\u50CF\u54E6~" }); var avatarInfo = karin.command("^(#)?\u5934\u50CF\u4FE1\u606F$", async (e) => { try { const config = avatarConfig.getConfig(); let infoMsg = "\u{1F4CA} \u5934\u50CF\u6587\u4EF6\u4FE1\u606F\n"; infoMsg += "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"; const dayInfo = fileUploadHandler.getFileInfo(config.dayAvatarPath); infoMsg += `\u{1F305} \u767D\u5929\u5934\u50CF: `; if (dayInfo.exists) { infoMsg += ` \u2705 \u6587\u4EF6\u5B58\u5728 (${dayInfo.sizeText}) `; infoMsg += ` \u{1F4C1} ${config.dayAvatarPath} `; } else { infoMsg += ` \u274C \u672A\u914D\u7F6E\u6216\u6587\u4EF6\u4E0D\u5B58\u5728 `; } infoMsg += "\n"; const nightInfo = fileUploadHandler.getFileInfo(config.nightAvatarPath); infoMsg += `\u{1F319} \u665A\u4E0A\u5934\u50CF: `; if (nightInfo.exists) { infoMsg += ` \u2705 \u6587\u4EF6\u5B58\u5728 (${nightInfo.sizeText}) `; infoMsg += ` \u{1F4C1} ${config.nightAvatarPath} `; } else { infoMsg += ` \u274C \u672A\u914D\u7F6E\u6216\u6587\u4EF6\u4E0D\u5B58\u5728 `; } infoMsg += "\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"; infoMsg += "\u{1F4A1} \u4F7F\u7528 #\u4E0A\u4F20\u5934\u50CF \u767D\u5929/\u665A\u4E0A \u6765\u4E0A\u4F20\u65B0\u5934\u50CF"; await e.reply(infoMsg); } catch (error) { logger2.error("[\u5934\u50CF\u4FE1\u606F] \u83B7\u53D6\u5934\u50CF\u4FE1\u606F\u5931\u8D25:", error); await e.reply(`\u274C \u83B7\u53D6\u5934\u50CF\u4FE1\u606F\u5931\u8D25: ${error}`); } return true; }, { name: "\u5934\u50CF\u4FE1\u606F", perm: "master", authFailMsg: "\u53EA\u6709\u4E3B\u4EBA\u624D\u80FD\u67E5\u770B\u5934\u50CF\u4FE1\u606F\u54E6~" }); function detectImageType(buffer) { if (buffer.length < 4) return null; if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) { return "jpeg"; } if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71) { return "png"; } if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) { return "gif"; } if (buffer[0] === 66 && buffer[1] === 77) { return "bmp"; } if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70) { if (buffer.length >= 12 && buffer[8] === 87 && buffer[9] === 69 && buffer[10] === 66 && buffer[11] === 80) { return "webp"; } } return null; } export { avatarInfo, avatarPreview, avatarUploadApi, deleteAvatar };