Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/cmd/privilege.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { AI } from "../AI/AI";
import { logger } from "../logger";
import { ConfigManager } from "../config/configManager";
import { aliasToCmd } from "../utils/utils";
import { PRIVILEGELEVELMAP } from "../config/config";


export interface CmdPrivInfo {
priv: [number, number, number], // 0: 会话所需权限, 1: 会话检查通过后用户所需权限, 2: 强行触发指令用户所需权限, 进行检查时若通过0和1则无需检查2
args?: CmdPriv; // 需通过前一级检查才可检查子命令
}

export interface CmdPriv { [key: string]: CmdPrivInfo };

export const U: [number, number, number] = [0, PRIVILEGELEVELMAP.user, PRIVILEGELEVELMAP.user]; // user
export const M: [number, number, number] = [0, PRIVILEGELEVELMAP.master, PRIVILEGELEVELMAP.master]; // master
export const I: [number, number, number] = [0, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.inviter]; // inviter
export const S: [number, number, number] = [1, PRIVILEGELEVELMAP.inviter, PRIVILEGELEVELMAP.master]; // spesial,会话所需权限为1,是才能被邀请者使用,否则需为骰主

export const defaultCmdPriv: CmdPriv = { ai: { priv: U } };

export class PrivilegeManager {
static cmdPriv: CmdPriv = defaultCmdPriv;

static reviveCmdPriv() {
try {
const cmdPriv = JSON.parse(ConfigManager.ext.storageGet('cmdPriv') || '{}');
if (typeof cmdPriv === 'object' && !Array.isArray(cmdPriv)) {
this.cmdPriv = this.updateCmdPriv(cmdPriv, JSON.parse(JSON.stringify(defaultCmdPriv)));
this.saveCmdPriv();
} else {
this.resetCmdPriv();
}
} catch (error) {
logger.error(`从数据库中获取cmdPriv失败:`, error);
}
}

static saveCmdPriv() {
ConfigManager.ext.storageSet('cmdPriv', JSON.stringify(this.cmdPriv));
}

static updateCmdPriv(cp: CmdPriv, defaultCp: CmdPriv): CmdPriv {
const newCp: CmdPriv = {};
for (const cmd in defaultCp) {
const defaultCpi = defaultCp[cmd];
if (!cp.hasOwnProperty(cmd)) {
newCp[cmd] = defaultCpi;
} else {
const cpi = cp[cmd];
if (defaultCpi.hasOwnProperty('args')) {
if (cpi.hasOwnProperty('args')) {
cpi.args = this.updateCmdPriv(cpi.args, defaultCpi.args);
} else {
cpi.args = defaultCpi.args;
}
} else if (cpi.hasOwnProperty('args')) {
delete cpi.args;
}
newCp[cmd] = cpi;
}
}
return newCp;
}

static resetCmdPriv() {
this.cmdPriv = JSON.parse(JSON.stringify(defaultCmdPriv));
this.saveCmdPriv();
}

static getCmdPrivInfo(cmdChain: string[], cp: CmdPriv = this.cmdPriv): CmdPrivInfo | null {
if (cmdChain.length === 0) {
return null;
}

const cmd = aliasToCmd(cmdChain[0]);
if (!cp.hasOwnProperty(cmd)) {
return null;
}

const cpi = cp[cmd];
if (cpi.args && cmdChain.length > 1) {
return this.getCmdPrivInfo(cmdChain.slice(1), cpi.args);
}

return cpi;
}

static checkPriv(ctx: seal.MsgContext, cmdArgs: seal.CmdArgs, ai: AI): { success: boolean, exist: boolean } {
const sessionPriv = ai.setting.priv;
const userPriv = ctx.privilegeLevel;
const cmdChain = [cmdArgs.command, ...cmdArgs.args].map(cmd => aliasToCmd(cmd));

function checkCmdPriv(cp: CmdPriv, i: number): { success: boolean, exist: boolean } {
if (i >= cmdChain.length) {
return { success: true, exist: true };
}

const cmd = cmdChain[i];
if (!cp.hasOwnProperty(cmd) && !cp.hasOwnProperty("*")) {
logger.warning(`权限检查失败,命令:[${cmdChain.join(' ')}],未在权限列表中找到匹配项`);
return { success: false, exist: false };
}

const cpi = cp[cmd] || cp["*"];

if (sessionPriv >= cpi.priv[0] && userPriv >= cpi.priv[1]) {
return cpi.args ? checkCmdPriv(cpi.args, i + 1) : { success: true, exist: true };
}

if (userPriv >= cpi.priv[2]) {
return cpi.args ? checkCmdPriv(cpi.args, i + 1) : { success: true, exist: true };
}

return { success: false, exist: true };
}

return checkCmdPriv(this.cmdPriv, 0);
}
}
132 changes: 132 additions & 0 deletions src/cmd/root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { AI, AIManager } from "../AI/AI";
import { ConfigManager } from "../config/configManager";
import { logger } from "../logger";
import { CmdPrivInfo, defaultCmdPriv, PrivilegeManager, U } from "./privilege";
import { aliasToCmd } from "../utils/utils";
import { registerCmdPrivilege } from "./sub_cmd/privilege";
import { registerCmdPrompt } from "./sub_cmd/prompt";
import { registerCmdStatus } from "./sub_cmd/status";
import { registerCmdCtxn } from "./sub_cmd/ctxn";
import { registerCmdTimer } from "./sub_cmd/timer";
import { registerCmdOn } from "./sub_cmd/on";
import { registerCmdStandby } from "./sub_cmd/standby";
import { registerCmdOff } from "./sub_cmd/off";
import { registerCmdForget } from "./sub_cmd/forget";
import { registerCmdRole } from "./sub_cmd/role";
import { registerCmdImage } from "./sub_cmd/image";
import { registerCmdMemory } from "./sub_cmd/memory";
import { registerCmdTool } from "./sub_cmd/tool";
import { registerCmdIgnore } from "./sub_cmd/ignore";
import { registerCmdToken } from "./sub_cmd/token";
import { registerCmdShut } from "./sub_cmd/shut";

