mirror of
https://github.com/Jerryplusy/crystelf-plugin.git
synced 2025-12-05 15:41:56 +00:00
Compare commits
7 Commits
2a43f40b5f
...
be1d756e1e
| Author | SHA1 | Date | |
|---|---|---|---|
| be1d756e1e | |||
| 7970f14f23 | |||
| 0dd31c9458 | |||
| 134f068bec | |||
| f59dffbf37 | |||
| 7619b01000 | |||
| f0bd88eed3 |
72
apps/ai.js
72
apps/ai.js
@ -108,7 +108,7 @@ async function index(e) {
|
||||
}
|
||||
|
||||
async function extractUserMessage(msg, nickname, e) {
|
||||
if (e.message) {
|
||||
if (e.message && msg && msg.trim()!=='' && msg !== '\n') {
|
||||
let text = [];
|
||||
let at = [];
|
||||
e.message.forEach((message) => {
|
||||
@ -124,7 +124,7 @@ async function extractUserMessage(msg, nickname, e) {
|
||||
text.forEach((message) => {
|
||||
if(message === '') {
|
||||
} else {
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id}]说:${message}`
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id},seq:${e.message_id}]说:${message}\n`
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -136,13 +136,32 @@ async function extractUserMessage(msg, nickname, e) {
|
||||
if (at == e.bot.uin) {
|
||||
//returnMessage += `[${e.sender?.nickname},id:${e.user_id}]@(at)了你,你的id是${at}\n`;
|
||||
} else {
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id}]@(at)了一个人,id是${at}\n`;
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id},seq:${e.message_id}]@(at)了一个人,id是${at}\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
const imgUrls = await YunzaiUtils.getImages(e, 1, true);
|
||||
if (imgUrls) {
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id}]发送了一张图片(你可能暂时无法查看)\n`;
|
||||
returnMessage += `[${e.sender?.nickname},id:${e.user_id},seq:${e.message_id}]发送了一张图片(你可能暂时无法查看)\n`;
|
||||
}
|
||||
if(e.source || e.reply_id){
|
||||
let reply;
|
||||
if(e.getReply) reply = await e.getReply();
|
||||
else {
|
||||
const history = await e.group.getChatHistory(e.source.seq,1);
|
||||
reply = history?.pop();
|
||||
}
|
||||
if(reply){
|
||||
const msgArr = Array.isArray(reply) ? reply : reply.message || [];
|
||||
msgArr.forEach((msg) => {
|
||||
if(msg.type === 'text'){
|
||||
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一段文本:${msg.text}\n`
|
||||
}
|
||||
if(msg.type === 'image'){
|
||||
returnMessage += `[${e.sender?.nickname}]引用了[被引用消息:${reply.sender?.nickname},id:${reply.user_id},seq:${reply.message_id}]发的一张图片(你可能暂时无法查看)\n`;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return returnMessage;
|
||||
}
|
||||
@ -180,7 +199,7 @@ async function processMessage(userMessage, e, aiConfig) {
|
||||
* @returns {Promise<[{type: string, data: string}]>}
|
||||
*/
|
||||
async function handleKeywordMode(userMessage, e) {
|
||||
const matchResult = await KeywordMatcher.matchKeywords(userMessage, 'ai');
|
||||
const matchResult = await KeywordMatcher.matchKeywords(e.msg, 'ai');
|
||||
|
||||
if (matchResult && matchResult.matched) {
|
||||
return [
|
||||
@ -188,8 +207,8 @@ async function handleKeywordMode(userMessage, e) {
|
||||
type: 'message',
|
||||
data: matchResult.text,
|
||||
at: false,
|
||||
quote: false,
|
||||
recall: 0,
|
||||
quote: -1,
|
||||
recall: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -214,7 +233,7 @@ async function handleMixMode(userMessage, e, aiConfig) {
|
||||
logger.info('[crystelf-ai] 消息过长,使用ai回复');
|
||||
return await callAiForResponse(userMessage, e, aiConfig);
|
||||
} else {
|
||||
const matchResult = await KeywordMatcher.matchKeywords(userMessage, 'ai');
|
||||
const matchResult = await KeywordMatcher.matchKeywords(e.msg, 'ai');
|
||||
if (matchResult && matchResult.matched) {
|
||||
const session = SessionManager.createOrGetSession(e.group_id, e.user_id, e);
|
||||
const historyLen = aiConfig.chatHistory;
|
||||
@ -224,16 +243,16 @@ async function handleMixMode(userMessage, e, aiConfig) {
|
||||
type: 'message',
|
||||
data: matchResult.text,
|
||||
at: false,
|
||||
quote: false,
|
||||
recall: 0,
|
||||
quote: -1,
|
||||
recall: false,
|
||||
},
|
||||
];
|
||||
let resMessage = {
|
||||
type: 'message',
|
||||
data: matchResult.text,
|
||||
at: false,
|
||||
quote: false,
|
||||
recall: 0,
|
||||
quote: -1,
|
||||
recall: false,
|
||||
};
|
||||
const newChatHistory = [
|
||||
...chatHistory,
|
||||
@ -316,19 +335,24 @@ async function callAiForResponse(userMessage, e, aiConfig) {
|
||||
*/
|
||||
async function sendResponse(e, messages) {
|
||||
try {
|
||||
const adapter = await YunzaiUtils.getAdapter(e);
|
||||
for (const message of messages) {
|
||||
switch (message.type) {
|
||||
case 'message':
|
||||
if (message.recall > 0) {
|
||||
await e.reply(message.data, message.quote, {
|
||||
recallMsg: message.recall,
|
||||
if(message.quote === -1) {
|
||||
if(message.recall) {
|
||||
await e.reply(message.data, false, {
|
||||
recallMsg: 60,
|
||||
at: message.at,
|
||||
});
|
||||
} else {
|
||||
await e.reply(message.data, message.quote, {
|
||||
await e.reply(message.data, false, {
|
||||
at: message.at,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await Message.sendGroupMessage(e,e.group_id,message.data,message.at,message.quote,adapter);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'code':
|
||||
@ -351,10 +375,6 @@ async function sendResponse(e, messages) {
|
||||
await handlePokeMessage(e, message);
|
||||
break;
|
||||
|
||||
case 'recall':
|
||||
await handleRecallMessage(e, message);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.warn(`[crystelf-ai] 不支持的消息类型: ${message.type}`);
|
||||
}
|
||||
@ -363,7 +383,7 @@ async function sendResponse(e, messages) {
|
||||
} catch (error) {
|
||||
const adapter = await YunzaiUtils.getAdapter(e);
|
||||
await Message.emojiLike(e, e.message_id, 10060, e.group_id, adapter);
|
||||
logger.error(`[crystelf-ai] 发送回复失败: ${error.message}`);
|
||||
logger.error(`[crystelf-ai] 发送回复失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,16 +452,6 @@ async function handlePokeMessage(e, message) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRecallMessage(e, message) {
|
||||
try {
|
||||
if (message.seq) {
|
||||
await Message.deleteMsg(e, message.seq);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[crystelf-ai] 撤回消息失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
//定期清理过期sessions
|
||||
setInterval(
|
||||
async () => {
|
||||
|
||||
@ -26,14 +26,14 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
|
||||
"type": "message",
|
||||
"data": "你的回复内容",
|
||||
"at": false,
|
||||
"quote": false,
|
||||
"recall": 0
|
||||
"quote": -1,
|
||||
"recall": false
|
||||
}
|
||||
]
|
||||
|
||||
支持的消息类型(type):
|
||||
常规消息:
|
||||
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为true(另外,不要在message里面加@qq号),quote:是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候为true
|
||||
- message(必须,其他均为可选): 普通文本消息,请将长句子分成多个message块返回(如果有多句话),data:回复内容,at:是否在发送本条消息的时候提醒用户,一般只在需要让用户注意的时候为true(另外,不要在message里面加@qq号),quote:是否引用用户的问题,一般只需要在回答用户问题或第一条回复或需要用到用户问题的时候才引用该消息,注意这里如果不需要引用,则填写-1,需要引用时填写消息的seq,seq会在用户发的消息那里给出,也可以引用用户回复的消息或聊天记录的相关消息,但是一个message只能含有一个引用seq,recall:值为true的时候会在发送消息后过一会撤回自己的这条消息
|
||||
- at: @某人(需要提供id,被at人qq号(number)),一般用于提醒用户,不常用
|
||||
- meme: 表情包(data值为情绪名称:angry、bye、confused、default、good、goodmorning、goodnight、happy、sad、shy、sorry、surprise),请根据聊天语境灵活选择需不需要表情包,如果感觉语境尴尬或需要表情包,那么发送一个default值的表情包,其他情绪的表情包按照当前你的情绪按需选择,注意:并不是每个聊天都需要有表情包,并且一次聊天最多回复一个表情包
|
||||
- poke: 戳一戳某人(需要提供id,被戳人qq号(number)),一般用户与用户互动,当想逗用户的时候可以使用,不要使用太过频繁(频率小于百分之20)
|
||||
@ -41,11 +41,7 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
|
||||
- code: 代码块(会自动渲染为高亮图片,必须有language参数指定编程语言)
|
||||
- markdown: 需要渲染的markdown内容(会自动渲染为图片)
|
||||
- memory: 存储记忆(需要提供data(记忆内容,需要简明扼要)、key(字符串数组,可以有多个关键词),timeout(遗忘世间,单位为天,建议一个月)),重要:如果你认为本次用户说的话有一些值得记住的东西(例如用户希望你叫他什么,用户说她生日是多少多少等),那么使用本功能记住用户说的话
|
||||
- recall: 撤回消息(需要提供seq),不常用,如果用户要求你撤回别人的消息可以使用
|
||||
- emoji-like: 表情反应(需要提供id,表情id),给用户的提问回应emoji,跟meme不同
|
||||
- ai-record: AI语音(需要提供data),发送语音,不常用,用户要求你发语音的时候可以发,发的data需要简短,可以多条消息,但是不能太长
|
||||
- like: 点赞某人(需要提供id和num),如果用户需要
|
||||
- file: 发送文件(需要提供data和filename),如果你需要发一个很长的文本,请使用file发送
|
||||
|
||||
重要规则:
|
||||
1. 必须返回JSON数组格式,一定要是数组!无论有多少条消息,一条消息也要是数组!
|
||||
@ -71,7 +67,7 @@ export const RESPONSE_FORMAT = `请严格按照以下格式按顺序返回你的
|
||||
"data": "你好呀~",
|
||||
"at": false,
|
||||
"quote": false,
|
||||
"recall": 0
|
||||
"recall": false
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@ -96,6 +96,32 @@ class AiCaller {
|
||||
return fullPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算时间差
|
||||
* @param pastTime 过去时间戳
|
||||
* @returns {string} 时间差字符串
|
||||
*/
|
||||
calculateTimeDifference(pastTime) {
|
||||
const now = Date.now();
|
||||
const diff = now - pastTime;
|
||||
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||
|
||||
let result = '';
|
||||
if (days > 0) {
|
||||
result += `${days}天`;
|
||||
}
|
||||
if (hours > 0) {
|
||||
result += `${hours}小时`;
|
||||
}
|
||||
if (minutes > 0) {
|
||||
result += `${minutes}分钟`;
|
||||
}
|
||||
return result || '刚刚';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统提示词
|
||||
* @param {object} e 上下文事件对象
|
||||
@ -140,7 +166,8 @@ class AiCaller {
|
||||
if (memories && memories.length > 0) {
|
||||
contextIntro += '你可能会用到的记忆,请按情况使用,如果不合语境请忽略,请结合记忆时间和当前时间智能判断:\n';
|
||||
memories.forEach((memory, index) => {
|
||||
contextIntro += `${index + 1}. 关键词:${memory.keywords},内容:${memory.data},记忆创建时间:${memory.createdAt}\n`;
|
||||
const timeDiff = this.calculateTimeDifference(memory.createdAt);
|
||||
contextIntro += `${index + 1}. 关键词:${memory.keywords},内容:${memory.data},记忆创建时间:${memory.createdAt},距离现在:${timeDiff}\\n`;
|
||||
});
|
||||
contextIntro += '\n';
|
||||
}
|
||||
|
||||
@ -85,8 +85,6 @@ class ResponseHandler {
|
||||
case 'memory':
|
||||
await this.handleMemoryMessage(message, groupId,userId);
|
||||
return null;
|
||||
case 'recall':
|
||||
return this.handleRecallMessage(message);
|
||||
default:
|
||||
return this.handleNormalMessage(message);
|
||||
}
|
||||
@ -106,9 +104,7 @@ class ResponseHandler {
|
||||
return false;
|
||||
}
|
||||
const validTypes = [
|
||||
'message', 'code', 'markdown', 'meme', 'at', 'poke',
|
||||
'recall', 'emoji-like', 'ai-record', 'function', 'like',
|
||||
'file', 'memory'
|
||||
'message', 'code', 'markdown', 'meme', 'at', 'poke','memory'
|
||||
];
|
||||
if (!validTypes.includes(message.type)) {
|
||||
logger.info(`[crystelf-ai] ai返回未知的type类型:${message.type}`)
|
||||
@ -140,13 +136,6 @@ class ResponseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
handleRecallMessage(message) {
|
||||
return {
|
||||
type: 'recall',
|
||||
seq: message.seq
|
||||
};
|
||||
}
|
||||
|
||||
//普通消息
|
||||
handleNormalMessage(message) {
|
||||
// 设置默认值
|
||||
@ -154,31 +143,24 @@ class ResponseHandler {
|
||||
type: message.type,
|
||||
data: message.data,
|
||||
at: message.at || false,
|
||||
quote: message.quote || false,
|
||||
recall: message.recall || 0
|
||||
quote: message.quote || -1,
|
||||
recall: message.recall || false
|
||||
};
|
||||
if (message.id) processedMessage.id = message.id;
|
||||
if (message.seq) processedMessage.seq = message.seq;
|
||||
if (message.num) processedMessage.num = message.num;
|
||||
if (message.filename) processedMessage.filename = message.filename;
|
||||
if (message.language) processedMessage.language = message.language;
|
||||
|
||||
return processedMessage;
|
||||
}
|
||||
|
||||
//对上下文消息进行处理
|
||||
handleChatHistory(message) {
|
||||
let messageToHistory = [];
|
||||
}
|
||||
|
||||
createErrorResponse(error) {
|
||||
const nickName = configControl.get('profile')?.nickName;
|
||||
return [{
|
||||
type: 'message',
|
||||
data: `${nickName}的服务器去火星开小差了..`,
|
||||
at: false,
|
||||
quote: true,
|
||||
recall: 120
|
||||
quote: -1,
|
||||
recall: true
|
||||
}];
|
||||
}
|
||||
|
||||
@ -188,8 +170,8 @@ class ResponseHandler {
|
||||
type: 'message',
|
||||
data: `${nickName}的服务器去火星开小差了..`,
|
||||
at: false,
|
||||
quote: true,
|
||||
recall: 120
|
||||
quote: -1,
|
||||
recall: true
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,79 @@ const Message = {
|
||||
count: count,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送群聊消息
|
||||
* @param e
|
||||
* @param group_id 群号
|
||||
* @param message 消息内容字符串
|
||||
* @param at -1 不@ 其他 被@的qq号
|
||||
* @param quote_seq -1 不引用 其他 引用的消息seq
|
||||
* @param adapter nc/lgr
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async sendGroupMessage(e,group_id,message,at = -1,quote_seq = -1,adapter = 'nc'){
|
||||
if(adapter === 'nc') {
|
||||
const msgChain = [];
|
||||
if (typeof quote_seq !== 'boolean' && quote_seq !== -1 && quote_seq !== undefined) {
|
||||
msgChain.push({
|
||||
type: "reply",
|
||||
data: {
|
||||
"id": quote_seq
|
||||
}
|
||||
});
|
||||
}
|
||||
if (at && at !== -1) {
|
||||
msgChain.push({
|
||||
type: "at",
|
||||
data: {
|
||||
"qq": at
|
||||
}
|
||||
});
|
||||
}
|
||||
msgChain.push({
|
||||
type: "text",
|
||||
data: {
|
||||
"text": message
|
||||
}
|
||||
});
|
||||
return await e.bot.sendApi('send_group_msg',{
|
||||
group_id : group_id,
|
||||
message: msgChain
|
||||
})
|
||||
} else if (adapter === 'lgr') {
|
||||
const messageData = {};
|
||||
if(quote_seq && quote_seq !== -1) {
|
||||
messageData.reply = {
|
||||
type: "reply",
|
||||
data: {
|
||||
"id": quote_seq
|
||||
}
|
||||
};
|
||||
}
|
||||
if(at && at !== -1) {
|
||||
messageData.at = {
|
||||
type: "at",
|
||||
data: {
|
||||
"qq": at
|
||||
}
|
||||
};
|
||||
}
|
||||
messageData.text = {
|
||||
type: "text",
|
||||
data: {
|
||||
"text": message
|
||||
}
|
||||
};
|
||||
return await e.bot.sendApi('send_group_msg',{
|
||||
group_id: group_id,
|
||||
message :{
|
||||
type: "dict",
|
||||
data : messageData
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Message;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user