mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
2 Commits
base-rpc-s
...
ai-qun
Author | SHA1 | Date | |
---|---|---|---|
![]() |
63902d440f | ||
![]() |
cea900ca2a |
@@ -41,7 +41,6 @@
|
|||||||
"ajv": "^8.13.0",
|
"ajv": "^8.13.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"commander": "^13.0.0",
|
"commander": "^13.0.0",
|
||||||
"cors": "^2.8.5",
|
|
||||||
"esbuild": "0.25.0",
|
"esbuild": "0.25.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-import-resolver-typescript": "^4.0.0",
|
"eslint-import-resolver-typescript": "^4.0.0",
|
||||||
@@ -63,7 +62,12 @@
|
|||||||
"compressing": "^1.10.1"
|
"compressing": "^1.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ffmpeg.wasm/core-mt": "^0.13.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"compressing": "^1.10.1",
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
|
"openai": "^4.85.1",
|
||||||
|
"piscina": "^4.7.0",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,7 @@ import {
|
|||||||
import { OB11Message } from './types';
|
import { OB11Message } from './types';
|
||||||
import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
|
import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
|
||||||
import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
|
import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
|
||||||
|
import { OB11PluginAdapter } from './network/plugin';
|
||||||
|
|
||||||
//OneBot实现类
|
//OneBot实现类
|
||||||
export class NapCatOneBot11Adapter {
|
export class NapCatOneBot11Adapter {
|
||||||
@@ -113,9 +114,9 @@ export class NapCatOneBot11Adapter {
|
|||||||
//创建NetWork服务
|
//创建NetWork服务
|
||||||
|
|
||||||
// 注册Plugin 如果需要基于NapCat进行快速开发
|
// 注册Plugin 如果需要基于NapCat进行快速开发
|
||||||
// this.networkManager.registerAdapter(
|
this.networkManager.registerAdapter(
|
||||||
// new OB11PluginAdapter('myPlugin', this.core, this,this.actions)
|
new OB11PluginAdapter('myPlugin', this.core, this,this.actions)
|
||||||
// );
|
);
|
||||||
for (const key of ob11Config.network.httpServers) {
|
for (const key of ob11Config.network.httpServers) {
|
||||||
if (key.enable) {
|
if (key.enable) {
|
||||||
this.networkManager.registerAdapter(
|
this.networkManager.registerAdapter(
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { OB11EmitEventContent, OB11NetworkReloadType } from './index';
|
import { OB11EmitEventContent, OB11NetworkReloadType } from './index';
|
||||||
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11ArrayMessage, OB11Message } from '@/onebot';
|
||||||
import { NapCatCore } from '@/core';
|
import { NapCatCore } from '@/core';
|
||||||
import { PluginConfig } from '../config/config';
|
import { PluginConfig } from '../config/config';
|
||||||
import { plugin_onmessage } from '@/plugin';
|
import { plugin_onmessage } from '@/plugin';
|
||||||
@@ -22,7 +22,7 @@ export class OB11PluginAdapter extends IOB11NetworkAdapter<PluginConfig> {
|
|||||||
|
|
||||||
onEvent<T extends OB11EmitEventContent>(event: T) {
|
onEvent<T extends OB11EmitEventContent>(event: T) {
|
||||||
if (event.post_type === 'message') {
|
if (event.post_type === 'message') {
|
||||||
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11Message, this.actions, this).then().catch();
|
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11ArrayMessage, this.actions, this).then().catch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,6 +31,10 @@ export interface OB11Message {
|
|||||||
post_type?: EventType;
|
post_type?: EventType;
|
||||||
raw?: RawMessage;
|
raw?: RawMessage;
|
||||||
}
|
}
|
||||||
|
export interface OB11ArrayMessage extends OB11Message {
|
||||||
|
message_format: 'array';
|
||||||
|
message: OB11MessageData[];
|
||||||
|
}
|
||||||
|
|
||||||
// 合并转发消息接口定义
|
// 合并转发消息接口定义
|
||||||
export interface OB11ForwardMessage extends OB11Message {
|
export interface OB11ForwardMessage extends OB11Message {
|
||||||
|
@@ -1,11 +1,217 @@
|
|||||||
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot';
|
import { NapCatOneBot11Adapter, OB11ArrayMessage, OB11MessageDataType } from '@/onebot';
|
||||||
import { NapCatCore } from '@/core';
|
import { ChatType, NapCatCore, Peer, RawMessage } from '@/core';
|
||||||
import { ActionMap } from '@/onebot/action';
|
import { ActionMap } from '@/onebot/action';
|
||||||
import { OB11PluginAdapter } from '@/onebot/network/plugin';
|
import { OB11PluginAdapter } from '@/onebot/network/plugin';
|
||||||
|
import { OpenAI } from 'openai';
|
||||||
|
import { RequestUtil } from '@/common/request';
|
||||||
|
import { randomBytes } from 'node:crypto';
|
||||||
|
const client = new OpenAI({
|
||||||
|
apiKey: '',//必填多模态
|
||||||
|
baseURL: 'https://api.bili2233.work/v1'
|
||||||
|
});
|
||||||
|
|
||||||
export const plugin_onmessage = async (adapter: string, _core: NapCatCore, _obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginAdapter) => {
|
async function handleMessageArray2String(messages: RawMessage[]): Promise<string[]> {
|
||||||
if (message.raw_message === 'ping') {
|
const result = [];
|
||||||
const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config);
|
let data = '';
|
||||||
console.log(ret);
|
for (let i = 0; i < messages.length; i++) {
|
||||||
|
try {
|
||||||
|
if (messages[i]) {
|
||||||
|
data += await handleMessage2String(messages[i]!) + '\n';
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((i + 1) % 1000 === 0 || i === messages.length - 1) {
|
||||||
|
result.push(data);
|
||||||
|
data = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMessage2String(message: RawMessage): Promise<string> {
|
||||||
|
let data = '';
|
||||||
|
for (let element of message.elements) {
|
||||||
|
if (element.textElement) {
|
||||||
|
data += element.textElement.content.replaceAll('->', '').replaceAll('<-', '');
|
||||||
|
}
|
||||||
|
if (element.replyElement) {
|
||||||
|
const records = message.records.find(msgRecord => msgRecord.msgId === element.replyElement?.sourceMsgIdInRecords);
|
||||||
|
if (records) {
|
||||||
|
data += '[Reply] 回应别人的消息 ->' + await handleMessage2String(records) + '<-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.length === 0) throw new Error('消息为空');
|
||||||
|
return (message.sendMemberName || message.sendNickName) + ' 说: ->' + data + '<- ';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateChatCompletion(content_data: string): Promise<string> {
|
||||||
|
const chatCompletion = await client.chat.completions.create({
|
||||||
|
messages: [{ role: 'user', content: content_data }],
|
||||||
|
model: 'gemini-2.0-flash-thinking-exp'
|
||||||
|
});
|
||||||
|
console.log(chatCompletion);
|
||||||
|
return chatCompletion.choices[0]?.message.content || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateChatCompletionWithImg(content_data: string, url: string): Promise<string> {
|
||||||
|
const chatCompletion = await client.chat.completions.create({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user', content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: content_data
|
||||||
|
}, {
|
||||||
|
type: 'image_url',
|
||||||
|
image_url: {
|
||||||
|
url: url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
model: 'gemini-2.0-flash-thinking-exp'
|
||||||
|
});
|
||||||
|
return chatCompletion.choices[0]?.message.content || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plugin_onmessage = async (
|
||||||
|
adapter: string,
|
||||||
|
core: NapCatCore,
|
||||||
|
_obCtx: NapCatOneBot11Adapter,
|
||||||
|
message: OB11ArrayMessage,
|
||||||
|
action: ActionMap,
|
||||||
|
instance: OB11PluginAdapter
|
||||||
|
) => {
|
||||||
|
|
||||||
|
|
||||||
|
if (!message.message.find(m => m.type === 'text' && m.data.text.includes('#画像'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user_id = message.message.find(m => m.type === 'at')?.data.qq ?? message.sender.user_id;
|
||||||
|
const user_uid = await core.apis.UserApi.getUidByUinV2(user_id.toString());
|
||||||
|
if (!user_uid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: message.group_id?.toString() ?? '' };
|
||||||
|
const msg = await core.apis.MsgApi.queryFirstMsgBySender(peer, [user_uid]);
|
||||||
|
if (msg.msgList.length < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let msg_tag = '根据下面图片提取该图片的描述的描述,回应只用给出头像描述即可不要给出 好的 等等无关句子也不得提及该提示词,下面为图片内容。';
|
||||||
|
let avater_info = '头像仅供参考,Ta的头像描述: ' + await generateChatCompletionWithImg(msg_tag, `https://thirdqq.qlogo.cn/g?b=sdk&nk=${user_id}&s=100`);
|
||||||
|
console.log(`Final avater_info ret: ${avater_info}`)
|
||||||
|
const msg_string_all = await handleMessageArray2String(msg.msgList);
|
||||||
|
const user_info = await action.get('get_group_member_info')?.handle({ group_id: message.group_id?.toString()!, user_id: user_id }, adapter, instance.config);
|
||||||
|
|
||||||
|
if (msg_string_all.length > 1) {
|
||||||
|
const summaryPromises = msg_string_all.map(async (msg_string, i) => {
|
||||||
|
const content_data = `请根据下面聊天内容,分析 ${user_info?.data?.card || user_info?.data?.nickname} 的聊天风格分析其性格特点和一些有趣的信息和好笑的信息,为其建立用户画像,并加以幽默风趣的吐槽,下面是聊天内容,通过-><-字符区分结构。注意回复内容只用输出内容,不要提及此段话,注意一定不要使用markdown,请采用纯文本回复。附加提示信息:${avater_info} \n精选聊天记录: ${msg_string}`;
|
||||||
|
try {
|
||||||
|
const data = await generateChatCompletion(content_data);
|
||||||
|
if (data) {
|
||||||
|
msg_string_all[i] = '[总结(此消息过长Ai已压缩改为总结)] ->' + data + '<- ';
|
||||||
|
console.log(`Summary for part ${i + 1}: ${data}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
msg_string_all[i] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await action.get('send_group_msg')?.handle({
|
||||||
|
group_id: String(message.group_id),
|
||||||
|
message: [
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.reply,
|
||||||
|
data: {
|
||||||
|
id: message.message_id.toString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.text,
|
||||||
|
data: {
|
||||||
|
text: `消息过长,共` + msg.msgList.length + `条消息,预计时间` + Math.round(10 * msg.msgList.length / 1000) + `秒,请稍等...`
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}, adapter, instance.config);
|
||||||
|
await Promise.all(summaryPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg_string = msg_string_all.join('\n');
|
||||||
|
|
||||||
|
const content_data =
|
||||||
|
`请根据下面聊天内容,分析 ${user_info?.data?.card || user_info?.data?.nickname} 的聊天风格分析其性格特点和一些有趣的信息和好笑的信息,为其建立用户画像,并加以幽默风趣的吐槽,下面是聊天内容,通过-><-字符区分结构。注意回复内容只用输出内容,不要提及此段话,注意一定不要使用markdown,请采用纯文本回复。附加信息:${avater_info} \n精选聊天记录:${msg_string}`;
|
||||||
|
console.log(`Final content data: ${content_data}`);
|
||||||
|
const msg_ret = await generateChatCompletion(content_data);
|
||||||
|
console.log(`Final content ret: ${msg_ret}`)
|
||||||
|
let pic_tag = `请根据下面对该人物性格的分析,并虚构想象一个场景,生成如 (1 cute girl with (cat ear and cat tail:1.2) stands in the garden:1.1), (cute:1.35), (detailed beautiful eyes:1.3), (beautiful face:1.3), casual, silver hair, silver ear, (blue hair:0.8), (blue ear:0.8), long hair, coat, short skirt, hair blowing with the wind, (blue eye:1.2), flowers, (little girl:0.65), butterflys flying around 格式的文本用于描述人物,注意格式为英文加空格加逗号进行区分,请务必多的描述人物和想象和场景,至少50个描述Tag,风格是可爱动漫二次元风,注意一定要是人为主体描述,不要好的什么的回应,只用给出要求格式的文本,不需要 好的 的回应,也不要提及此段话,下面为该人物性格分析.附加信息:${avater_info}.下面是人物分析.\n${msg_ret}`;
|
||||||
|
let pic_tag_ret = await generateChatCompletion(pic_tag);
|
||||||
|
let pic = `https://thirdqq.qlogo.cn/g?b=sdk&nk=${user_id}&s=100`;
|
||||||
|
try {
|
||||||
|
let pic_generate = await RequestUtil.HttpGetJson<{ images?: Array<{ url: string }> }>
|
||||||
|
('https://api.siliconflow.cn/v1/images/generations', 'POST', {
|
||||||
|
"model": "stabilityai/stable-diffusion-xl-base-1.0",
|
||||||
|
"prompt": 'original, (masterpiece), (illustration), (extremely fine and beautiful), perfect detailed, photorealistic, (beautiful and clear background:1.25), (depth of field:0.7),' + pic_tag_ret,
|
||||||
|
"seed": randomBytes(4).readUInt32LE(0),
|
||||||
|
"negative_prompt": "(copyright name:1.5),logo,(watermark:1.5),character_watermark,lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, bad feet, ((cowboy)),(((pubic))), ((((pubic_hair))))sketch, duplicate, ugly, huge eyes, text, logo, monochrome, worst face, (bad and mutated hands:1.3), (worst quality:2.0), (low quality:2.0), (blurry:2.0), horror, geometry, bad_prompt, (bad hands), (missing fingers), multiple limbs, bad anatomy, (interlocked fingers:1.2), Ugly Fingers, (extra digit and hands and fingers and legs and arms:1.4), crown braid, ((2girl)), (deformed fingers:1.2), (long fingers:1.2),succubus wings,horn,succubus horn,succubus hairstyle, (bad-artist-anime), bad-artist, bad hand"
|
||||||
|
}, {
|
||||||
|
Authorization: 'Bearer ',//必填
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
});
|
||||||
|
if (pic_generate?.images?.[0]) {
|
||||||
|
pic = pic_generate?.images?.[0].url;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
await action.get('send_group_msg')?.handle({
|
||||||
|
group_id: String(message.group_id),
|
||||||
|
message: [{
|
||||||
|
type: OB11MessageDataType.node,
|
||||||
|
data: {
|
||||||
|
user_id: user_id,
|
||||||
|
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.text,
|
||||||
|
data: { text: msg_ret }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.node,
|
||||||
|
data: {
|
||||||
|
user_id: user_id,
|
||||||
|
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.text,
|
||||||
|
data: { text: 'Tag: ' + pic_tag_ret }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.node,
|
||||||
|
data: {
|
||||||
|
user_id: user_id,
|
||||||
|
nickname: user_info?.data?.card || user_info?.data?.nickname || '',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: OB11MessageDataType.image,
|
||||||
|
data: {
|
||||||
|
file: pic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, adapter, instance.config);
|
||||||
|
|
||||||
|
};
|
@@ -7,7 +7,8 @@ import { builtinModules } from 'module';
|
|||||||
const external = [
|
const external = [
|
||||||
'silk-wasm',
|
'silk-wasm',
|
||||||
'ws',
|
'ws',
|
||||||
'express'
|
'express',
|
||||||
|
'openai'
|
||||||
];
|
];
|
||||||
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
|
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user