From 7796c2e534b4b7c5cc913c6c1042889b665ff513 Mon Sep 17 00:00:00 2001 From: baiyu-yu <135424680+baiyu-yu@users.noreply.github.com> Date: Thu, 8 Jan 2026 19:14:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8B=89=E9=BB=91=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=B7=A5=E5=85=B7=E5=92=8C=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/block.ts | 74 ++++++++++++++++++++++ src/index.ts | 139 ++++++++++++++++++++++++++++++++++++++++- src/privilege.ts | 8 +++ src/tool/tool.ts | 2 + src/tool/tool_block.ts | 89 ++++++++++++++++++++++++++ 5 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/block.ts create mode 100644 src/tool/tool_block.ts diff --git a/src/block.ts b/src/block.ts new file mode 100644 index 0000000..26d7678 --- /dev/null +++ b/src/block.ts @@ -0,0 +1,74 @@ +import { ConfigManager } from "./config/configManager"; +import { logger } from "./logger"; +import { revive } from "./utils/utils"; +import { fmtDate } from "./utils/utils_string"; + +export class BlockInfo { + static validKeys: (keyof BlockInfo)[] = ['reason', 'time']; + reason: string; + time: number; + + constructor() { + this.reason = ''; + this.time = 0; + } +} + +export class BlockManager { + static blockList: { [id: string]: BlockInfo } = {}; + + static initBlockList() { + try { + const data = JSON.parse(ConfigManager.ext.storageGet('blacklist') || '{}'); + if (typeof data !== 'object') throw new Error('blacklist不是对象'); + + for (const key in data) { + if (data.hasOwnProperty(key)) { + this.blockList[key] = revive(BlockInfo, data[key]); + } + } + } catch (error) { + logger.error(`从数据库中获取blacklist失败:`, error); + } + } + + static saveBlockList() { + ConfigManager.ext.storageSet('blacklist', JSON.stringify(this.blockList)); + } + + static addBlock(id: string, reason: string) { + const info = new BlockInfo(); + info.reason = reason; + info.time = Date.now(); + + this.blockList[id] = info; + this.saveBlockList(); + } + + static removeBlock(id: string) { + if (this.blockList.hasOwnProperty(id)) { + delete this.blockList[id]; + this.saveBlockList(); + return true; + } + return false; + } + + static checkBlock(id: string): string | null { + if (this.blockList.hasOwnProperty(id)) { + return this.blockList[id].reason; + } + return null; + } + + static getListText(): string { + const ids = Object.keys(this.blockList); + if (ids.length === 0) { + return '黑名单为空'; + } + return ids.map(id => { + const info = this.blockList[id]; + return `${id}: ${info.reason} (拉黑时间: ${fmtDate(Math.floor(info.time / 1000))})`; + }).join('\n'); + } +} diff --git a/src/index.ts b/src/index.ts index dbf04bb..e387b71 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { aliasToCmd } from "./utils/utils"; import { knowledgeMM } from "./AI/memory"; import { HELPMAP, CQTYPESALLOW } from "./config/config"; import { ImageManager } from "./AI/image"; +import { BlockManager } from "./block"; function main() { ConfigManager.registerConfig(); @@ -22,6 +23,7 @@ function main() { TimerManager.init(); PrivilegeManager.reviveCmdPriv(); knowledgeMM.init(); + BlockManager.initBlockList(); const ext = ConfigManager.ext; @@ -42,6 +44,7 @@ function main() { 【.ai memo】AI的记忆相关 【.ai tool】AI的工具相关 【.ai ign】AI的忽略名单相关 +【.ai block】AI的黑名单相关 【.ai tk】AI的token相关 【.ai shut】终止AI当前流式输出`; cmdAI.allowDelegate = true; @@ -1037,6 +1040,77 @@ ${images.map(img => img.CQCode).join('\n')}`)); } } } + case 'block': { + const mctx = seal.getCtxProxyFirst(ctx, cmdArgs); + const muid = cmdArgs.amIBeMentionedFirst ? epId : mctx.player.userId; + + const val2 = cmdArgs.getArgN(2); + switch (aliasToCmd(val2)) { + case 'add': { + let targetId = ''; + let reason = ''; + const arg3 = cmdArgs.getArgN(3); + const arg4 = cmdArgs.getArgN(4); + + if (cmdArgs.at.length > 0) { + targetId = muid; + reason = arg4; + } else if (arg3 && (arg3.startsWith('QQ:') || arg3.startsWith('QQ-Group:'))) { + targetId = arg3; + reason = arg4; + } else { + seal.replyToSender(ctx, msg, '参数缺失,【.ai block add <统一ID/@xxx> <原因>】添加黑名单'); + return ret; + } + + if (!reason || reason.trim() === '') { + reason = `未填写原因`; + } + + if (BlockManager.checkBlock(targetId)) { + seal.replyToSender(ctx, msg, '已经在黑名单中'); + return ret; + } + + BlockManager.addBlock(targetId, reason); + seal.replyToSender(ctx, msg, `已将<${targetId}>加入黑名单,原因: ${reason}`); + return ret; + } + case 'remove': { + let targetId = ''; + const arg3 = cmdArgs.getArgN(3); + + if (cmdArgs.at.length > 0) { + targetId = muid; + } else if (arg3 && (arg3.startsWith('QQ:') || arg3.startsWith('QQ-Group:'))) { + targetId = arg3; + } else { + seal.replyToSender(ctx, msg, '参数缺失,【.ai block rm <统一ID/@xxx>】移除黑名单'); + return ret; + } + + if (BlockManager.removeBlock(targetId)) { + seal.replyToSender(ctx, msg, `已将<${targetId}>移出黑名单`); + } else { + seal.replyToSender(ctx, msg, `不在黑名单中`); + } + return ret; + } + case 'list': { + seal.replyToSender(ctx, msg, BlockManager.getListText()); + return ret; + } + default: { + seal.replyToSender(ctx, msg, `帮助: +【.ai block add <统一ID/@xxx> <原因>】添加黑名单 +【.ai block rm <统一ID/@xxx>】移除黑名单 +【.ai block list】查看黑名单列表 + +被拉黑的对象无法触发AI对话`); + return ret; + } + } + } case 'token': { const val2 = cmdArgs.getArgN(2); switch (aliasToCmd(val2)) { @@ -1431,6 +1505,22 @@ ${images.map(img => img.CQCode).join('\n')}`)); ext.cmdMap['ai'] = cmdAI; ext.onPoke = (ctx, event) => { + const uid = event.senderId; + const blockReason = BlockManager.checkBlock(uid); + if (blockReason) { + logger.info(`用户<${uid}>在黑名单中,原因: ${blockReason},忽略戳一戳`); + return; + } + + if (!event.isPrivate) { + const gid = event.groupId; + const groupBlockReason = BlockManager.checkBlock(gid); + if (groupBlockReason) { + logger.info(`群组<${gid}>在黑名单中,原因: ${groupBlockReason},忽略戳一戳`); + return; + } + } + const msg = createMsg(event.isPrivate ? 'private' : 'group', event.senderId, event.groupId); msg.message = `[CQ:poke,qq=${event.targetId.replace(/^.+:/, '')}]`; if (event.senderId === ctx.endPoint.userId) ext.onMessageSend(ctx, msg); @@ -1440,12 +1530,28 @@ ${images.map(img => img.CQCode).join('\n')}`)); //接受非指令消息 ext.onNotCommandReceived = (ctx, msg): void | Promise => { try { + // 黑名单用户消息不接收不处理 + const uid = ctx.player.userId; + const blockReason = BlockManager.checkBlock(uid); + if (blockReason) { + logger.info(`用户<${uid}>在黑名单中,原因: ${blockReason},忽略消息`); + return; + } + + if (!ctx.isPrivate) { + const gid = ctx.group.groupId; + const groupBlockReason = BlockManager.checkBlock(gid); + if (groupBlockReason) { + logger.info(`群组<${gid}>在黑名单中,原因: ${groupBlockReason},忽略消息`); + return; + } + } + const { disabledInPrivate, globalStandby, triggerRegex, ignoreRegex, triggerCondition } = ConfigManager.received; if (ctx.isPrivate && disabledInPrivate) { return; } - const uid = ctx.player.userId; const gid = ctx.group.groupId; const sid = ctx.isPrivate ? uid : gid; const ai = AIManager.getAI(sid); @@ -1532,6 +1638,22 @@ ${images.map(img => img.CQCode).join('\n')}`)); //接受的指令 ext.onCommandReceived = (ctx, msg, cmdArgs) => { try { + const uid = ctx.player.userId; + const blockReason = BlockManager.checkBlock(uid); + if (blockReason) { + logger.info(`用户<${uid}>在黑名单中,原因: ${blockReason},忽略指令`); + return; + } + + if (!ctx.isPrivate) { + const gid = ctx.group.groupId; + const groupBlockReason = BlockManager.checkBlock(gid); + if (groupBlockReason) { + logger.info(`群组<${gid}>在黑名单中,原因: ${groupBlockReason},忽略指令`); + return; + } + } + if (ToolManager.cmdArgs === null) { ToolManager.cmdArgs = cmdArgs; } @@ -1566,6 +1688,21 @@ ${images.map(img => img.CQCode).join('\n')}`)); ext.onMessageSend = (ctx, msg) => { try { const uid = ctx.player.userId; + const blockReason = BlockManager.checkBlock(uid); + if (blockReason) { + logger.info(`用户<${uid}>在黑名单中,原因: ${blockReason},忽略发送消息`); + return; + } + + if (!ctx.isPrivate) { + const gid = ctx.group.groupId; + const groupBlockReason = BlockManager.checkBlock(gid); + if (groupBlockReason) { + logger.info(`群组<${gid}>在黑名单中,原因: ${groupBlockReason},忽略发送消息`); + return; + } + } + const gid = ctx.group.groupId; const sid = ctx.isPrivate ? uid : gid; const ai = AIManager.getAI(sid); diff --git a/src/privilege.ts b/src/privilege.ts index f865f3f..2a3dec3 100644 --- a/src/privilege.ts +++ b/src/privilege.ts @@ -133,6 +133,14 @@ export const defaultCmdPriv: CmdPriv = { list: { priv: U } } }, + block: { + priv: M, args: { + add: { priv: M }, + remove: { priv: M }, + list: { priv: M }, + help: { priv: U } + } + }, token: { priv: S, args: { list: { priv: U }, diff --git a/src/tool/tool.ts b/src/tool/tool.ts index 299f30b..5676587 100644 --- a/src/tool/tool.ts +++ b/src/tool/tool.ts @@ -22,6 +22,7 @@ import { registerSetTrigger } from "./tool_trigger" import { registerMusicPlay } from "./tool_music" import { registerMeme } from "./tool_meme" import { registerRender } from "./tool_render" +import { registerBlockTool } from "./tool_block" import { logger } from "../logger" import { Image } from "../AI/image"; import { fixJsonString } from "../utils/utils_string"; @@ -205,6 +206,7 @@ export class ToolManager { registerMusicPlay(); registerMeme(); registerRender(); + registerBlockTool(); } /** diff --git a/src/tool/tool_block.ts b/src/tool/tool_block.ts new file mode 100644 index 0000000..1468aa3 --- /dev/null +++ b/src/tool/tool_block.ts @@ -0,0 +1,89 @@ +import { Tool } from "./tool"; +import { BlockManager } from "../block"; +import { ConfigManager } from "../config/configManager"; + +export function registerBlockTool() { + const toolBlock = new Tool({ + type: 'function', + function: { + name: 'block_user', + description: '拉黑指定用户,使其无法触发AI', + parameters: { + type: 'object', + properties: { + name: { + type: 'string', + description: '用户名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') + }, + reason: { + type: 'string', + description: '拉黑原因' + } + }, + required: ['name', 'reason'] + } + } + }); + toolBlock.solve = async (ctx, _, ai, args) => { + const { name, reason } = args; + + const ui = await ai.context.findUserInfo(ctx, name); + if (ui === null) return { content: `未找到<${name}>`, images: [] }; + + if (BlockManager.checkBlock(ui.id)) { + return { content: `用户<${name}>已经在黑名单中`, images: [] }; + } + + BlockManager.addBlock(ui.id, reason); + ctx.notice(`AI已将用户<${name}>(${ui.id})加入黑名单,原因: ${reason}`); + return { content: `已将<${name}>加入黑名单,原因: ${reason}`, images: [] }; + } + +// 不确定是否给ai +// const toolUnblock = new Tool({ +// type: 'function', +// function: { +// name: 'unblock_user', +// description: '移除黑名单中的用户', +// parameters: { +// type: 'object', +// properties: { +// name: { +// type: 'string', +// description: '用户名称' + (ConfigManager.message.showNumber ? '或纯数字QQ号' : '') +// } +// }, +// required: ['name'] +// } +// } +// }); +// toolUnblock.solve = async (ctx, _, ai, args) => { +// const { name } = args; + +// const ui = await ai.context.findUserInfo(ctx, name); +// if (ui === null) return { content: `未找到<${name}>`, images: [] }; + +// if (BlockManager.removeBlock(ui.id)) { +// return { content: `已将<${name}>移出黑名单`, images: [] }; +// } else { +// return { content: `用户<${name}>不在黑名单中`, images: [] }; +// } +// } + +// const toolList = new Tool({ +// type: 'function', +// function: { +// name: 'get_block_list', +// description: '获取AI黑名单列表', +// parameters: { +// type: 'object', +// properties: {}, +// required: [] +// } +// } +// }); +// toolList.solve = async (_, __, ___, ____) => { +// const list = BlockManager.getListText(); +// return { content: list, images: [] }; +// } +}