export interface SubCmdContext {
ctx: seal.MsgContext;
msg: seal.Message;
cmdArgs: seal.CmdArgs;
epId: string;
uid: string;
gid: string;
sid: string;
ai: AI;
page: number;
ret: seal.CmdExecuteResult;
}

export class SubCmd {
name: string;
desc: string;
help: string;
priv: CmdPrivInfo;
solve: (scc: SubCmdContext) => seal.CmdExecuteResult | Promise<seal.CmdExecuteResult>;

constructor(name: string) {
this.name = name;
this.desc = '';
this.help = '';
this.priv = { priv: U };
this.solve = async () => { return seal.ext.newCmdExecuteResult(false); };

SubCmd.map[name] = this;
}

static map: { [key: string]: SubCmd } = {};
static register() {
registerCmdPrivilege();
registerCmdPrompt();
registerCmdStatus();
registerCmdCtxn();
registerCmdTimer();
registerCmdOn();
registerCmdStandby();
registerCmdOff();
registerCmdForget();
registerCmdRole();
registerCmdImage();
registerCmdMemory();
registerCmdTool();
registerCmdIgnore();
registerCmdToken();
registerCmdShut();

defaultCmdPriv.ai.args = Object.values(SubCmd.map).reduce((acc, sc) => {
acc[sc.name] = sc.priv;
return acc;
}, {});
}
}

export function registerCmd() {
SubCmd.register();

const cmd = seal.ext.newCmdItemInfo();
cmd.name = 'ai';
cmd.help = `帮助:\n${Object.values(SubCmd.map).map((sc) => `【.ai ${sc.name}】${sc.desc}`).join('\n')}`;
cmd.allowDelegate = true;
cmd.solve = (ctx, msg, cmdArgs) => {
try {
const ret = seal.ext.newCmdExecuteResult(true);

const subCmd = aliasToCmd(cmdArgs.getArgN(1));
if (SubCmd.map.hasOwnProperty(aliasToCmd(subCmd))) {
const uid = ctx.player.userId;
const gid = ctx.group.groupId;
const epId = ctx.endPoint.userId;
const sid = ctx.isPrivate ? uid : gid;

let page = 1;
const kwargPage = cmdArgs.kwargs.find((kwarg) => kwarg.name === 'page' || kwarg.name === 'p');
if (kwargPage && kwargPage.valueExists) {
page = parseInt(kwargPage.value);
if (isNaN(page)) {
seal.replyToSender(ctx, msg, '页码必须为数字');
return ret;
}
if (page < 1) {
seal.replyToSender(ctx, msg, '页码必须大于等于1');
return ret;
}
}

const ai = AIManager.getAI(sid);
const { success, exist } = PrivilegeManager.checkPriv(ctx, cmdArgs, ai);
if (!success) {
seal.replyToSender(ctx, msg, exist ? '权限不足' : '命令不存在');
return ret;
}

return SubCmd.map[subCmd].solve({ ctx, msg, cmdArgs, epId, uid, gid, sid, ai, page, ret });
} else {
ret.showHelp = true;
return ret;
}
} catch (e) {
logger.error(`指令.ai执行失败:${e.message}`);
seal.replyToSender(ctx, msg, `指令.ai执行失败:${e.message}`);
return seal.ext.newCmdExecuteResult(true);
}
}

ConfigManager.ext.cmdMap['AI'] = cmd;
ConfigManager.ext.cmdMap['ai'] = cmd;
}
66 changes: 66 additions & 0 deletions src/cmd/sub_cmd/ctxn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { aliasToCmd } from "../../utils/utils";
import { I, U } from "../privilege";
import { SubCmd, SubCmdContext } from "../root";

