diff --git a/apps/bym.js b/apps/bym.js index 4b912bb..ac6cf93 100644 --- a/apps/bym.js +++ b/apps/bym.js @@ -17,7 +17,7 @@ import { JinyanTool } from '../utils/tools/JinyanTool.js' import { KickOutTool } from '../utils/tools/KickOutTool.js' import { SetTitleTool } from '../utils/tools/SetTitleTool.js' import { SerpTool } from '../utils/tools/SerpTool.js' -import { getToimg, downImg, fileImgList } from '../utils/ToDoimg.js' +import { initializeImageTool } from '../utils/tools/ImageTool.js' const DefaultConfig = { returnQQ: [], @@ -52,75 +52,85 @@ export class bym extends plugin { } ] }) + this.initializeConfig() + } + initializeConfig() { if (typeof Config.assistantLabel === 'string') { Config.assistantLabel = [Config.assistantLabel] } - Config.assistantLabel = Config.assistantLabel || DefaultConfig.assistantLabel - Config.returnQQ = Config.returnQQ || DefaultConfig.returnQQ - Config.GroupList = Config.GroupList || DefaultConfig.GroupList - Config.UserList = Config.UserList || DefaultConfig.UserList - Config.enableBYM = Config.enableBYM ?? DefaultConfig.enableBYM - Config.bymPreset = Config.bymPreset || DefaultConfig.bymPreset - Config.bymFuckPrompt = Config.bymFuckPrompt || DefaultConfig.bymFuckPrompt - Config.blockWords = Config.blockWords || DefaultConfig.blockWords - Config.AutoToDownImg = Config.AutoToDownImg ?? DefaultConfig.AutoToDownImg - Config.debug = Config.debug ?? DefaultConfig.debug + Object.entries(DefaultConfig).forEach(([key, value]) => { + Config[key] = Config[key] ?? value + }) } + async readConfigData(id, configList) { + let data = { + chatsList: 20, + propNum: 0, + notOfGroup: false, + maxText: 50 + } + + const matchedConfig = configList.find(item => String(item.id) === String(id)) + if (matchedConfig) { + data.chatsList = parseInt(matchedConfig.chatslist) || data.chatsList + data.propNum = parseInt(matchedConfig.propNum) || data.propNum + data.notOfGroup = matchedConfig.notofgroup || data.notOfGroup + data.maxText = parseInt(matchedConfig.maxtext) || data.maxText + } + return data + } /** 复读 */ async bym(e) { - if (!Config.enableBYM) { - return false - } - const sender = e.sender.user_id - const atbot = e.atme - const group = e.group_id - let IsAtBot = false - let ALLRole = "default" - let ChatsList = 20 - let MaxText = 50 - let prop = Math.floor(Math.random() * 100) + if (!Config.enableBYM) return false - if (!Config.returnQQ.includes(sender)) { - const group_data = await ReadArr(group, Config.GroupList) - const user_data = await ReadArr(sender, Config.UserList) - MaxText = user_data[3] !== group_data[3] ? user_data[3] : group_data[3] - prop = user_data[2] ? user_data[1] : group_data[1] - ChatsList = group_data[0] - if (Config.assistantLabel.some(UserMsg => e.msg?.toLowerCase().includes(UserMsg.toLowerCase())) || atbot) { - prop = -1 - IsAtBot = true - } else { - if (Config.UserList.some(index => index.id === sender)) { - if (user_data[2]) logger.info(`单独概率用户`) - } - if (user_data[2] && !Config.UserList.some(Id => group.includes(Id)) && !Config.GroupList.length) return false - } - async function ReadArr(i, arrlist) { - let NotfoGroup - if (arrlist.some(index => String(index.id) === String(i))) { - let ServerProp = prop - for (let user of arrlist) { - if (String(user.id) === String(i)) { - ChatsList = parseInt(user?.chatslist) || ChatsList - prop = parseInt(user?.propNum) || prop - NotfoGroup = user?.notofgroup || false - MaxText = parseInt(user?.maxtext) || MaxText - } - } - ServerProp -= prop - prop = Math.max(-1, ServerProp) - } - return [ChatsList, prop, NotfoGroup, MaxText] - } + const sender = e.sender.user_id + const atBot = e.atme + const group = e.group_id + let ALLRole = 'default' + + + if (Config.returnQQ.includes(sender)) return false + + const context = { + isAtBot: false, + shouldRespond: false, + maxText: 50, + probability: 0, + chatsList: 20 + } + + const groupData = await this.readConfigData(group, Config.GroupList) + const userData = await this.readConfigData(sender, Config.UserList) + + context.maxText = userData.maxText !== groupData.maxText ? userData.maxText : groupData.maxText + context.probability = userData.notOfGroup ? userData.propNum : groupData.propNum + context.chatsList = groupData.chatsList + + if (Config.assistantLabel.some(label => e.msg?.toLowerCase().includes(label.toLowerCase())) || atBot) { + context.probability = 100 + context.isAtBot = true } else { - return false + if (Config.UserList.some(user => user.id === sender)) { + if (userData.notOfGroup) { + logger.info('单独概率用户') + } + } + if (userData.notOfGroup && + !Config.UserList.some(user => group.includes(user.id)) && + !Config.GroupList.length) { + return null + } } - if (prop < 0) { + + context.shouldRespond = Math.floor(Math.random() * 100) - context.probability < 0 + + if (context.shouldRespond) { await bymGo() - } + } else return false async function bymGo(NotToImg) { + let opt = { maxOutputTokens: 500, temperature: 1, @@ -128,7 +138,7 @@ export class bym extends plugin { } let imgs = await getImg(e) if (!e.msg) { - if (imgs && imgs.length > 0) { + if (imgs?.length > 0) { let image = imgs[0] const response = await fetch(image) const base64Image = Buffer.from(await response.arrayBuffer()) @@ -142,20 +152,19 @@ export class bym extends plugin { } } - if (!opt.image && imgs && imgs.length > 0) { + if (!opt.image && imgs?.length > 0) { let image = imgs[0] const response = await fetch(image) const base64Image = Buffer.from(await response.arrayBuffer()) opt.image = base64Image.toString('base64') } + logger.info('[bymGo] 开始处理回复') - let ForRole = ALLRole - if (opt.image && !IsAtBot && !NotToImg && !e.at && Config.AutoToDownImg) { + let previousRole = ALLRole + if (opt.image && !context.isAtBot && !NotToImg && !e.at && Config.AutoToDownImg) { ALLRole = 'downimg' } - - const ImgList = await fileImgList() - + const now = new Date(); const DateTime = now.toLocaleString() let Dateday = now.getDay() === 0 ? '日' : now.getDay() @@ -169,7 +178,7 @@ export class bym extends plugin { const txmod = [ '"app":"com.tencent.multimsg","config"', '"app":"com.tencent.structmsg","config"' - + ] let candidate = Config.bymPreset function replaceUserInput(input) { @@ -179,11 +188,11 @@ export class bym extends plugin { } return result; } - if (IsAtBot) { + if (context.isAtBot) { if (e.msg) { const originalMsg = e.msg; const replacedMsg = replaceUserInput(e.msg); - + if (originalMsg !== replacedMsg) { e.msg = replacedMsg; } @@ -194,7 +203,7 @@ export class bym extends plugin { RecallMsg = true candidate += Config.bymFuckPrompt } - if (e.msg.length >= MaxText && !txmod.some(UserMsg => e.msg?.includes(UserMsg))) { + if (e.msg.length >= context.maxText && !txmod.some(UserMsg => e.msg?.includes(UserMsg))) { const userIndex = RoleFalseUser.findIndex(user => user.UserQQ === e.user_id); if (userIndex === -1) { @@ -225,7 +234,7 @@ export class bym extends plugin { logger.info(log) } - let chats = await getChatHistoryGroup(e, ChatsList) + let chats = await getChatHistoryGroup(e, context.chatsList) chats = chats .filter(chat => !Config.returnQQ.includes(chat.user_id)) @@ -240,8 +249,8 @@ export class bym extends plugin { async function SearchRole(user_role) { let Role; - - switch(user_role) { + + switch (user_role) { case "downimg": Role = '现在看到的是一张图片,若你觉得是一张表情包,并不是通知,或其他的图片,注意辨别图片文字是否为通知;单纯是表情包,请发送 DOWNIMG: 命名该表情。 不需要发送过多的参数,只需要发送格式DOWNIMG: 命名该表情,注意不需要携带后缀,请以你的角度觉得如果要发这个表情包要用什么名字来命名; 若不是表情包等,及发送NOTIMG'; break; @@ -251,20 +260,13 @@ export class bym extends plugin { `以下是聊天记录: ${Group_Chat} \n你的回复应该尽可能简练,像人类一样随意,不要附加任何奇怪的东西,如聊天记录的格式(比如${Config.assistantLabel}:),禁止重复聊天记录。 - 注意当前时间与日期为${DateTime},星期${Dateday},24小时制,时区已正确,不要被日志的时间与其他时间搞混了,如果有人咨询时间就使用${DateTime},星期${Dateday}这个时间,群友与你几乎在一个时区,若有人说或做的事情与时间段不合理,反驳他,注意除了他声明了自己的时区 - 以下是可用的表情包列表 - ${ImgList}` + - (ImgList.length > 0 && Config.AutoToDownImg ? ` - 如果要发送表情包,请根据该格式 GETIMG: 完整表情包名称,实例 GETIMG: 挠头.gif 即可发送,注意发送完整名称 - 可根据聊天,选择表情包发送。禁止发送多余的格式与说明。发送格式为 注意前面不需要换行 GETIMG: 挠头.gif 不需要换行 - 不要被日志和其他聊天消息的格式迷惑,请保持标准格式,禁止发送[表情包:xxx]、[图片]!!!,禁止发送[表情包:xxx]、[图片]!!! - ` : ''); + 注意当前时间与日期为${DateTime},星期${Dateday},24小时制,时区已正确,不要被日志的时间与其他时间搞混了,如果有人咨询时间就使用${DateTime},星期${Dateday}这个时间,群友与你几乎在一个时区,若有人说或做的事情与时间段不合理,反驳他,注意除了他声明了自己的时区` break; default: logger.error(`未知的 Role 类型:${user_role},使用默认 Role`); Role = `你的名字是"${Config.assistantLabel}",你在一个qq群里。请简短回复。`; } - + return Role; } opt.system = Role @@ -306,6 +308,14 @@ export class bym extends plugin { if (e.group.is_owner) { tools.push(new SetTitleTool()) } + + const imageTool = await initializeImageTool(e, previousRole, bymGo) // 注意这里需要 await + if (Config.AutoToDownImg) { + tools.push(imageTool) + const imagePrompt = await imageTool.getSystemPrompt() // 使用 await + opt.system += '\n' + imagePrompt + } + client.addTools(tools) let rsp = await client.sendMessage(e.msg, opt) let text = rsp.text @@ -318,32 +328,11 @@ export class bym extends plugin { if (text[text.indexOf(t) + t.length] === '?') { t += '?' } - const getImgRegex = /GETIMG:\s*([\s\S]+?)\s*$/i; - const match1 = t.match(getImgRegex); - if (match1) { - const tag = match1[1].trim(); - if (tag === "") { - t = t.replace(getImgRegex, ' ').trim(); - } else { - await getToimg(e, tag); - t = t.replace(getImgRegex, ' ').trim(); - } - } - const notImgRegex = /NOTIMG(.*)/i; - const notmatch = t.match(notImgRegex); - if (notmatch) { - t = null - ALLRole = ForRole - await bymGo(true) - } - const downImgRegex = /DOWNIMG:\s*(.+)/i; - const match = t?.match(downImgRegex); - if (match) { - await downImg(e, opt.image, t); - continue; - } + const processed = await imageTool.processText(t, { + image: opt.image + }) - if (t) { + if (!processed) { let finalMsg = await convertFaces(t, true, e) logger.info(JSON.stringify(finalMsg)) if (Math.floor(Math.random() * 100) < 10) { diff --git a/utils/ToDoimg.js b/utils/ToDoimg.js index cee0584..f5bb250 100644 --- a/utils/ToDoimg.js +++ b/utils/ToDoimg.js @@ -2,127 +2,143 @@ import fs from "fs"; import pathModule from 'path'; import { fileTypeFromBuffer } from 'file-type'; import moment from 'moment'; -const _path = process.cwd(); -const path = _path + "/temp/tp-bq"; -// 没文件夹就创建一个 -if (!fs.existsSync(path)) { - fs.mkdirSync(path, { recursive: true }) -} -if (!fs.existsSync(pathModule.join(path, 'pictures'))) { - fs.mkdirSync(pathModule.join(path, 'pictures'), { recursive: true }) -} +// 配置 +const ROOT_PATH = process.cwd(); +const PICTURES_DIR = pathModule.join(ROOT_PATH, "temp/tp-bq", "pictures"); + +// 工具函数 +const createDirIfNotExists = (dir) => { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +}; + +const sanitizeFilename = (name) => { + return name + .replace(/[\u200B-\u200D\uFEFF]/g, '') + .replace(/[\[\]]/g, '') + .trim() + .replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_.]/g, '-'); +}; + +const findMatchingFiles = (fileList, tag) => { + const sanitizedTag = sanitizeFilename(tag); + let matches = fileList.filter(file => file === sanitizedTag); + if (matches.length === 0) { + matches = fileList.filter(file => file.startsWith(sanitizedTag)); + } + if (matches.length === 0) { + matches = fileList.filter(file => file.includes(sanitizedTag)); + } + return matches; +}; + +// 初始化目录 +createDirIfNotExists(PICTURES_DIR); /** - * - * @param {*} e - 输入的消息 - * @param {*} tag - 表情包标签 - * @returns + * 获取并发送表情包 + * @param {Object} e - 消息对象 + * @param {string} tag - 表情包标签 + * @returns {Promise} */ export async function getToimg(e, tag) { - const picturesPath = pathModule.join(path, 'pictures'); - const fileImgList = await fs.promises.readdir(picturesPath); - try { - const sanitizedTag = tag - .replace(/[\u200B-\u200D\uFEFF]/g, '') - .replace(/[\[\]]/g, '') - .trim() - .replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_.]/g, '-'); - let matchedFiles = fileImgList.filter(file => file === sanitizedTag); + // 读取文件列表 + const fileList = await fs.promises.readdir(PICTURES_DIR); + const matchedFiles = findMatchingFiles(fileList, tag); + if (matchedFiles.length === 0) { - matchedFiles = fileImgList.filter(file => file.startsWith(sanitizedTag)); - } - if (matchedFiles.length === 0) { - matchedFiles = fileImgList.filter(file => file.includes(sanitizedTag)); - } - if (matchedFiles.length === 0) { - logger.warn(`未找到匹配的表情包: ${sanitizedTag}`); + logger.warn(`未找到匹配的表情包: ${tag}`); return; } - // 随机选择一个文件 + + // 随机选择文件 const selectedFile = matchedFiles[Math.floor(Math.random() * matchedFiles.length)]; - const picPath = pathModule.join(picturesPath, selectedFile); + const picPath = pathModule.join(PICTURES_DIR, selectedFile); + try { await fs.promises.access(picPath); + await e.reply(segment.image('file:///' + picPath)); + logger.info(`发送表情包: ${picPath}`); + return false; } catch { logger.warn(`找不到指定的表情包文件: ${picPath}`); return; } - e.reply(segment.image('file:///' + picPath)); - - logger.info(`发送表情包: ${picPath}`); - return false; } catch (error) { - logger.error('Error in getToimg:', error); + logger.error('获取表情包失败:', error); + return; } } /** - * - * @param {*} e - 输入的消息 - * @param {*} image - 图片Base64 - * @returns + * 保存表情包 + * @param {Object} e - 消息对象 + * @param {string} image - Base64图片数据 + * @param {string} text - 命令文本 + * @returns {Promise} */ export async function downImg(e, image, t) { try { - let reply; - if (e.source) { - if (e.isGroup) { - reply = (await e.group.getChatHistory(e.source.seq, 1)).pop()?.message; - } else { - reply = (await e.friend.getChatHistory(e.source.time, 1)).pop()?.message; + if (!e.img && !image) { + return false; } - if (reply) { - for (let val of reply) { - if (val.type === "image") { - e.img = [val.url]; - break; + let kWordReg = /^#?(DOWNIMG:)\s*(.*)/i; + t = t.replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); + const match = kWordReg.exec(t); + if (!match) { + logger.error('DOWNIMG command format invalid:', t); + return; + } + let rawmsg = match[2] || "defaultTag"; + let kWord = rawmsg.replace(/,|,|、| |。/g, "-") + .replace(/--+/g, "-") + .replace(/^-|-$|--/g, "") + .trim() || "defaultTag"; + + if (image) { + const imageBuffer = Buffer.from(image, 'base64'); + const type = await fileTypeFromBuffer(imageBuffer); + let picType = 'png'; + if (type && type.ext) { + picType = type.ext; } - } + const currentTime = moment().format("YYMMDDHHmmss"); + const safeTag = kWord.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_]/g, '-'); + const picPath = pathModule.join(PICTURES_DIR, 'pictures', `${currentTime}-${safeTag.substring(0, 200)}.${picType}`); + logger.mark("DOWNIMG:", picPath); + + if (!fs.existsSync(pathModule.join(PICTURES_DIR, 'pictures'))) { + fs.mkdirSync(pathModule.join(PICTURES_DIR, 'pictures'), { recursive: true }); + } + fs.writeFileSync(picPath, imageBuffer); + logger.info(`图片已保存,标签为:${kWord}`); + return true; // 返回成功标志 } - } - if (!e.img && !image) { - return false; - } - let kWordReg = /^#?(DOWNIMG:)\s*(.*)/i; - t = t.replace(/[\u200B-\u200D\uFEFF]/g, '').trim(); - const match = kWordReg.exec(t); - if (!match) { - logger.error('DOWNIMG command format invalid:', t); - return; - } - let rawmsg = match[2] || "defaultTag"; - let kWord = rawmsg.replace(/,|,|、| |。/g, "-").replace(/--+/g, "-").replace(/^-|-$|--/g, "").trim() || "defaultTag"; - if (image) { - const imageBuffer = Buffer.from(image, 'base64'); - const type = await fileTypeFromBuffer(imageBuffer); - let picType = 'png'; - if (type && type.ext) { - picType = type.ext; - } - const currentTime = moment().format("YYMMDDHHmmss"); - const safeTag = kWord.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_]/g, '-'); - const picPath = pathModule.join(path, 'pictures', `${currentTime}-${safeTag.substring(0, 200)}.${picType}`); - logger.mark("DOWNIMG:", picPath); - if (!fs.existsSync(pathModule.join(path, 'pictures'))) { - fs.mkdirSync(pathModule.join(path, 'pictures'), { recursive: true }); - } - fs.writeFileSync(picPath, imageBuffer); - logger.info(`图片已保存,标签为:${kWord}`); - } } catch (error) { - logger.error('Error in downImg:', error); - logger.error("保存图片时发生错误"); + logger.error('Error in downImg:', error); + logger.error("保存图片时发生错误"); + return false; // 返回失败标志 } } +/** + * 获取表情包列表 + * @returns {Promise} + */ export async function fileImgList() { - const picturesPath = pathModule.join(path, 'pictures'); - const ImgList = await fs.promises.readdir(picturesPath); - const fileImgList = ImgList.map(filename => { - const match = filename.match(/\d{12}-(.+)$/); - return match ? match[1] : filename; - }); - return fileImgList; -} \ No newline at end of file + try { + const files = await fs.promises.readdir(PICTURES_DIR); + return files + .map(filename => { + const match = filename.match(/\d{12}-(.+)$/); + return match ? match[1] : filename; + }) + .filter(Boolean); + } catch (error) { + logger.error('读取表情包列表失败:', error); + return []; + } +} \ No newline at end of file diff --git a/utils/tools/ImageTool.js b/utils/tools/ImageTool.js new file mode 100644 index 0000000..5885214 --- /dev/null +++ b/utils/tools/ImageTool.js @@ -0,0 +1,210 @@ +import { AbstractTool } from './AbstractTool.js' +import { getToimg, downImg, fileImgList } from '../../utils/ToDoimg.js' + +export class ImageProcessTool extends AbstractTool { + name = 'imageProcess' + #availableImages = [] + #initialized = false + + parameters = { + properties: { + action: { + type: 'string', + enum: ['get', 'download', 'notImage', 'listImages'], + description: 'Action to perform: get (local image), download (save image), notImage (switch mode), or listImages' + }, + imageName: { + type: 'string', + description: 'Name or tag of the image' + }, + imageData: { + type: 'string', + description: 'Base64 image data for download action' + } + }, + required: ['action'] + } + + constructor(e, previousRole, bymGo) { + super() + this.e = e + this.previousRole = previousRole + this.bymGo = bymGo + this.initializeImageList() + } + + async func(opts) { + const { action, imageName, imageData } = opts + + try { + switch (action) { + case 'get': + const getResult = await this.handleGetImage(imageName) + return getResult + + case 'download': + const downloadResult = await this.handleDownloadImage(imageName, imageData) + return downloadResult + + case 'notImage': + const notImageResult = await this.handleNotImage() + return notImageResult + + case 'listImages': + return this.getImagesPrompt() // 直接返回列表文本,让模型知道有哪些表情包 + + default: + throw new Error(`未知操作: ${action}`) + } + } catch (error) { + throw error + } + } + + + +async initializeImageList() { + try { + this.#availableImages = await fileImgList() + this.#initialized = true + } catch (error) { + this.#availableImages = [] + } + } + + async getAvailableImages() { + if (!this.#initialized) { + await this.initializeImageList() + } + return this.#availableImages + } + + async getImagesPrompt() { + const images = await this.getAvailableImages() + if (images.length === 0) { + return '当前没有可用的表情包' + } + return `可用的表情包列表(共 ${images.length} 个):\n${images.join('\n')}\n\n使用方法:发送 GETIMG: <表情包名称> 来使用表情包` + } + + async processText(text, options = {}) { + + const commands = { + get: { + regex: /GETIMG:\s*([\s\S]+?)\s*$/i, + handler: async (match) => { + const imageName = match[1].trim() + await this.handleGetImage(imageName) + return true + } + }, + notImage: { + regex: /NOTIMG(.*)/i, + handler: async (match) => { + await this.handleNotImage(match[1]?.trim()) + return true + } + }, + download: { + regex: /DOWNIMG:\s*(.+)/i, + handler: async (match) => { + await this.handleDownloadImage(match[1].trim(), options.image) + return true + } + }, + list: { + regex: /^(?:表情包列表|查看表情包|列出表情包)$/i, + handler: async () => { + await this.e.reply(this.getImagesPrompt()) + return true + } + } + } + + for (const [type, { regex, handler }] of Object.entries(commands)) { + const match = text.match(regex) + if (match) { + try { + return await handler(match) + } catch (error) { + await this.e.reply(`处理失败: ${error.message}`) + return true + } + } + } + + return null + } + async handleGetImage(imageName) { + if (!imageName) { + throw new Error('需要指定表情包名称') + } + + try { + const result = await getToimg(this.e, imageName) + + if (result === undefined) { + await this.e.reply(`未找到匹配的表情包: ${imageName}`) + } + + return true // 表示已处理 + } catch (error) { + throw error + } + } + + async handleDownloadImage(imageName, imageData) { + if (!imageName || !imageData) { + throw new Error('需要提供图片名称和数据') + } + + try { + const text = `DOWNIMG: ${imageName}` + await downImg(this.e, imageData, text) + await this.initializeImageList() + return true + } catch (error) { + throw error + } + } + + async handleNotImage() { + try { + this.ALLRole = this.previousRole + await this.bymGo(true) + return true + } catch (error) { + throw error + } + } + + + + description = `图片处理工具:支持发送本地表情包、保存新表情包、切换处理模式。 +可用命令: +- GETIMG: <表情包名称> - 发送指定表情包 +- DOWNIMG: <名称> - 保存当前图片为表情包 +- NOTIMG - 切换到文本模式 +- 表情包列表 - 查看所有可用表情包` + // 添加 getSystemPrompt 方法 + async getSystemPrompt() { + const images = await this.getAvailableImages() + let prompt = `${this.description}\n` + + if (images.length > 0) { + prompt += `\n当前可用的表情包:\n${images.join('\n')}` + prompt += `\n使用 GETIMG: <表情包名称> 来发送表情包` + } else { + logger.warn('[ImageProcessTool] 没有可用的表情包') + prompt += '\n当前没有可用的表情包' + } + + return prompt + } +} + +export async function initializeImageTool(e, previousRole, bymGo) { + const tool = new ImageProcessTool(e, previousRole, bymGo) + await tool.initializeImageList() // 确保初始化完成 + return tool +} \ No newline at end of file