koishi-plugin-satori-ai
Version:
基于 Satori 适配器的智能对话插件,具备用户画像、长期记忆、常识管理和好感度系统
1,358 lines (1,344 loc) • 236 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/locales/zh.yml
var require_zh = __commonJS({
"src/locales/zh.yml"(exports2, module2) {
module2.exports = { commands: { sat: { description: "AI聊天", messages: { "no-prompt": "你好。使用说明可以看我的空间哦。", tooManyEnglishLetters: "请不要用这么多英文......", err: "寄!{0}", tooLong: "你的话太多了。", content_tooLong: "这个问题有点复杂,你还是去问别人吧。", "duplicate-dialogue": "这个刚刚说过了吧......", online: "思考中,请再等一会!", "no-response": "我不知道你在说什么……", update_level_succeed: "用户等级已更新为{0}", exceeds: "已经差不多够了吧?请明天再来。", usage: "今日次数{0}/{1}", hatMan: "叮!帽子先生保护了你的好感度,快说谢谢帽子先生吧!", rockBottom: "叮!谷底小石保护了你的好感度,快说谢谢谷底小石吧!", not_good_mood: "别得寸进尺……(好感↓)", pocket_money: "心情不错,给你点零花钱吧~(获得了{0}p点)", warning: "<at id={0}/>", block1: "我讨厌你!", block2: "我讨厌你们!" } }, "sat.clear": { description: "清空当前频道会话", messages: { clean: "已清空当前会话。", Allclean: "已清空所有会话。" } }, "sat.common_sense": { description: "添加常识", messages: { "no-prompt": "你想让我知道什么?", succeed: "我知道了,{0}" } }, "sat.broadcast": { description: "广播一条消息,在每个生效群收到首条消息时会推送", options: { delete: "删除一条广播,参数为广播的ID", list: "查看当前所有广播", img: "使用puppeteer图片化广播消息" } } } };
}
});
// src/index.tsx
var src_exports = {};
__export(src_exports, {
SAT: () => SAT,
default: () => src_default,
puppeteer: () => puppeteer,
refreshPuppeteer: () => refreshPuppeteer
});
module.exports = __toCommonJS(src_exports);
var import_koishi16 = require("koishi");
var path8 = __toESM(require("path"));
var import_fs = require("fs");
// src/emoji.ts
var import_koishi2 = require("koishi");
var fs2 = __toESM(require("fs"));
var path2 = __toESM(require("path"));
// src/utils.ts
var import_koishi = require("koishi");
// src/database.ts
async function isTargetIdNotExists(ctx, userId) {
const users = await ctx.database.get("p_system", { userid: userId });
return users.length === 0;
}
__name(isTargetIdNotExists, "isTargetIdNotExists");
async function createUser(ctx, user) {
await ctx.database.create("p_system", {
userid: user.userid || "",
usersname: user.usersname || "",
p: user.p || 0,
favorability: user.favorability || 0,
userlevel: user.userlevel || 0,
usage: user.usage || 0,
location: user.location || "",
lastChatTime: user.lastChatTime || (/* @__PURE__ */ new Date()).getDate(),
items: user.items || {}
});
}
__name(createUser, "createUser");
async function updateFavorability(ctx, user, adjustment) {
if (!user)
return;
let newValue;
if (typeof adjustment === "number")
newValue = user.favorability + adjustment;
else
newValue = user.favorability;
await ctx.database.set("p_system", { userid: user.userid }, { favorability: newValue });
}
__name(updateFavorability, "updateFavorability");
async function updateUserLevel(ctx, user, level) {
if (!user)
return;
level = level < 5 ? level : 4;
await ctx.database.set("p_system", { userid: user.userid }, { userlevel: level });
}
__name(updateUserLevel, "updateUserLevel");
async function updateUserUsage(ctx, user, adjustment = 1) {
if (!user)
return;
if (user.lastChatTime && (/* @__PURE__ */ new Date()).getDate() !== user.lastChatTime)
user.usage = 0;
await ctx.database.set("p_system", { userid: user.userid }, { usage: user.usage + adjustment });
await ctx.database.set("p_system", { userid: user.userid }, { lastChatTime: (/* @__PURE__ */ new Date()).getDate() });
return user.usage + adjustment;
}
__name(updateUserUsage, "updateUserUsage");
async function updateUserItems(ctx, user) {
if (!user)
return;
await ctx.database.set("p_system", { userid: user.userid }, { items: user.items });
}
__name(updateUserItems, "updateUserItems");
async function updateUserP(ctx, user, adjustment) {
if (!user)
return;
user.p = await ctx.database.get("p_system", { userid: user.userid })[0]?.p || user.p;
if (user.p + adjustment < 0)
adjustment = -user.p;
await ctx.database.set("p_system", { userid: user.userid }, { p: user.p + adjustment });
}
__name(updateUserP, "updateUserP");
async function updateUserLocation(ctx, user, location) {
if (!user)
return;
await ctx.database.set("p_system", { userid: user.userid }, { location });
}
__name(updateUserLocation, "updateUserLocation");
async function getUser(ctx, userId) {
const users = await ctx.database.get("p_system", { userid: userId });
return users[0] || null;
}
__name(getUser, "getUser");
async function ensureUserExists(ctx, userId, username) {
const notExists = await isTargetIdNotExists(ctx, userId);
if (notExists) {
await createUser(ctx, {
userid: userId,
usersname: username,
p: 0,
favorability: 0,
userlevel: 0,
usage: 0,
location: "",
lastChatTime: (/* @__PURE__ */ new Date()).getDate(),
items: {}
});
}
return getUser(ctx, userId);
}
__name(ensureUserExists, "ensureUserExists");
function extendDatabase(ctx) {
ctx.model.extend("p_system", {
id: "unsigned",
userid: "string",
usersname: "string",
p: "integer",
favorability: "integer",
userlevel: "integer",
usage: "integer",
location: "string",
lastChatTime: "integer",
items: "object"
}, { autoInc: true });
}
__name(extendDatabase, "extendDatabase");
// src/utils.ts
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var logger = new import_koishi.Logger("satori-utils");
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
__name(escapeRegExp, "escapeRegExp");
function parseTimeToMinutes(timeStr) {
const [hours, minutes] = timeStr.split(":").map(Number);
return hours * 60 + (minutes || 0);
}
__name(parseTimeToMinutes, "parseTimeToMinutes");
function parseTime(timestamp) {
const date = new Date(timestamp);
return date.getHours() * 60 + date.getMinutes();
}
__name(parseTime, "parseTime");
function getTimeOfDay(hours) {
if (hours >= 5 && hours < 9)
return "清晨";
if (hours < 12)
return "上午";
if (hours < 14)
return "中午";
if (hours < 17)
return "下午";
if (hours < 19)
return "傍晚";
if (hours < 22)
return "晚上";
return "深夜";
}
__name(getTimeOfDay, "getTimeOfDay");
function trimSlash(url) {
return url.replace(/\/$/, "");
}
__name(trimSlash, "trimSlash");
function splitSentences(text, MIN_LENGTH, MAX_LENGTH) {
const PUNCTUATION_REGEX = /([。!?!?…]+)/g;
const rawSegments = text.split(PUNCTUATION_REGEX);
const initialSentences = [];
for (let i = 0; i < rawSegments.length; i += 2) {
const sentence = rawSegments[i]?.trim() || "";
const punctuation = rawSegments[i + 1]?.trim() || "";
if (sentence)
initialSentences.push(sentence + punctuation);
}
const finalSentences = [];
let currentSentence = "";
for (const sentence of initialSentences) {
const potentialLength = currentSentence.length + sentence.length;
if (sentence.length > MAX_LENGTH) {
if (currentSentence)
finalSentences.push(currentSentence);
finalSentences.push(sentence);
currentSentence = "";
continue;
}
if (potentialLength <= MAX_LENGTH) {
currentSentence += sentence;
if (currentSentence.length >= MIN_LENGTH && potentialLength >= MAX_LENGTH * 0.8) {
finalSentences.push(currentSentence);
currentSentence = "";
}
} else {
finalSentences.push(currentSentence);
currentSentence = sentence;
}
}
if (currentSentence)
finalSentences.push(currentSentence);
return finalSentences.reduce((result, sentence) => {
if (sentence.length < MIN_LENGTH && result.length > 0) {
result[result.length - 1] += sentence;
} else {
result.push(sentence);
}
return result;
}, []);
}
__name(splitSentences, "splitSentences");
function probabilisticCheck(probability) {
return Math.random() < probability;
}
__name(probabilisticCheck, "probabilisticCheck");
function isErrorWithMessage(error) {
return typeof error === "object" && error !== null && "message" in error;
}
__name(isErrorWithMessage, "isErrorWithMessage");
function processPrompt(prompt) {
if (!prompt)
return "";
if (prompt.includes(":poke"))
return "戳戳";
prompt = prompt.replace(/<[^>]*?avatar[^>]*>/g, "。回复:");
prompt = prompt.replace(/<[^>]*?img[^>]*>/g, "[图片]");
prompt = prompt.replace(/<[^>]*?name="([^\"]*)"[^>]*>/g, (_, name) => `${name}`);
prompt = prompt.replace(/\*\*/g, "");
if (!prompt)
return "**";
return prompt.trim();
}
__name(processPrompt, "processPrompt");
function filterResponse(prompt, words, options) {
const applyBracketFilter = options?.applyBracketFilter ?? true;
const applyTagFilter = options?.applyTagFilter ?? true;
let working = prompt;
if (applyBracketFilter) {
const parts = working.split(/([(\[【《(][^))]{0,500}[)\]】》)])/g);
const filtered = parts.map((part) => {
if (part.startsWith("(") && part.endsWith(")") || part.startsWith("(") && part.endsWith(")") || part.startsWith("[") && part.endsWith("]") || part.startsWith("【") && part.endsWith("】") || part.startsWith("《") && part.endsWith("》")) {
return words.some((word) => part.includes(word)) ? "" : part;
}
return part;
}).join("…");
working = filtered.replace(/\s+/g, ".");
if (!working) {
working = prompt;
}
}
if (!applyTagFilter) {
return working ? { content: working, error: false } : { content: "返回内容为空,请重置对话", error: true };
}
const pTagRegex = /<p>[\s\S]*?<\/p>/g;
const doubaoRegex = /<doubaothinking>[\s\S]*?<\/doubaothinking>/g;
const answerTagRegex = /<answer>[\s\S]*?<\/answer>/g;
const pMatches = working.match(pTagRegex) || [];
const doubaoMatches = working.match(doubaoRegex) || [];
const answerTagMatches = working.match(answerTagRegex) || [];
let combined = null;
if (pMatches.length > 0) {
combined = pMatches.map((s) => s.replace(/^<p>/i, "").replace(/<\/p>$/i, "")).join(",");
} else if (doubaoMatches.length > 0) {
combined = doubaoMatches.map((s) => s.replace(/<doubaothinking>/i, "").replace(/<\/doubaothinking>/i, "")).join(",");
} else if (answerTagMatches.length > 0) {
combined = answerTagMatches.map((s) => s.replace(/<answer>/i, "").replace(/<\/answer>/i, "")).join(",");
}
if (!combined) {
return { content: "内容抽取失败,请重置对话", error: true };
}
const cleanupRegex = /<p>|<\/p>|<doubaothinking>|<\/doubaothinking>|<answer>|<\/answer>|<br>|<\/br>|<br\/>/gi;
const cleanedContent = combined.replace(cleanupRegex, "").trim();
return cleanedContent ? { content: cleanedContent, error: false } : { content: "标签过滤失败,请重置对话", error: true };
}
__name(filterResponse, "filterResponse");
function addOutputCensor(session, word, baseURL) {
const blockWordsPath = path.resolve(baseURL, "output_censor.txt");
if (!fs.existsSync(blockWordsPath)) {
fs.mkdirSync(blockWordsPath);
fs.writeFileSync(blockWordsPath, word);
}
let blockWords = fs.readFileSync(blockWordsPath, "utf-8").split(",");
blockWords.push(word);
fs.writeFileSync(blockWordsPath, blockWords.join(","));
session.send(`添加"${word}"成功`);
}
__name(addOutputCensor, "addOutputCensor");
async function updateUserPWithTicket(ctx, user, adjustment) {
if (!user)
return;
if (user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description === "on") {
await updateUserP(ctx, user, adjustment);
}
}
__name(updateUserPWithTicket, "updateUserPWithTicket");
function findLongestCommonSubstring(str1, str2) {
if (!str1 || !str2)
return 0;
const m = str1.length;
const n = str2.length;
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
let maxLength = 0;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (str1[i - 1] === str2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
maxLength = Math.max(maxLength, dp[i][j]);
}
}
}
return maxLength;
}
__name(findLongestCommonSubstring, "findLongestCommonSubstring");
function countCommonChars(str1, str2) {
const chars1 = /* @__PURE__ */ new Map();
const chars2 = /* @__PURE__ */ new Map();
for (const char of str1) {
if (/[\u4e00-\u9fff]/.test(char)) {
chars1.set(char, (chars1.get(char) || 0) + 1);
}
}
for (const char of str2) {
if (/[\u4e00-\u9fff]/.test(char)) {
chars2.set(char, (chars2.get(char) || 0) + 1);
}
}
let commonCount = 0;
for (const [char, count1] of chars1) {
const count2 = chars2.get(char) || 0;
commonCount += Math.min(count1, count2);
}
const countDigits = /* @__PURE__ */ __name((s) => (s.match(/\d/g) || []).length, "countDigits");
const digits1 = countDigits(str1);
const digits2 = countDigits(str2);
if (digits1 >= 3 || digits2 >= 3) {
return Math.max(commonCount, 2);
}
return commonCount;
}
__name(countCommonChars, "countCommonChars");
async function wrapInHTML(str, width = 20) {
if (!puppeteer) {
logger.warn("puppeteer未就绪");
return "出现错误,请联系管理员";
}
const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<style>
/* 让页面宽度随内容收缩,这样 puppeteer 渲染产物不会有大片空白 */
html, body {
margin: 0;
padding: 0;
width: auto;
height: auto;
display: inline-block; /* shrink-to-fit */
background: transparent;
}
.satori-text {
padding: 10px;
display: inline-block;
box-sizing: border-box;
font-family: "Noto Sans SC", "Microsoft YaHei", Arial, sans-serif;
font-size: 16px;
line-height: 1.4;
/* 最大约 20 个字符宽度(可调整为 10-20ch) */
max-width: ${width}ch;
/* 同时允许内容根据文字宽度收缩(fit-content 在一些旧浏览器需备份) */
width: -moz-fit-content;
width: fit-content;
/* 保留输入中的换行符并在需要时换行 */
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
-webkit-font-smoothing: antialiased;
}
</style>
</head>
<body>
<div class="satori-text">${str.replaceAll(/\n/g, "<br/>")}</div>
</body>
</html>`;
try {
const renderPromise = puppeteer.render(html);
const timeoutPromise = new Promise(
(_, reject) => setTimeout(() => reject(new Error("Puppeteer 渲染超时")), 3e4)
);
return await Promise.race([renderPromise, timeoutPromise]);
} catch (error) {
logger.error(`Puppeteer 渲染失败: ${error}`);
return "渲染失败,请联系管理员";
}
}
__name(wrapInHTML, "wrapInHTML");
// src/emoji.ts
var logger2 = new import_koishi2.Logger("satori-emoji");
var EmojiManager = class {
constructor(ctx, config) {
this.ctx = ctx;
this.config = config;
this.emojiDir = path2.resolve(this.config.dataDir, "emojis");
this.loadEmojis();
}
static {
__name(this, "EmojiManager");
}
categories = [];
emojiDir;
async loadEmojis() {
try {
if (!fs2.existsSync(this.emojiDir)) {
logger2.warn(`表情包目录不存在: ${this.emojiDir}`);
return;
}
const entries = await fs2.promises.readdir(this.emojiDir, { withFileTypes: true });
const folders = entries.filter((entry) => entry.isDirectory());
for (const folder of folders) {
const folderPath = path2.join(this.emojiDir, folder.name);
const files = await fs2.promises.readdir(folderPath);
const imageFiles = files.filter((file) => /\.(jpg|jpeg|png|gif|webp)$/i.test(file));
if (imageFiles.length > 0) {
this.categories.push({
name: folder.name,
files: imageFiles
});
}
}
logger2.info(`已加载表情包类别: ${this.categories.map((c) => c.name).join(", ")}`);
} catch (error) {
logger2.error(`加载表情包失败: ${error}`);
}
}
shouldSendEmoji() {
if (!this.config.enable_emoji)
return false;
return Math.random() < this.config.emoji_probability;
}
async getEmoji(session, user, recentMessages) {
if (this.categories.length === 0)
return null;
const contextMessages = recentMessages.slice(-5).map((msg) => {
if (msg.role === "system")
return "";
return `${msg.role === "user" ? "用户" : "AI"}: ${msg.content}`;
}).filter((s) => s).join("\n");
const categoryNames = this.categories.map((c) => c.name).join("、");
const systemPrompt = `你是一个表情包推荐助手。请根据提供的对话上下文,从给定的表情包类别中为AI选择一个最合适的表情包类别。
表情包类别列表:[${categoryNames}]
如果不适合发送表情包,请返回 "none"。
请只返回类别名称或 "none",不要包含其他内容。`;
const userPrompt = `对话上下文:
${contextMessages}
请推荐一个最合适的表情包类别,只输出类别名称。`;
const fullPrompt = `${systemPrompt}
${userPrompt}`;
const messages = [
{ role: "user", content: fullPrompt }
];
logger2.info(`表情包推荐提示词:
${fullPrompt}`);
const { baseURL, key, model } = this.getAPIConfig(user);
try {
const response = await this.callLLM(baseURL, key, model, messages);
const categoryName = response.trim();
logger2.info(`模型推荐的表情包类别: ${categoryName}`);
if (!categoryName || categoryName === "none")
return null;
const category = this.categories.find((c) => c.name === categoryName);
if (category) {
const randomFile = category.files[Math.floor(Math.random() * category.files.length)];
const filePath = path2.join(this.emojiDir, category.name, randomFile);
logger2.info(`选择表情包: ${category.name}/${randomFile}`);
return filePath;
} else {
logger2.info(`模型返回了无效的表情包类别: ${categoryName}`);
return null;
}
} catch (error) {
logger2.warn(`获取表情包失败: ${error}`);
return null;
}
}
getAPIConfig(user) {
const enableUserKey = user?.items?.["地灵殿通行证"]?.description === "on";
if (enableUserKey) {
const ticket = user.items["地灵殿通行证"].metadata;
if (ticket?.not_reasoner_model) {
return {
baseURL: ticket.baseURL,
key: ticket.key,
model: ticket.not_reasoner_model
};
}
return {
baseURL: ticket.baseURL,
key: ticket.key,
model: ticket.model
};
}
return {
baseURL: this.config.not_reasoner_LLM_URL,
key: this.config.not_reasoner_LLM_key[0],
// 简化处理,使用第一个key
model: this.config.not_reasoner_LLM
};
}
async callLLM(baseURL, key, model, messages) {
const url = `${trimSlash(baseURL)}/chat/completions`;
const headers = {
"Authorization": `Bearer ${key}`,
"Content-Type": "application/json"
};
const payload = {
model,
messages,
temperature: 0.6,
max_tokens: 500
// 增加到500以适应可能的思考过程
};
try {
const response = await this.ctx.http.post(url, payload, { headers, timeout: 1e4 });
logger2.info(`LLM响应: ${JSON.stringify(response)}`);
if (response.choices && response.choices.length > 0) {
const content = response.choices[0].message?.content;
return content || "";
}
throw new Error("API响应格式错误");
} catch (error) {
if (isErrorWithMessage(error)) {
const status = error.response?.status || 0;
const message = error.response?.data?.error?.message || error.message;
logger2.warn(`API Error [${status}]: ${message}`);
}
throw error;
}
}
};
// src/api.ts
var import_koishi3 = require("koishi");
var logger3 = new import_koishi3.Logger("satori-api");
var APIClient = class {
constructor(ctx, config) {
this.ctx = ctx;
this.config = config;
this.testConnection();
}
static {
__name(this, "APIClient");
}
currentKeyIndex = 0;
// 发送聊天请求
async chat(user, messages) {
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
let keys;
let modle;
let baseURL;
let thinkingType = "enabled";
if (enableUserKey) {
const ticket = user.items["地灵殿通行证"].metadata;
keys = [ticket?.key];
modle = ticket?.model;
baseURL = ticket?.baseURL;
const not_reasoner_model = ticket?.not_reasoner_model;
const length = ticket?.use_not_reasoner_LLM_length;
if (not_reasoner_model && length && messages[messages.length - 1].content.length <= length) {
thinkingType = "disabled";
modle = not_reasoner_model;
}
} else {
const config = this.config;
const useNotReasoner = messages[messages.length - 1].content.length <= config.use_not_reasoner_LLM_length;
keys = useNotReasoner ? config.not_reasoner_LLM_key : config.keys;
modle = useNotReasoner ? config.not_reasoner_LLM : config.appointModel;
baseURL = useNotReasoner ? config.not_reasoner_LLM_URL : config.baseURL;
thinkingType = useNotReasoner ? "disabled" : "enabled";
}
const payload = this.createPayload(messages, modle, thinkingType);
for (let i = 0; i < keys.length; i++) {
try {
return await this.tryRequest(baseURL, payload, keys);
} catch (error) {
if (i == keys.length - 1) {
return this.handleAPIError(error);
}
this.rotateKey();
}
}
}
// 发送辅助聊天请求
async auxiliaryChat(messages) {
const AuxiliaryPayload = this.createAuxiliaryPayload(messages, this.config.auxiliary_LLM);
const url = `${trimSlash(this.config.auxiliary_LLM_URL)}/chat/completions`;
const headers = this.createHeaders(this.config.auxiliary_LLM_key);
let content;
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
try {
const response = await this.ctx.http.post(url, AuxiliaryPayload, { headers, timeout: 36e5 });
content = response.choices[0].message.content;
return { content, error: false };
} catch (error) {
if (i == this.config.maxRetryTimes) {
return this.handleAPIError(error);
}
logger3.warn(`辅助模型API请求失败(${error}),重试(第${i}次)中...`);
continue;
}
}
return { content: "unreachable", error: true };
}
// 发送生成用户画像请求
async generateUserPortrait(user, messages) {
if (user.userid == "Alice")
return { content: "测试画像", error: false };
const enableUserKey = user?.items?.["地灵殿通行证"]?.description && user.items["地灵殿通行证"].description == "on";
let keys;
let modle;
let baseURL;
if (enableUserKey) {
const key = user.items["地灵殿通行证"].metadata?.key;
keys = [key];
modle = user.items["地灵殿通行证"].metadata?.model;
baseURL = user.items["地灵殿通行证"].metadata?.baseURL;
} else {
keys = this.config.keys;
modle = this.config.appointModel;
baseURL = this.config.baseURL;
}
const payload = this.createAuxiliaryPayload(messages, modle);
const url = `${trimSlash(baseURL)}/chat/completions`;
const headers = this.createHeaders(keys);
let content;
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
try {
const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
content = response.choices[0].message.content;
return { content, error: false };
} catch (error) {
if (i == this.config.maxRetryTimes) {
return this.handleAPIError(error);
}
await new Promise((resolve4) => setTimeout(resolve4, this.config.retry_delay_time || 5e3));
logger3.warn(`生成画像时API请求失败(${error}),重试(第${i}次)中...`);
continue;
}
}
return { content: "unreachable", error: true };
}
// 生成请求体
createPayload(messages, model, thinkingType = "enabled") {
return {
model,
messages,
temperature: this.config.temperature,
top_p: 1,
max_tokens: this.config.max_output_tokens,
frequency_penalty: this.config.frequency_penalty,
presence_penalty: this.config.presence_penalty,
thinking: {
type: thinkingType
}
};
}
// 生成辅助请求体
createAuxiliaryPayload(messages, model) {
return {
model,
messages,
temperature: 0.1
};
}
// 尝试请求
async tryRequest(URL, payload, keys) {
const url = `${trimSlash(URL)}/chat/completions`;
const headers = this.createHeaders(keys);
let content;
for (let i = 1; i <= this.config.maxRetryTimes; i++) {
try {
const response = await this.ctx.http.post(url, payload, { headers, timeout: 36e5 });
logger3.info(`APIID: ${response.id}, 输入token: ${response.usage.prompt_tokens}, 输出token: ${response.usage.completion_tokens}, 总token: ${response.usage.total_tokens}`);
content = response.choices[0].message.content;
const reasoning_content = response.choices[0].message.reasoning_content || "无";
if (this.config.reasoning_content)
logger3.info(`思维链: ${reasoning_content}`);
if (!content && reasoning_content) {
logger3.warn("返回内容为空,但存在推理内容");
}
if (reasoning_content != "无" && content && content.length > reasoning_content.length) {
logger3.warn("返回内容疑似包含推理内容");
}
if (response.usage.completion_tokens > this.config.max_output_tokens) {
logger3.warn("返回内容过长导致被截断");
continue;
}
return { content, error: false, reasoning_content };
} catch (error) {
if (i == this.config.maxRetryTimes) {
return this.handleAPIError(error);
}
await new Promise((resolve4) => setTimeout(resolve4, this.config.retry_delay_time || 5e3));
logger3.warn(`API请求失败(${error}),重试(第${i}次)中...`);
continue;
}
}
return { content: "请求失败,请重试", error: true };
}
// 生成请求头
createHeaders(keys) {
return {
Authorization: `Bearer ${keys[this.currentKeyIndex]}`,
"Content-Type": "application/json"
};
}
// 处理API错误
handleAPIError(error) {
if (!isErrorWithMessage(error))
throw error;
const status = error.response?.status || 0;
const errorCode = error.response?.data?.error?.code || "unknown";
const message = error.response?.data?.error?.message || error.message;
logger3.error(`API Error [${status}]: ${errorCode} - ${message}`);
switch (status) {
case 400:
return { content: "请求体格式错误:" + message, error: true };
case 401:
return { content: "API key 错误,认证失败:" + message, error: true };
case 402:
return { content: "账号余额不足:" + message, error: true };
case 422:
return { content: "请求体参数错误:" + message, error: true };
case 429:
return { content: "请求速率(TPM 或 RPM)达到上限:" + message, error: true };
case 500:
return { content: `api服务器内部故障:` + message, error: true };
case 503:
return { content: "api服务器负载过高:" + message, error: true };
default:
return { content: message, error: true };
}
}
// 切换API密钥
rotateKey() {
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.config.keys.length;
logger3.debug(`Switched to API key index: ${this.currentKeyIndex}`);
}
// 测试连接
async testConnection() {
try {
await this.ctx.http.get(`${trimSlash(this.config.baseURL)}/models`, { headers: this.createHeaders(this.config.keys) });
logger3.info("API connection test succeeded");
return true;
} catch (error) {
logger3.warn("API connection test failed");
return false;
}
}
};
// src/memory.ts
var import_koishi4 = require("koishi");
var fs3 = __toESM(require("fs"));
var path3 = __toESM(require("path"));
var logger4 = new import_koishi4.Logger("satori-memory");
var MemoryManager = class {
constructor(ctx, config) {
this.ctx = ctx;
this.config = config;
}
static {
__name(this, "MemoryManager");
}
channelMemories = /* @__PURE__ */ new Map();
channelDialogues = /* @__PURE__ */ new Map();
charactersToRemove = ["的", "一", "是", "了", "什", "么", "我", "谁", "不", "人", "在", "他", "有", "这", "个", "上", "们", "来", "到", "时", "大", "地", "为", "子", "中", "你", "说", "生", "国", "年", "着", "就", "那", "和", "要", "她", "出", "也", "得", "里", "后", "自", "以", "会", "id="];
MAX_MEMORY_LENGTH = 5e3;
// 更新记忆
async updateMemories(session, prompt, config, response) {
if (response.error)
return;
this.updateChannelMemory(session, prompt, config, response.content);
const date = ` (对话日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()})`;
const userFavourbility = (await getUser(this.ctx, session.userId)).favorability;
if (this.shouldRemember(prompt, userFavourbility)) {
await this.saveLongTermMemory(session, [{
role: date,
content: prompt
}]);
}
}
// 是否应当记忆
shouldRemember(content, userFavourbility) {
return (content.length >= this.config.remember_min_length || content.includes("记住") && userFavourbility >= 50) && !this.config.memory_block_words.some((word) => content.includes(word));
}
// 更新频道对话
async updateChannelDialogue(session, prompt, name) {
if (!this.config.channel_dialogues)
return "";
if (!this.channelDialogues[session.channelId])
this.channelDialogues[session.channelId] = [];
this.channelDialogues[session.channelId].push('"' + name + '" 说: ' + prompt);
if (this.channelDialogues[session.channelId]?.length > this.config.channel_dialogues_max_length) {
this.channelDialogues[session.channelId] = this.channelDialogues[session.channelId].slice(-this.config.channel_dialogues_max_length);
}
}
async getChannelDialogue(session) {
if (!this.config.channel_dialogues)
return "";
const Dialogue = this.channelDialogues[session.channelId]?.join("\n") || "";
const result = "以下是群聊内最近的包括所有人的聊天记录,当当前对话涉及其中内容时你可以参考这些信息,但是要注意分辨发言人是谁:{\n" + Dialogue + "\n}\n";
return result;
}
// 括号引号过滤
bracketFilter(content, config) {
if (!config.bracket_filter)
return content;
let filtered = content.replace(/["'‘“]|[’”'"]/g, "");
let previous;
do {
previous = filtered;
filtered = filtered.replace(/[(({\[][^()\]})]*[))\]\}]/g, "");
} while (filtered !== previous);
return filtered.trim() || content.replace(/[(({\[]|[))\]\}]/g, "");
}
// 内容过滤
memoryFilter(content, config) {
if (!config.memory_filter)
return content;
const filterWords = config.memory_filter.split("-").map((word) => word.trim()).filter((word) => word.length > 0);
if (!filterWords.length)
return content;
const sentenceSplitRegex = /([。!?;!?;…]|\.{3})/g;
const sentences = content.split(sentenceSplitRegex).reduce((acc, cur, i, arr) => {
if (i % 2 === 0)
acc.push(cur + (arr[i + 1] || ""));
return acc;
}, []);
const filtered = sentences.filter((sentence) => !filterWords.some((word) => sentence.includes(word))).join("");
const result = filtered.replace(/([,、…])\1+/g, "$1").replace(/^[,。!?;,.!?]+/, "").trim();
return result || content;
}
// 更新短期记忆
updateChannelMemory(session, prompt, config, response) {
if (response) {
response = this.bracketFilter(response, config);
response = this.memoryFilter(response, config);
}
if (!response) {
response = "…";
}
let channelId = session.channelId;
if (config.personal_memory)
channelId = session.userId;
if (!this.channelMemories.has(channelId)) {
this.channelMemories.set(channelId, {
dialogues: [],
updatedAt: Date.now()
});
}
const memory = this.channelMemories.get(channelId);
memory.dialogues.push({ role: "user", content: prompt });
this.updateChannelDialogue(session, prompt, session.username);
if (this.config.enhanceReasoningProtection) {
if (this.config.enable_self_memory) {
memory.dialogues.push({ role: "assistant", content: "<p>" + response + "</p>" });
} else {
memory.dialogues.push({ role: "assistant", content: "<p>…</p>" });
}
} else {
if (this.config.enable_self_memory) {
memory.dialogues.push({ role: "assistant", content: response });
} else {
memory.dialogues.push({ role: "assistant", content: "…" });
}
}
if (memory.dialogues.length > this.config.message_max_length) {
memory.dialogues = memory.dialogues.slice(-this.config.message_max_length);
}
}
// 清除频道记忆
clearChannelMemory(channelId) {
this.channelMemories.delete(channelId);
}
// 清除频道对话
clearChannelDialogue(channelId) {
this.channelDialogues[channelId] = [];
}
// 清除全部记忆
clearAllMemories() {
this.channelMemories.clear();
this.channelDialogues = /* @__PURE__ */ new Map();
}
// 返回频道记忆
getChannelMemory(channelId) {
return this.channelMemories.get(channelId)?.dialogues || [];
}
// 长期记忆存储
async saveLongTermMemory(session, dialogues, filePath = "") {
if (filePath === "")
filePath = this.getUserMemoryPath(session.userId);
await this.ensureMemoryFile(filePath);
const filtered = dialogues.filter((entry) => !this.config.memory_block_words.some((word) => entry.content.includes(word)));
if (filtered.length === 0)
return;
const existing = await this.loadMemoryFile(filePath);
let updated = [...existing, ...filtered];
if (updated.length > this.MAX_MEMORY_LENGTH) {
updated = updated.slice(-this.MAX_MEMORY_LENGTH);
}
await fs3.promises.writeFile(filePath, JSON.stringify(updated, null, 2), "utf-8");
}
// 记忆检索
async searchMemories(session, type = "user") {
const filePathMap = {
"user": this.getUserMemoryPath(session.userId),
"common": path3.join(this.config.dataDir, "common_sense.txt"),
"group": path3.join(this.config.dataDir, "group_sense", `${session.channelId}.txt`)
};
const topNMap = {
"user": this.config.dailogues_topN,
"common": this.config.common_topN,
"group": this.config.common_topN
// 群常识和常识使用同样的topN
};
const filePath = filePathMap[type];
if (!fs3.existsSync(filePath)) {
return "";
}
const channelId = this.config.personal_memory ? session.userId : session.channelId;
const recentMemories = this.getChannelContext(channelId);
const userDialogues = recentMemories.filter((entry) => entry.role === "user").map((entry) => entry.content).join("");
const keywords = userDialogues.split("").filter((word) => !this.charactersToRemove.includes(word));
let entries = await this.loadMemoryFile(filePath);
const matched = this.findBestMatches(entries, keywords).slice(0, topNMap[type] * 5);
if (type === "user") {
const remainingEntries = entries.filter((entry) => !matched.includes(entry));
let updatedEntries = [...remainingEntries, ...matched];
await fs3.promises.writeFile(filePath, JSON.stringify(updatedEntries, null, 2), "utf-8");
}
const result = this.formatMatches(matched, type, topNMap[type]);
return result;
}
// 记忆检索
findBestMatches(entries, keywords) {
return entries.map((entry) => ({ entry, ...this.calculateMatchScore(entry.content, keywords) })).filter(({ count }) => count > 1).sort((a, b) => b.score - a.score).map(({ entry }) => entry);
}
// 匹配度计算
calculateMatchScore(content, keywords) {
if (keywords.length === 0)
return { score: 0, count: 0 };
const regex = new RegExp(`[${this.charactersToRemove.join("")}]`, "g");
content = content.replace(regex, "");
const Keyword = keywords.map((k) => escapeRegExp(k)).join("");
const length = findLongestCommonSubstring(content, Keyword);
const count = countCommonChars(content, Keyword);
const ratio = (length * length + count) / content.length;
return { score: ratio, count };
}
// 格式化匹配结果
formatMatches(matched, type, topN = 5) {
const prefixMap = {
"common": "这是你可能需要知道的信息:",
"user": "以下是较久之前用户说过的话和对话时间:",
"group": "以下是本群聊你可能需要知道的信息:"
};
const time = `时段:${getTimeOfDay((/* @__PURE__ */ new Date()).getHours())}`;
const date = `当前日期和时间:${(/* @__PURE__ */ new Date()).toLocaleString()} ${time}`;
if (matched.length > 0) {
matched = matched.slice(0, topN < matched.length ? topN : matched.length);
if (type === "common") {
const result = `${prefixMap[type]}{
${matched.map((entry) => entry.content).join("\n")}
${date}
}
`;
return result;
} else if (type === "user") {
matched.forEach((entry) => entry.content = entry.content + (entry.role === "user" ? "(未记录时间)" : entry.role));
const result = `${prefixMap[type]}{
${matched.map((entry) => entry.content).join("\n")}
}
`;
return result;
} else if (type === "group") {
const result = `${prefixMap[type]}{
${matched.map((entry) => entry.content).join("\n")}
}
`;
return result;
}
} else {
if (type === "common") {
return "这是你可能用到的信息:{\n" + date + "\n}\n";
} else {
return "";
}
}
}
// 获取用户记忆文件路径
getUserMemoryPath(userId) {
return path3.join(this.config.dataDir, "dialogues", `${userId}.txt`);
}
// 确保记忆文件存在
async ensureMemoryFile(filePath) {
await fs3.promises.mkdir(path3.dirname(filePath), { recursive: true });
if (!fs3.existsSync(filePath)) {
await fs3.promises.writeFile(filePath, "[]", "utf-8");
}
}
// 加载记忆文件
async loadMemoryFile(filePath) {
try {
const content = await fs3.promises.readFile(filePath, "utf-8");
return JSON.parse(content);
} catch {
return [];
}
}
// 获取频道上下文
getChannelContext(channelId) {
return this.channelMemories.get(channelId)?.dialogues || [];
}
};
// src/fixed-dialogues.ts
var fs4 = __toESM(require("fs"));
var path4 = __toESM(require("path"));
async function handleFixedDialogues(ctx, session, user, prompt, config) {
if (!config.enable_fixed_dialogues)
return null;
const filePath = path4.join(config.dataDir, "fixed_dialogues.json");
const dialogues = await loadFixedDialogues(filePath);
const currentTime = parseTime(session.timestamp);
const matched = dialogues.filter((dialogue) => matchDialogue(dialogue, prompt, user, currentTime));
if (matched.length === 0)
return null;
const selected = selectDialogueByProbability(matched);
await processFavorability(ctx, user, selected);
return selected.response;
}
__name(handleFixedDialogues, "handleFixedDialogues");
async function ensureFixedDialoguesFile(filePath) {
if (!fs4.existsSync(filePath)) {
const defaultDialogues = [
{
triggers: ["你好", "您好"],
favorabilityRange: [0, 100],
probability: 1,
timeRange: ["06:00", "08:00"],
response: "早上好!很高兴见到你。"
},
{
triggers: ["再见", "拜拜"],
favorabilityRange: [0, 100],
probability: 1,
timeRange: ["18:00", "20:00"],
response: "再见!希望很快再见到你。"
}
];
fs4.writeFileSync(filePath, JSON.stringify(defaultDialogues, null, 2));
}
}
__name(ensureFixedDialoguesFile, "ensureFixedDialoguesFile");
async function loadFixedDialogues(filePath) {
await ensureFixedDialoguesFile(filePath);
try {
return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
} catch (error) {
console.error("Error loading fixed dialogues:", error);
return [];
}
}
__name(loadFixedDialogues, "loadFixedDialogues");
function matchDialogue(dialogue, content, user, currentTime) {
const triggerMatch = dialogue.triggers.some((t) => content === t);
const favorabilityMatch = matchFavorability(dialogue, user);
const timeMatch = matchTimeRange(dialogue, currentTime);
return triggerMatch && favorabilityMatch && timeMatch;
}
__name(matchDialogue, "matchDialogue");
function matchFavorability(dialogue, user) {
if (!dialogue.favorabilityRange)
return true;
const [min, max] = dialogue.favorabilityRange;
return user.favorability >= min && user.favorability <= max;
}
__name(matchFavorability, "matchFavorability");
function matchTimeRange(dialogue, currentTime) {
if (!dialogue.timeRange)
return true;
const [start, end] = dialogue.timeRange.map(parseTimeToMinutes);
return currentTime >= start && currentTime <= end;
}
__name(matchTimeRange, "matchTimeRange");
function selectDialogueByProbability(dialogues) {
const total = dialogues.reduce((sum, d) => sum + d.probability, 0);
let random = Math.random() * total;
for (const dialogue of dialogues) {
random -= dialogue.probability;
if (random <= 0)
return dialogue;
}
return dialogues[0];
}
__name(selectDialogueByProbability, "selectDialogueByProbability");
async function processFavorability(ctx, user, dialogue) {
if (dialogue.favorability) {
await updateFavorability(ctx, user, dialogue.favorability);
}
}
__name(processFavorability, "processFavorability");
// src/favorability.ts
var fs5 = __toESM(require("fs"));
var path5 = __toESM(require("path"));
async function handleFavorabilitySystem(ctx, session, config) {
const user = await ensureUserExists(ctx, session.userId, session.username);
const level = getFavorabilityLevel(user, config);
if (user.favorability < config.favorability_div_1 - 20 && user.favorability > -900 && level !== "夫妻") {
return session.text("commands.sat.messages.block1");
}
const processedPrompt = processPrompt(session.content);
const englishCount = (processedPrompt.match(/[a-zA-Z]/g) || []).length;
if (user.favorability < 50 && englishCount > 8 && level !== "夫妻") {
return session.text("commands.sat.messages.tooManyEnglishLetters");
}
if (englishCount > 30 && level !== "夫妻") {
return session.text("commands.sat.messages.tooManyEnglishLetters");
}
if (englishCount > 60) {
return session.text("commands.sat.messages.tooManyEnglishLetters");
}
return;
}
__name(handleFavorabilitySystem, "handleFavorabilitySystem");
function getFavorabilityLevel(user, config) {
if (user.favorability >= config.favorability_div_3 && user?.items["镇定贴"]?.count > 0 && user?.items["镇定贴"]?.description && user?.items["镇定贴"]?.description == "on")
return "朋友";
if (user?.items["订婚戒指"]?.count > 0 && user?.items["订婚戒指"]?.description && user?.items["订婚戒指"]?.description == "已使用")
return "夫妻";
if (user.favorability < config.favorability_div_1)
return "厌恶";
if (user.favorability < config.favorability_div_2)
return "陌生";
if (user.favorability < config.favorability_div_3)
return "朋友";
if (user.favorability < config.favorability_div_4)
return "暧昧";
return "恋人";
}
__name(getFavorabilityLevel, "getFavorabilityLevel");
function generateLevelPrompt(level, config, user) {
const prompts = {
"厌恶": config.prompt_0,
"陌生": config.prompt_1,
"朋友": config.prompt_2,
"暧昧": config.prompt_3,
"恋人": config.prompt_4,
"夫妻": config.prompt_5
};
return `${prompts[level]}
`;
}
__name(generateLevelPrompt, "generateLevelPrompt");
function generateAuxiliaryPrompt(prompt, responseContent, user, config) {
const messages = [];
messages.push({
role: "system",
content: "请你评价我之后给你的对话,你需要从回答者的角度,猜测回答者听到此问题和做出此回答的感受好坏,然后返回打分。你需要谨慎判断回答者是在警告还是在调情。你返回的值应当是从0到9之间的一个数字,数字越大代表感受越幸福,数字越小代表感受越恶心。你只需要返回一个数字,不要补充其他内容"
});
messages.push({
role: "user",
content: `问题:${prompt},回答:${responseContent}`
});
return messages;
}
__name(generateAuxiliaryPrompt, "generateAuxiliaryPrompt");
async function handleAuxiliaryResult(ctx, session, config, responseContent) {
const user = await ensureUserExists(ctx, session.userId, session.username);
const regex = /\d+/g;
const value = parseInt(responseContent.match(regex)[0]) ? parseInt(responseContent.match(regex)[0]) : 5;
let favorabilityEffect = value - config.offset_of_fafavorability;
await applyFavorabilityEffect(ctx, user, favorabilityEffect ? favorabilityEffect : 0, session);
if (favorabilityEffect < 0) {
return "(好感↓)";
}
if (favorabilityEffect > 0) {
return "(好感↑)";
}
return;
}
__name(handleAuxiliaryResult, "handleAuxiliaryResult");
async function inputContentCheck(ctx, content, userid, config, session, moodManager) {
const user = await getUser(ctx, userid);
if (!user)
return 0;
const regex = /\*\*/g;
const hasCensor = regex.test(content);
if (hasCensor && config.input_censor_favorability) {
moodManager.handleInputMoodChange(user, getFavorabilityLevel(user, config));
await applyFavorabilityEffect(ctx, user, -1 * config.value_of_input_favorability, session);
return -1 * config.value_of_input_favorability;
}
moodManager.applyMoodChange(user, 1);
if (config.enable_auxiliary_LLM || user.usage > config.max_favorability_perday)
return 0;
const mood = moodManager.getMoodValue(user.userid);
if (mood >= 0)
await applyFavorabilityEffect(ctx, user, 1, session);
return 1;
}
__name(inputContentCheck, "inputContentCheck");
async function outputContentCheck(ctx, response, userid, config, session, moodManager) {
if (response.error)
return 0;
const user = await getUser(ctx, userid);
if (!user)
return 0;
if (config.output_censor_favorability) {
const content = response.content;
const filePath = path5.join(config.dataDir, "output_censor.txt");
if (!fs5.existsSync(filePath)) {
return 0;
}
const censorWords = fs5.readFileSync(filePath, "utf-8").split(",");
const censoredContent = censorWords.reduce((acc, cur) => acc.replace(cur, "**"), content);
const regex = /\*\*/g;
const hasCensor = regex.test(censoredContent);
const moodLevel = moodManager.getMoodLevel(user.userid);
if (hasCensor) {
const mood = moodManager.getMoodValue(user.userid);
moodManager.handleOutputMoodChange(user, getFavorabilityLevel(user, config));
if (mood <= 0) {
await updateFavorability(ctx, user, -1 * config.value_of_output_favorability);
return -config.value_of_output_favorability;
}
}
if (moodLevel === "angry") {
await updateFavorability(ctx, user, -1 * config.value_of_output_favorability);
return -config.value_of_output_favorability;
}
}
return 0;
}
__name(outputContentCheck, "outputContentCheck");
async function ensureCensorFileExists(basePath) {
const filePath = path5.join(basePath, "output_censor.txt");
fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
if (!fs5.existsSync(filePath)) {
fs5.writeFileSync(filePath, "示例屏蔽词1,示例屏蔽词2,示例屏蔽词3", "utf-8");
}
}
__name(ensureCensorFileExists, "ensureCensorFileExists");
async function applyFavorabilityEffect(ctx, user, effect, session) {
if (effect < 0 && user.items["谷底小石"]?.count > 0) {
session.send(session.text("commands.sat.messages.rockBottom"));
return;
}
if (effect < 0 && user.items["帽子先生"]?.count > 0) {
user.items["帽子先生"].count--;
if (user.items["帽子先生"].count === 0)
delete user.items["帽子先生"];
await updateUserItems(ctx, user);
session.send(session.text("commands.sat.messages.hatMan"));
return;
}
await updateFavorability(ctx, user, effect);
}
__name(applyFavorabilityEffect, "applyFavorabilityEffect");
// src/middleware.ts
var import_koishi5 = require("koishi");
var logger5 = new import_koishi5.Logger("satori-ai-middleware");
function createMiddleware(ctx, sat, config) {
return async (session, next) => {
if (config.enable_favorability && config.enable_warning && session.channelId === config.warning_group)
sat.getWarningList(session);
await sat.broadcastManager.seedBroadcast(session);
if (config.private && isPrivateSession(session)) {
return await handlePrivateMessage(sat, session);
}
if (config.nick_name && await hasNickName(ctx, session, config)) {
return await handleNickNameMessage(sat, session);
}
if (shouldRandomTrigger(session, config)) {
return await sat.handleRandomMiddleware(session);
}
if (!isSpecialMessage(session))
await sat.handleChannelMemoryManager(session);
return next();
};
}
__name(createMiddleware, "createMiddleware");
function isPrivateSession(session) {
if (isSpecialMessage(session))
return false;
return session.subtype === "private" || session.channelId.includes("private");
}
__name(isPrivateSession, "isPrivateSession");
async function handlePrivateMessage(SAT2, session) {
const content = session.content.trim();
if (content)
return await SAT2.handleNickNameMiddleware(session, content);
}
__name(handlePrivateMessage, "handlePrivateMessage");
async function hasNickName(ctx, session, config) {
if (session.userId === session.selfId)
return false;
if (config.nick_name_block_words.some((word) => session.content.includes(word)))
return false;
const user = await ensureUserExists(ctx, session.userId, session.username);
let names = config.nick_name_list;
if (user?.items?.["情侣合照"]?.metadata?.botNickName) {
names = names.concat(user.items["情侣合照"].metadata.botNickName);
}
return names.some((name) => session.content.inc