export function registerCmdCtxn() {
const cmd = new SubCmd('ctxn');
cmd.desc = '上下文里的名字相关';
cmd.help = `帮助:
【.ai ctxn status】查看上下文里的名字和自动修改状态
【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片
【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改)
0: 不自动修改
1: 自动修改为昵称
2: 自动修改为群名片`;
cmd.priv = {
priv: U, args: {
status: { priv: U },
set: { priv: I },
mod: { priv: I }
}
};
cmd.solve = (scc: SubCmdContext) => {
const { ctx, msg, cmdArgs, epId, gid, ai, ret } = scc;
const val2 = cmdArgs.getArgN(2);
switch (aliasToCmd(val2)) {
case 'status': {
seal.replyToSender(ctx, msg, `自动修改上下文里的名字状态:${ai.context.autoNameMod}
上下文里的名字有:\n${ai.context.userInfoList.map(ui => `${ui.name}(${ui.id})`).join('\n')}`);
return ret;
}
case 'set': {
const val3 = cmdArgs.getArgN(3);
const mod = aliasToCmd(val3);
if (mod !== 'nickname' && mod !== 'card') {
seal.replyToSender(ctx, msg, `帮助:
【.ai ctxn set [nick/card]】设置上下文里的名字为昵称/群名片`);
return ret;
}
const promises = ai.context.userInfoList.map(ui => ai.context.setName(epId, gid, ui.id, mod));
Promise.all(promises).then(() => {
seal.replyToSender(ctx, msg, `设置完成,上下文里的名字有:\n${ai.context.userInfoList.map(uni => `${uni.name}(${uni.id})`).join('\n')}`);
});
return ret;
}
case 'mod': {
const val3 = cmdArgs.getArgN(3);
const mod = parseInt(val3);
if (isNaN(mod) || mod < 0 || mod > 2) {
seal.replyToSender(ctx, msg, `帮助:
【.ai ctxn mod <数字>】自动修改上下文里的名字(只在第一次出现时修改)
0: 不自动修改
1: 自动修改为昵称
2: 自动修改为群名片`);
return ret;
}
ai.context.autoNameMod = mod;
seal.replyToSender(ctx, msg, `设置成功,将自动修改上下文里的名字为${mod === 1 ? '昵称' : '群名片'}`);
return ret;
}
default: {
seal.replyToSender(ctx, msg, cmd.help);
return ret;
}
}
}
}
43 changes: 43 additions & 0 deletions src/cmd/sub_cmd/forget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AIManager } from "../../AI/AI";
import { aliasToCmd } from "../../utils/utils";
import { I, U } from "../privilege";
import { SubCmd, SubCmdContext } from "../root";

export function registerCmdForget() {
const cmd = new SubCmd('forget');
cmd.desc = '遗忘上下文';
cmd.help = '';
cmd.priv = {
priv: I, args: {
assistant: { priv: U },
user: { priv: U }
}
};
cmd.solve = (scc: SubCmdContext) => {
const { ctx, msg, cmdArgs, sid, ai, ret } = scc;

ai.resetState();

const val2 = cmdArgs.getArgN(2);
switch (aliasToCmd(val2)) {
case 'assistant': {
ai.context.clearMessages('assistant', 'tool');
seal.replyToSender(ctx, msg, 'ai上下文已清除');
AIManager.saveAI(sid);
return ret;
}
case 'user': {
ai.context.clearMessages('user');
seal.replyToSender(ctx, msg, '用户上下文已清除');
AIManager.saveAI(sid);
return ret;
}
default: {
ai.context.clearMessages();
seal.replyToSender(ctx, msg, '上下文已清除');
AIManager.saveAI(sid);
return ret;
}
}
}
}
Loading