mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 22:07:10 +00:00
feat(image-tool): 被说写答辩了(
This commit is contained in:
parent
541ad12023
commit
e3fe14c6e6
3 changed files with 412 additions and 197 deletions
183
apps/bym.js
183
apps/bym.js
|
|
@ -17,7 +17,7 @@ import { JinyanTool } from '../utils/tools/JinyanTool.js'
|
||||||
import { KickOutTool } from '../utils/tools/KickOutTool.js'
|
import { KickOutTool } from '../utils/tools/KickOutTool.js'
|
||||||
import { SetTitleTool } from '../utils/tools/SetTitleTool.js'
|
import { SetTitleTool } from '../utils/tools/SetTitleTool.js'
|
||||||
import { SerpTool } from '../utils/tools/SerpTool.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 = {
|
const DefaultConfig = {
|
||||||
returnQQ: [],
|
returnQQ: [],
|
||||||
|
|
@ -52,75 +52,85 @@ export class bym extends plugin {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
this.initializeConfig()
|
||||||
|
}
|
||||||
|
initializeConfig() {
|
||||||
if (typeof Config.assistantLabel === 'string') {
|
if (typeof Config.assistantLabel === 'string') {
|
||||||
Config.assistantLabel = [Config.assistantLabel]
|
Config.assistantLabel = [Config.assistantLabel]
|
||||||
}
|
}
|
||||||
Config.assistantLabel = Config.assistantLabel || DefaultConfig.assistantLabel
|
Object.entries(DefaultConfig).forEach(([key, value]) => {
|
||||||
Config.returnQQ = Config.returnQQ || DefaultConfig.returnQQ
|
Config[key] = Config[key] ?? value
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async bym(e) {
|
||||||
if (!Config.enableBYM) {
|
if (!Config.enableBYM) return false
|
||||||
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.returnQQ.includes(sender)) {
|
const sender = e.sender.user_id
|
||||||
const group_data = await ReadArr(group, Config.GroupList)
|
const atBot = e.atme
|
||||||
const user_data = await ReadArr(sender, Config.UserList)
|
const group = e.group_id
|
||||||
MaxText = user_data[3] !== group_data[3] ? user_data[3] : group_data[3]
|
let ALLRole = 'default'
|
||||||
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) {
|
if (Config.returnQQ.includes(sender)) return false
|
||||||
prop = -1
|
|
||||||
IsAtBot = true
|
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 {
|
} else {
|
||||||
if (Config.UserList.some(index => index.id === sender)) {
|
if (Config.UserList.some(user => user.id === sender)) {
|
||||||
if (user_data[2]) logger.info(`单独概率用户`)
|
if (userData.notOfGroup) {
|
||||||
}
|
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
|
if (userData.notOfGroup &&
|
||||||
prop = Math.max(-1, ServerProp)
|
!Config.UserList.some(user => group.includes(user.id)) &&
|
||||||
|
!Config.GroupList.length) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return [ChatsList, prop, NotfoGroup, MaxText]
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return false
|
context.shouldRespond = Math.floor(Math.random() * 100) - context.probability < 0
|
||||||
}
|
|
||||||
if (prop < 0) {
|
if (context.shouldRespond) {
|
||||||
await bymGo()
|
await bymGo()
|
||||||
}
|
} else return false
|
||||||
|
|
||||||
async function bymGo(NotToImg) {
|
async function bymGo(NotToImg) {
|
||||||
|
|
||||||
let opt = {
|
let opt = {
|
||||||
maxOutputTokens: 500,
|
maxOutputTokens: 500,
|
||||||
temperature: 1,
|
temperature: 1,
|
||||||
|
|
@ -128,7 +138,7 @@ export class bym extends plugin {
|
||||||
}
|
}
|
||||||
let imgs = await getImg(e)
|
let imgs = await getImg(e)
|
||||||
if (!e.msg) {
|
if (!e.msg) {
|
||||||
if (imgs && imgs.length > 0) {
|
if (imgs?.length > 0) {
|
||||||
let image = imgs[0]
|
let image = imgs[0]
|
||||||
const response = await fetch(image)
|
const response = await fetch(image)
|
||||||
const base64Image = Buffer.from(await response.arrayBuffer())
|
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]
|
let image = imgs[0]
|
||||||
const response = await fetch(image)
|
const response = await fetch(image)
|
||||||
const base64Image = Buffer.from(await response.arrayBuffer())
|
const base64Image = Buffer.from(await response.arrayBuffer())
|
||||||
opt.image = base64Image.toString('base64')
|
opt.image = base64Image.toString('base64')
|
||||||
}
|
}
|
||||||
|
logger.info('[bymGo] 开始处理回复')
|
||||||
|
|
||||||
let ForRole = ALLRole
|
let previousRole = ALLRole
|
||||||
if (opt.image && !IsAtBot && !NotToImg && !e.at && Config.AutoToDownImg) {
|
if (opt.image && !context.isAtBot && !NotToImg && !e.at && Config.AutoToDownImg) {
|
||||||
ALLRole = 'downimg'
|
ALLRole = 'downimg'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImgList = await fileImgList()
|
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const DateTime = now.toLocaleString()
|
const DateTime = now.toLocaleString()
|
||||||
let Dateday = now.getDay() === 0 ? '日' : now.getDay()
|
let Dateday = now.getDay() === 0 ? '日' : now.getDay()
|
||||||
|
|
@ -179,7 +188,7 @@ export class bym extends plugin {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (IsAtBot) {
|
if (context.isAtBot) {
|
||||||
if (e.msg) {
|
if (e.msg) {
|
||||||
const originalMsg = e.msg;
|
const originalMsg = e.msg;
|
||||||
const replacedMsg = replaceUserInput(e.msg);
|
const replacedMsg = replaceUserInput(e.msg);
|
||||||
|
|
@ -194,7 +203,7 @@ export class bym extends plugin {
|
||||||
RecallMsg = true
|
RecallMsg = true
|
||||||
candidate += Config.bymFuckPrompt
|
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);
|
const userIndex = RoleFalseUser.findIndex(user => user.UserQQ === e.user_id);
|
||||||
|
|
||||||
if (userIndex === -1) {
|
if (userIndex === -1) {
|
||||||
|
|
@ -225,7 +234,7 @@ export class bym extends plugin {
|
||||||
logger.info(log)
|
logger.info(log)
|
||||||
}
|
}
|
||||||
|
|
||||||
let chats = await getChatHistoryGroup(e, ChatsList)
|
let chats = await getChatHistoryGroup(e, context.chatsList)
|
||||||
|
|
||||||
chats = chats
|
chats = chats
|
||||||
.filter(chat => !Config.returnQQ.includes(chat.user_id))
|
.filter(chat => !Config.returnQQ.includes(chat.user_id))
|
||||||
|
|
@ -241,7 +250,7 @@ export class bym extends plugin {
|
||||||
async function SearchRole(user_role) {
|
async function SearchRole(user_role) {
|
||||||
let Role;
|
let Role;
|
||||||
|
|
||||||
switch(user_role) {
|
switch (user_role) {
|
||||||
case "downimg":
|
case "downimg":
|
||||||
Role = '现在看到的是一张图片,若你觉得是一张表情包,并不是通知,或其他的图片,注意辨别图片文字是否为通知;单纯是表情包,请发送 DOWNIMG: 命名该表情。 不需要发送过多的参数,只需要发送格式DOWNIMG: 命名该表情,注意不需要携带后缀,请以你的角度觉得如果要发这个表情包要用什么名字来命名; 若不是表情包等,及发送NOTIMG';
|
Role = '现在看到的是一张图片,若你觉得是一张表情包,并不是通知,或其他的图片,注意辨别图片文字是否为通知;单纯是表情包,请发送 DOWNIMG: 命名该表情。 不需要发送过多的参数,只需要发送格式DOWNIMG: 命名该表情,注意不需要携带后缀,请以你的角度觉得如果要发这个表情包要用什么名字来命名; 若不是表情包等,及发送NOTIMG';
|
||||||
break;
|
break;
|
||||||
|
|
@ -251,14 +260,7 @@ export class bym extends plugin {
|
||||||
`以下是聊天记录:
|
`以下是聊天记录:
|
||||||
${Group_Chat}
|
${Group_Chat}
|
||||||
\n你的回复应该尽可能简练,像人类一样随意,不要附加任何奇怪的东西,如聊天记录的格式(比如${Config.assistantLabel}:),禁止重复聊天记录。
|
\n你的回复应该尽可能简练,像人类一样随意,不要附加任何奇怪的东西,如聊天记录的格式(比如${Config.assistantLabel}:),禁止重复聊天记录。
|
||||||
注意当前时间与日期为${DateTime},星期${Dateday},24小时制,时区已正确,不要被日志的时间与其他时间搞混了,如果有人咨询时间就使用${DateTime},星期${Dateday}这个时间,群友与你几乎在一个时区,若有人说或做的事情与时间段不合理,反驳他,注意除了他声明了自己的时区
|
注意当前时间与日期为${DateTime},星期${Dateday},24小时制,时区已正确,不要被日志的时间与其他时间搞混了,如果有人咨询时间就使用${DateTime},星期${Dateday}这个时间,群友与你几乎在一个时区,若有人说或做的事情与时间段不合理,反驳他,注意除了他声明了自己的时区`
|
||||||
以下是可用的表情包列表
|
|
||||||
${ImgList}` +
|
|
||||||
(ImgList.length > 0 && Config.AutoToDownImg ? `
|
|
||||||
如果要发送表情包,请根据该格式 GETIMG: 完整表情包名称,实例 GETIMG: 挠头.gif 即可发送,注意发送完整名称
|
|
||||||
可根据聊天,选择表情包发送。禁止发送多余的格式与说明。发送格式为 注意前面不需要换行 GETIMG: 挠头.gif 不需要换行
|
|
||||||
不要被日志和其他聊天消息的格式迷惑,请保持标准格式,禁止发送[表情包:xxx]、[图片]!!!,禁止发送[表情包:xxx]、[图片]!!!
|
|
||||||
` : '');
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.error(`未知的 Role 类型:${user_role},使用默认 Role`);
|
logger.error(`未知的 Role 类型:${user_role},使用默认 Role`);
|
||||||
|
|
@ -306,6 +308,14 @@ export class bym extends plugin {
|
||||||
if (e.group.is_owner) {
|
if (e.group.is_owner) {
|
||||||
tools.push(new SetTitleTool())
|
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)
|
client.addTools(tools)
|
||||||
let rsp = await client.sendMessage(e.msg, opt)
|
let rsp = await client.sendMessage(e.msg, opt)
|
||||||
let text = rsp.text
|
let text = rsp.text
|
||||||
|
|
@ -318,32 +328,11 @@ export class bym extends plugin {
|
||||||
if (text[text.indexOf(t) + t.length] === '?') {
|
if (text[text.indexOf(t) + t.length] === '?') {
|
||||||
t += '?'
|
t += '?'
|
||||||
}
|
}
|
||||||
const getImgRegex = /GETIMG:\s*([\s\S]+?)\s*$/i;
|
const processed = await imageTool.processText(t, {
|
||||||
const match1 = t.match(getImgRegex);
|
image: opt.image
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t) {
|
if (!processed) {
|
||||||
let finalMsg = await convertFaces(t, true, e)
|
let finalMsg = await convertFaces(t, true, e)
|
||||||
logger.info(JSON.stringify(finalMsg))
|
logger.info(JSON.stringify(finalMsg))
|
||||||
if (Math.floor(Math.random() * 100) < 10) {
|
if (Math.floor(Math.random() * 100) < 10) {
|
||||||
|
|
|
||||||
140
utils/ToDoimg.js
140
utils/ToDoimg.js
|
|
@ -2,86 +2,86 @@ import fs from "fs";
|
||||||
import pathModule from 'path';
|
import pathModule from 'path';
|
||||||
import { fileTypeFromBuffer } from 'file-type';
|
import { fileTypeFromBuffer } from 'file-type';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
const _path = process.cwd();
|
|
||||||
const path = _path + "/temp/tp-bq";
|
|
||||||
|
|
||||||
// 没文件夹就创建一个
|
// 配置
|
||||||
if (!fs.existsSync(path)) {
|
const ROOT_PATH = process.cwd();
|
||||||
fs.mkdirSync(path, { recursive: true })
|
const PICTURES_DIR = pathModule.join(ROOT_PATH, "temp/tp-bq", "pictures");
|
||||||
}
|
|
||||||
if (!fs.existsSync(pathModule.join(path, 'pictures'))) {
|
|
||||||
fs.mkdirSync(pathModule.join(path, 'pictures'), { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// 工具函数
|
||||||
*
|
const createDirIfNotExists = (dir) => {
|
||||||
* @param {*} e - 输入的消息
|
if (!fs.existsSync(dir)) {
|
||||||
* @param {*} tag - 表情包标签
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
* @returns
|
}
|
||||||
*/
|
};
|
||||||
export async function getToimg(e, tag) {
|
|
||||||
const picturesPath = pathModule.join(path, 'pictures');
|
|
||||||
const fileImgList = await fs.promises.readdir(picturesPath);
|
|
||||||
|
|
||||||
try {
|
const sanitizeFilename = (name) => {
|
||||||
const sanitizedTag = tag
|
return name
|
||||||
.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
.replace(/[\u200B-\u200D\uFEFF]/g, '')
|
||||||
.replace(/[\[\]]/g, '')
|
.replace(/[\[\]]/g, '')
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_.]/g, '-');
|
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_.]/g, '-');
|
||||||
let matchedFiles = fileImgList.filter(file => file === sanitizedTag);
|
};
|
||||||
if (matchedFiles.length === 0) {
|
|
||||||
matchedFiles = fileImgList.filter(file => file.startsWith(sanitizedTag));
|
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 (matchedFiles.length === 0) {
|
if (matches.length === 0) {
|
||||||
matchedFiles = fileImgList.filter(file => file.includes(sanitizedTag));
|
matches = fileList.filter(file => file.includes(sanitizedTag));
|
||||||
}
|
}
|
||||||
|
return matches;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化目录
|
||||||
|
createDirIfNotExists(PICTURES_DIR);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取并发送表情包
|
||||||
|
* @param {Object} e - 消息对象
|
||||||
|
* @param {string} tag - 表情包标签
|
||||||
|
* @returns {Promise<boolean|undefined>}
|
||||||
|
*/
|
||||||
|
export async function getToimg(e, tag) {
|
||||||
|
try {
|
||||||
|
// 读取文件列表
|
||||||
|
const fileList = await fs.promises.readdir(PICTURES_DIR);
|
||||||
|
const matchedFiles = findMatchingFiles(fileList, tag);
|
||||||
|
|
||||||
if (matchedFiles.length === 0) {
|
if (matchedFiles.length === 0) {
|
||||||
logger.warn(`未找到匹配的表情包: ${sanitizedTag}`);
|
logger.warn(`未找到匹配的表情包: ${tag}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 随机选择一个文件
|
|
||||||
|
// 随机选择文件
|
||||||
const selectedFile = matchedFiles[Math.floor(Math.random() * matchedFiles.length)];
|
const selectedFile = matchedFiles[Math.floor(Math.random() * matchedFiles.length)];
|
||||||
const picPath = pathModule.join(picturesPath, selectedFile);
|
const picPath = pathModule.join(PICTURES_DIR, selectedFile);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(picPath);
|
await fs.promises.access(picPath);
|
||||||
|
await e.reply(segment.image('file:///' + picPath));
|
||||||
|
logger.info(`发送表情包: ${picPath}`);
|
||||||
|
return false;
|
||||||
} catch {
|
} catch {
|
||||||
logger.warn(`找不到指定的表情包文件: ${picPath}`);
|
logger.warn(`找不到指定的表情包文件: ${picPath}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.reply(segment.image('file:///' + picPath));
|
|
||||||
|
|
||||||
logger.info(`发送表情包: ${picPath}`);
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in getToimg:', error);
|
logger.error('获取表情包失败:', error);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* 保存表情包
|
||||||
* @param {*} e - 输入的消息
|
* @param {Object} e - 消息对象
|
||||||
* @param {*} image - 图片Base64
|
* @param {string} image - Base64图片数据
|
||||||
* @returns
|
* @param {string} text - 命令文本
|
||||||
|
* @returns {Promise<boolean|undefined>}
|
||||||
*/
|
*/
|
||||||
export async function downImg(e, image, t) {
|
export async function downImg(e, image, t) {
|
||||||
try {
|
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 (reply) {
|
|
||||||
for (let val of reply) {
|
|
||||||
if (val.type === "image") {
|
|
||||||
e.img = [val.url];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!e.img && !image) {
|
if (!e.img && !image) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,11 @@ export async function downImg(e, image, t) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let rawmsg = match[2] || "defaultTag";
|
let rawmsg = match[2] || "defaultTag";
|
||||||
let kWord = rawmsg.replace(/,|,|、| |。/g, "-").replace(/--+/g, "-").replace(/^-|-$|--/g, "").trim() || "defaultTag";
|
let kWord = rawmsg.replace(/,|,|、| |。/g, "-")
|
||||||
|
.replace(/--+/g, "-")
|
||||||
|
.replace(/^-|-$|--/g, "")
|
||||||
|
.trim() || "defaultTag";
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
const imageBuffer = Buffer.from(image, 'base64');
|
const imageBuffer = Buffer.from(image, 'base64');
|
||||||
const type = await fileTypeFromBuffer(imageBuffer);
|
const type = await fileTypeFromBuffer(imageBuffer);
|
||||||
|
|
@ -103,26 +107,38 @@ export async function downImg(e, image, t) {
|
||||||
}
|
}
|
||||||
const currentTime = moment().format("YYMMDDHHmmss");
|
const currentTime = moment().format("YYMMDDHHmmss");
|
||||||
const safeTag = kWord.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_]/g, '-');
|
const safeTag = kWord.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_]/g, '-');
|
||||||
const picPath = pathModule.join(path, 'pictures', `${currentTime}-${safeTag.substring(0, 200)}.${picType}`);
|
const picPath = pathModule.join(PICTURES_DIR, 'pictures', `${currentTime}-${safeTag.substring(0, 200)}.${picType}`);
|
||||||
logger.mark("DOWNIMG:", picPath);
|
logger.mark("DOWNIMG:", picPath);
|
||||||
if (!fs.existsSync(pathModule.join(path, 'pictures'))) {
|
|
||||||
fs.mkdirSync(pathModule.join(path, 'pictures'), { recursive: true });
|
if (!fs.existsSync(pathModule.join(PICTURES_DIR, 'pictures'))) {
|
||||||
|
fs.mkdirSync(pathModule.join(PICTURES_DIR, 'pictures'), { recursive: true });
|
||||||
}
|
}
|
||||||
fs.writeFileSync(picPath, imageBuffer);
|
fs.writeFileSync(picPath, imageBuffer);
|
||||||
logger.info(`图片已保存,标签为:${kWord}`);
|
logger.info(`图片已保存,标签为:${kWord}`);
|
||||||
|
return true; // 返回成功标志
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in downImg:', error);
|
logger.error('Error in downImg:', error);
|
||||||
logger.error("保存图片时发生错误");
|
logger.error("保存图片时发生错误");
|
||||||
|
return false; // 返回失败标志
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表情包列表
|
||||||
|
* @returns {Promise<string[]>}
|
||||||
|
*/
|
||||||
export async function fileImgList() {
|
export async function fileImgList() {
|
||||||
const picturesPath = pathModule.join(path, 'pictures');
|
try {
|
||||||
const ImgList = await fs.promises.readdir(picturesPath);
|
const files = await fs.promises.readdir(PICTURES_DIR);
|
||||||
const fileImgList = ImgList.map(filename => {
|
return files
|
||||||
|
.map(filename => {
|
||||||
const match = filename.match(/\d{12}-(.+)$/);
|
const match = filename.match(/\d{12}-(.+)$/);
|
||||||
return match ? match[1] : filename;
|
return match ? match[1] : filename;
|
||||||
});
|
})
|
||||||
return fileImgList;
|
.filter(Boolean);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('读取表情包列表失败:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
210
utils/tools/ImageTool.js
Normal file
210
utils/tools/ImageTool.js
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue