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 __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], 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/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply6, inject: () => inject, inject2: () => inject2, name: () => name }); module.exports = __toCommonJS(src_exports); var import_koishi7 = require("koishi"); // src/plugins/chat.ts var import_messages = require("@langchain/core/messages"); var import_koishi2 = require("koishi"); var import_count_tokens = require("koishi-plugin-chatluna/llm-core/utils/count_tokens"); // src/utils.ts var import_koishi = require("koishi"); var import_marked = require("marked"); var import_he = __toESM(require("he"), 1); function isEmoticonStatement(text, elements) { if (elements.length === 1 && elements[0].attrs["span"]) { return "span"; } const regex = /^[\p{P}\p{S}\p{Z}\p{M}\p{N}\p{L}\s]*\p{So}[\p{P}\p{S}\p{Z}\p{M}\p{N}\p{L}\s]*$/u; return regex.test(text) ? "emoji" : "text"; } __name(isEmoticonStatement, "isEmoticonStatement"); function isOnlyPunctuation(text) { const regex = /^[.,;!?…·—–—()【】「」『』《》<>《》{}【】〔〕“”‘’'"\[\]@#¥%\^&\*\-+=|\\~?。`]+$/; return regex.test(text); } __name(isOnlyPunctuation, "isOnlyPunctuation"); function parseMessageContent(response) { let rawMessage = response.match( /\s*(.*?)\s*<\/message_part>/s )?.[1]; const status = response.match(/(.*?)<\/status>/s)?.[1]; if (rawMessage == null) { rawMessage = response.match(//)?.[0]; } if (rawMessage == null) { throw new Error("Failed to parse response: " + response); } const tempJson = parseXmlToObject(rawMessage); return { rawMessage: tempJson.content, messageType: tempJson.type, status, sticker: tempJson.sticker }; } __name(parseMessageContent, "parseMessageContent"); function processElements(elements) { const resultElements = []; const forEachElement = /* @__PURE__ */ __name((elements2) => { for (let i = 0; i < elements2.length; i++) { const element = elements2[i]; if (element.type === "text") { if (element.attrs["code"] || element.attrs["span"]) { resultElements.push([element]); continue; } const matchArray = splitSentence( import_he.default.decode(element.attrs.content) ).filter((x) => x.length > 0); for (const match of matchArray) { resultElements.push([import_koishi.h.text(match)]); } } else if (["em", "strong", "del", "p"].includes(element.type)) { forEachElement(element.children); } else { resultElements.push([element]); } } }, "forEachElement"); forEachElement(elements); return resultElements; } __name(processElements, "processElements"); function processTextMatches(rawMessage, useAt = true) { const currentElements = []; let parsedMessage = ""; const matches = [ ...matchAt(rawMessage).map((m) => ({ type: "at", content: m.at, start: m.start, end: m.end })), ...matchPre(rawMessage).map((m) => ({ type: "pre", content: m.pre, start: m.start, end: m.end })) ].sort((a, b) => a.start - b.start); if (matches.length === 0) { parsedMessage = rawMessage; currentElements.push(...transform(rawMessage)); return { currentElements, parsedMessage }; } let lastIndex = 0; for (const match of matches) { const before = rawMessage.substring(lastIndex, match.start); if (before.length > 0) { parsedMessage += before; currentElements.push(...transform(before)); } if (match.type === "at") { if (useAt) { currentElements.push(import_koishi.h.at(match.content)); } } else { parsedMessage += match.content; currentElements.push( (0, import_koishi.h)("text", { span: true, content: match.content }) ); } lastIndex = match.end; } const after = rawMessage.substring(lastIndex); if (after.length > 0) { parsedMessage += after; currentElements.push(...transform(after)); } return { currentElements, parsedMessage }; } __name(processTextMatches, "processTextMatches"); function parseResponse(response, useAt = true) { try { const { rawMessage, messageType, status, sticker } = parseMessageContent(response); const { currentElements, parsedMessage } = processTextMatches( rawMessage, useAt ); const resultElements = processElements(currentElements); if (resultElements[0]?.[0]?.type === "at" && resultElements.length > 1) { resultElements[1].unshift(import_koishi.h.text(" ")); resultElements[1].unshift(resultElements[0][0]); resultElements.shift(); } return { elements: resultElements, rawMessage: parsedMessage, status, sticker, messageType }; } catch (e) { logger.error(e); throw new Error("Failed to parse response: " + response); } } __name(parseResponse, "parseResponse"); function splitSentence(text) { if (isOnlyPunctuation(text)) { return [text]; } const result = []; const lines = text.split("\n").filter((line) => line.trim().length > 0).join(" "); const state = { bracket: 0, text: 0 }; let current = ""; const punctuations = [ ",", "。", "?", "!", ";", ":", ",", "?", "!", ";", ":", "、", "~", "—", "\r" ]; const retainPunctuations = ["?", "!", "?", "!", "~"]; const mustPunctuations = ["。", "?", "!", "?", "!", ":", ":"]; const brackets = ["【", "】", "《", "》", "(", ")", "(", ")"]; for (let index = 0; index < lines.length; index++) { const char = lines[index]; const nextChar = lines?.[index + 1]; const indexOfBrackets = brackets.indexOf(char); if (indexOfBrackets > -1) { state.bracket += indexOfBrackets % 2 === 0 ? 1 : -1; } if (indexOfBrackets > -1 && state.bracket === 0 && state.text > 0) { current += char; result.push(current); state.text = 0; current = ""; continue; } else if (indexOfBrackets % 2 === 0 && state.bracket === 1) { result.push(current); state.text = 0; current = char; continue; } else if (state.bracket > 0) { current += char; state.text++; continue; } if (!punctuations.includes(char)) { current += char; continue; } if (retainPunctuations.includes(char)) { current += char; } if (retainPunctuations.indexOf(nextChar) % 2 === 0 && retainPunctuations.indexOf(char) % 2 === 1) { index += 1; } if (current.length < 1) { continue; } if (current.length > 2 || mustPunctuations.includes(char)) { result.push(current); current = ""; } else if (!retainPunctuations.includes(char)) { current += char; } } if (current.length > 0) { result.push(current); } return result.filter((item) => punctuations.indexOf(item) === -1); } __name(splitSentence, "splitSentence"); function matchAt(str) { const atRegex = /]*>(.*?)<\/at>/gs; return [...str.matchAll(atRegex)].map((item) => { return { at: item[1], start: item.index, end: item.index + item[0].length }; }); } __name(matchAt, "matchAt"); function matchPre(str) { const preRegex = /
(.*?)<\/pre>/gs;
  return [...str.matchAll(preRegex)].map((item) => {
    return {
      pre: item[1],
      start: item.index,
      end: item.index + item[0].length
    };
  });
}
__name(matchPre, "matchPre");
async function formatMessage(messages, config, model, systemPrompt, historyPrompt) {
  const maxTokens = config.maxTokens - 300;
  let currentTokens = 0;
  currentTokens += await model.getNumTokens(systemPrompt);
  currentTokens += await model.getNumTokens(historyPrompt);
  const calculatedMessages = [];
  for (let i = messages.length - 1; i >= 0; i--) {
    const message = messages[i];
    const xmlMessage = `${message.content}`;
    const xmlMessageToken = await model.getNumTokens(xmlMessage);
    if (currentTokens + xmlMessageToken > maxTokens - 4) {
      break;
    }
    currentTokens += xmlMessageToken;
    calculatedMessages.unshift(xmlMessage);
  }
  const lastMessage = calculatedMessages.pop();
  if (lastMessage === void 0) {
    throw new Error(
      "lastMessage is undefined, please set the max token to be bigger"
    );
  }
  return [calculatedMessages, lastMessage];
}
__name(formatMessage, "formatMessage");
async function formatCompletionMessages(messages, humanMessage, config, model) {
  const maxTokens = config.maxTokens - 600;
  const systemMessage = messages.shift();
  let currentTokens = 0;
  currentTokens += await model.getNumTokens(systemMessage.content);
  currentTokens += await model.getNumTokens(humanMessage.content);
  const result = [];
  result.unshift(humanMessage);
  for (let index = messages.length - 1; index >= 0; index--) {
    const message = messages[index];
    const messageTokens = await model.getNumTokens(
      message.content
    );
    if (currentTokens + messageTokens > maxTokens) {
      break;
    }
    currentTokens += messageTokens;
    result.unshift(message);
  }
  logger.debug(`maxTokens: ${maxTokens}, currentTokens: ${currentTokens}`);
  result.unshift(systemMessage);
  return result;
}
__name(formatCompletionMessages, "formatCompletionMessages");
function parseXmlToObject(xml) {
  const messageRegex = /(.*?)<\/message>/s;
  const match = xml.match(messageRegex);
  if (!match) {
    throw new Error("Failed to parse response: " + xml);
  }
  const [, attributes, content] = match;
  const getAttr = /* @__PURE__ */ __name((name3) => {
    const attrRegex = new RegExp(`${name3}=['"]?([^'"]+)['"]?`);
    const attrMatch = attributes.match(attrRegex);
    if (!attrMatch) {
      logger.warn(`Failed to parse ${name3} attribute: ${xml}`);
      return "";
    }
    return attrMatch[1];
  }, "getAttr");
  const name2 = getAttr("name");
  const id = getAttr("id");
  const type = getAttr("type") || "text";
  const sticker = getAttr("sticker");
  if (content === void 0) {
    throw new Error("Failed to parse content: " + xml);
  }
  return { name: name2, id, type, sticker, content };
}
__name(parseXmlToObject, "parseXmlToObject");
var tagRegExp = /<(\/?)([^!\s>/]+)([^>]*?)\s*(\/?)>/;
function renderToken(token) {
  if (token.type === "code") {
    return (0, import_koishi.h)("text", { code: true, content: token.text + "\n" });
  } else if (token.type === "paragraph") {
    return (0, import_koishi.h)("p", render(token.tokens));
  } else if (token.type === "image") {
    return import_koishi.h.image(token.href);
  } else if (token.type === "blockquote") {
    return (0, import_koishi.h)("text", { content: token.text + "\n" });
  } else if (token.type === "text") {
    return (0, import_koishi.h)("text", { content: token.text });
  } else if (token.type === "em") {
    return (0, import_koishi.h)("em", render(token.tokens));
  } else if (token.type === "strong") {
    return (0, import_koishi.h)("strong", render(token.tokens));
  } else if (token.type === "del") {
    return (0, import_koishi.h)("del", render(token.tokens));
  } else if (token.type === "link") {
    return (0, import_koishi.h)("a", { href: token.href }, render(token.tokens));
  } else if (token.type === "html") {
    const cap = tagRegExp.exec(token.text);
    if (!cap) {
      return (0, import_koishi.h)("text", { content: token.text });
    }
    if (cap[2] === "img") {
      if (cap[1]) return;
      const src = cap[3].match(/src="([^"]+)"/);
      if (src) return import_koishi.h.image(src[1]);
    }
  }
  return (0, import_koishi.h)("text", { content: token.raw });
}
__name(renderToken, "renderToken");
function render(tokens) {
  return tokens.map(renderToken).filter(Boolean);
}
__name(render, "render");
function transform(source, ...args) {
  if (!source) return [];
  if (Array.isArray(source)) {
    source = args.map((arg, index) => source[index] + arg).join("") + source[args.length];
  }
  return render(import_marked.marked.lexer(source));
}
__name(transform, "transform");
var logger;
function setLogger(setLogger2) {
  logger = setLogger2;
}
__name(setLogger, "setLogger");

// src/plugins/chat.ts
async function apply(ctx, config) {
  const service = ctx.chatluna_character;
  const preset = service.preset;
  const stickerService = service.stickerService;
  logger2 = service.logger;
  setLogger(logger2);
  const modelPool = {};
  const [platform, modelName] = (0, import_count_tokens.parseRawModelName)(config.model);
  await ctx.chatluna.awaitLoadPlatform(platform);
  const globalModel = await ctx.chatluna.createChatModel(
    platform,
    modelName
  );
  logger2.info("global model loaded %c", config.model);
  if (config.modelOverride?.length > 0) {
    for (const override of config.modelOverride) {
      modelPool[override.groupId] = (async () => {
        const [platform2, modelName2] = (0, import_count_tokens.parseRawModelName)(override.model);
        await ctx.chatluna.awaitLoadPlatform(platform2);
        const loadedModel = await ctx.chatluna.createChatModel(
          platform2,
          modelName2
        );
        logger2.info(
          "override model loaded %c for group %c",
          override.model,
          override.groupId
        );
        modelPool[override.groupId] = Promise.resolve(loadedModel);
        return loadedModel;
      })();
    }
  }
  let globalPreset = preset.getPresetForCache(config.defaultPreset);
  let presetPool = {};
  ctx.on("chatluna_character/preset_updated", () => {
    globalPreset = preset.getPresetForCache(config.defaultPreset);
    presetPool = {};
  });
  service.collect(async (session, messages) => {
    const guildId = session.event.guild?.id ?? session.guildId;
    const model = await (modelPool[guildId] ?? Promise.resolve(globalModel));
    const currentGuildConfig = config.configs[guildId];
    let copyOfConfig = Object.assign({}, config);
    let currentPreset = globalPreset;
    if (currentGuildConfig != null) {
      copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
      currentPreset = presetPool[guildId] ?? await (async () => {
        const template = preset.getPresetForCache(
          currentGuildConfig.preset
        );
        presetPool[guildId] = template;
        return template;
      })();
    }
    const [recentMessage, lastMessage] = await formatMessage(
      messages,
      copyOfConfig,
      model,
      currentPreset.system.template,
      currentPreset.system.template
    );
    const temp = await service.getTemp(session);
    const formattedSystemPrompt = await currentPreset.system.format({
      time: (/* @__PURE__ */ new Date()).toLocaleString(),
      status: temp.status ?? currentPreset.status ?? "",
      stickers: JSON.stringify(stickerService.getAllStickTypes())
    });
    logger2.debug("messages_new: " + JSON.stringify(recentMessage));
    logger2.debug("messages_last: " + JSON.stringify(lastMessage));
    const humanMessage = new import_messages.HumanMessage(
      await currentPreset.input.format({
        history_new: recentMessage,
        history_last: lastMessage,
        time: (/* @__PURE__ */ new Date()).toLocaleString(),
        stickers: JSON.stringify(stickerService.getAllStickTypes()),
        status: temp.status ?? currentPreset.status ?? ""
      })
    );
    const completionMessages = await formatCompletionMessages(
      [new import_messages.SystemMessage(formattedSystemPrompt)].concat(
        temp.completionMessages
      ),
      humanMessage,
      copyOfConfig,
      model
    );
    logger2.debug(
      "completion message: " + JSON.stringify(completionMessages.map((it) => it.content))
    );
    let responseMessage;
    let parsedResponse;
    let retryCount = 0;
    while (retryCount < 3) {
      retryCount++;
      try {
        responseMessage = await model.invoke(completionMessages);
      } catch (e) {
        logger2.error("model requests failed", e);
        retryCount = 3;
        break;
      }
      try {
        logger2.debug("model response: " + responseMessage.content);
        parsedResponse = parseResponse(
          responseMessage.content,
          copyOfConfig.isAt
        );
        break;
      } catch (e) {
        await (0, import_koishi2.sleep)(3e3);
      }
    }
    if (retryCount >= 3) {
      return;
    }
    temp.status = parsedResponse.status;
    if (parsedResponse.elements.length < 1) {
      service.mute(session, copyOfConfig.muteTime);
      return;
    }
    temp.completionMessages.push(humanMessage, responseMessage);
    if (temp.completionMessages.length > 5) {
      temp.completionMessages.length = 0;
    }
    const random = new import_koishi2.Random();
    for (const elements of parsedResponse.elements) {
      const text = elements.map((element) => element.attrs.content ?? "").join("");
      const emoticonStatement = isEmoticonStatement(text, elements);
      if (elements.length < 1) {
        continue;
      }
      let maxTime = text.length * copyOfConfig.typingTime + 100;
      if (elements.length === 1 && elements[0].attrs["code"] === true) {
        maxTime = maxTime * 0.1;
      }
      if (parsedResponse.messageType === "voice" && emoticonStatement !== "text") {
        continue;
      }
      if (config.splitVoice !== true && parsedResponse.messageType === "voice") {
        maxTime = parsedResponse.rawMessage.length * copyOfConfig.typingTime + 100;
        await (0, import_koishi2.sleep)(random.int(maxTime / 4, maxTime / 2));
        try {
          await session.send(
            await ctx.vits.say({ input: parsedResponse.rawMessage })
          );
        } catch (e) {
          logger2.error(e);
          await session.send(elements);
        }
        continue;
      }
      try {
        if (emoticonStatement !== "span") {
          await (0, import_koishi2.sleep)(random.int(maxTime / 2, maxTime));
        } else {
          await (0, import_koishi2.sleep)(random.int(maxTime / 12, maxTime / 4));
        }
        switch (parsedResponse.messageType) {
          case "text":
            await session.send(elements);
            break;
          case "voice":
            await session.send(
              await ctx.vits.say({
                input: text
              })
            );
            break;
          default:
            await session.send(elements);
            break;
        }
      } catch (e) {
        logger2.error(e);
        await session.send(elements);
      }
    }
    const randomNumber = Math.random();
    if (randomNumber < config.sendStickerProbability) {
      const sticker = await stickerService.randomStickByType(
        parsedResponse.sticker
      );
      await (0, import_koishi2.sleep)(random.int(500, 2e3));
      await session.send(sticker);
    }
    service.mute(session, copyOfConfig.coolDownTime * 1e3);
    await service.broadcastOnBot(session, parsedResponse.elements.flat());
  });
}
__name(apply, "apply");
var logger2;

// src/plugins/filter.ts
var groupInfos = {};
async function apply2(ctx, config) {
  const maxMessages = config.messageInterval;
  const service = ctx.chatluna_character;
  const preset = service.preset;
  const logger3 = service.logger;
  const globalPreset = await preset.getPreset(config.defaultPreset);
  const presetPool = {};
  service.addFilter((session, message) => {
    const guildId = session.guildId;
    const info = groupInfos[guildId] || {
      messageCount: 0,
      messageSendProbability: 1
    };
    const currentGuildConfig = config.configs[guildId];
    let copyOfConfig = Object.assign({}, config);
    let currentPreset = globalPreset;
    if (currentGuildConfig != null) {
      copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
      currentPreset = presetPool[guildId] ?? (() => {
        const template = preset.getPresetForCache(
          currentGuildConfig.preset
        );
        presetPool[guildId] = template;
        return template;
      })();
    }
    let { messageCount, messageSendProbability } = info;
    logger3.debug(
      `messageCount: ${messageCount}, messageSendProbability: ${messageSendProbability}. content: ${JSON.stringify(
        message
      )}`
    );
    if (copyOfConfig.disableChatLuna && copyOfConfig.whiteListDisableChatLuna.includes(guildId)) {
      const selfId = session.bot.userId ?? session.bot.selfId ?? "0";
      const guildMessages = ctx.chatluna_character.getMessages(guildId);
      let maxRecentMessage = 0;
      if (guildMessages == null || guildMessages.length === 0) {
        maxRecentMessage = 6;
      }
      while (maxRecentMessage < 5) {
        const currentMessage = guildMessages[guildMessages?.length - 1 - maxRecentMessage];
        if (currentMessage == null) {
          return false;
        }
        if (currentMessage.id === selfId) {
          break;
        }
        maxRecentMessage++;
      }
    }
    let isAt = session.stripped.appel;
    for (const element of session.elements) {
      if (element.type === "at" && element.attrs.id === session.bot.userId) {
        isAt = true;
        break;
      }
    }
    if (copyOfConfig.isForceMute && isAt && currentPreset.mute_keyword?.length > 0) {
      const needMute = currentPreset.mute_keyword.some(
        (value) => message.content.includes(value)
      );
      if (needMute) {
        logger3.debug(`mute content: ${message.content}`);
        service.mute(session, config.muteTime);
      }
    }
    const isMute = service.isMute(session);
    if ((messageCount > maxMessages || messageSendProbability > 1 || session.stripped.appel || config.isNickname && currentPreset.nick_name.some(
      (value) => message.content.startsWith(value)
    )) && !isMute) {
      info.messageCount = 0;
      info.messageSendProbability = 1;
      groupInfos[session.guildId] = info;
      return true;
    }
    if (Math.random() > messageSendProbability && !isMute) {
      info.messageCount = 0;
      info.messageSendProbability = 1;
      groupInfos[session.guildId] = info;
      return true;
    }
    messageCount++;
    messageSendProbability -= 1 / maxMessages * copyOfConfig.messageProbability;
    info.messageCount = messageCount;
    info.messageSendProbability = messageSendProbability;
    groupInfos[session.guildId] = info;
    return false;
  });
}
__name(apply2, "apply");

// src/plugins/commands.ts
function apply3(ctx, config) {
  ctx.command("chatluna.character", "角色扮演相关命令");
  ctx.command(
    "chatluna.character.clear [group]",
    "清除群组的聊天记录"
  ).action(async ({ session }, group) => {
    if (!session.isDirect) {
      return;
    }
    const groupId = group ?? session.guildId;
    if (!groupId) {
      return "请检查你是否提供了群组 id";
    }
    const groupInfo = groupInfos[groupId];
    if (!groupInfo) {
      return "没有找到群组信息";
    }
    groupInfo.messageCount = 0;
    groupInfo.messageSendProbability = 0;
    ctx.chatluna_character.clear(groupId);
    return `已清除群组 ${groupId} 的聊天记录`;
  });
}
__name(apply3, "apply");

// src/plugins/config.ts
var import_koishi3 = require("koishi");
var import_types = require("koishi-plugin-chatluna/llm-core/platform/types");
async function apply4(ctx, config) {
  ctx.on("chatluna/model-added", (service) => {
    ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(service)));
  });
  ctx.on("chatluna/model-removed", (service) => {
    ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(service)));
  });
  ctx.schema.set("model", import_koishi3.Schema.union(getModelNames(ctx.chatluna.platform)));
}
__name(apply4, "apply");
function getModelNames(service) {
  return service.getAllModels(import_types.ModelType.llm).map((m) => import_koishi3.Schema.const(m));
}
__name(getModelNames, "getModelNames");

// src/plugins/interception.ts
function apply5(ctx, config) {
  ctx.on("chatluna/before-check-sender", async (session) => {
    const guildId = session.guildId;
    if (!config.applyGroup.includes(guildId) || session.isDirect || !session.stripped.appel) {
      return false;
    }
    if (config.disableChatLuna && config.whiteListDisableChatLuna?.includes(guildId)) {
      const selfId = session.bot.userId ?? session.bot.selfId ?? "0";
      const guildMessages = ctx.chatluna_character.getMessages(guildId);
      if (guildMessages == null || guildMessages.length === 0) {
        return true;
      }
      let maxRecentMessage = 0;
      while (maxRecentMessage < 3) {
        const currentMessage = guildMessages[guildMessages?.length - 1 - maxRecentMessage];
        if (currentMessage == null) {
          return false;
        }
        if (currentMessage.id === selfId) {
          return true;
        }
        maxRecentMessage++;
      }
    }
    return config.disableChatLuna;
  });
}
__name(apply5, "apply");

// src/plugin.ts
async function plugins(ctx, parent) {
  const middlewares = (
    // middleware start
    [apply, apply3, apply4, apply2, apply5]
  );
  for (const middleware of middlewares) {
    await middleware(ctx, parent);
  }
}
__name(plugins, "plugins");

// src/service/message.ts
var import_events = __toESM(require("events"), 1);
var import_koishi6 = require("koishi");
var import_logger = require("koishi-plugin-chatluna/utils/logger");

// src/preset.ts
var import_prompts = require("@langchain/core/prompts");
var import_promises = __toESM(require("fs/promises"), 1);
var import_js_yaml = require("js-yaml");
var import_koishi4 = require("koishi");
var import_error = require("koishi-plugin-chatluna/utils/error");
var import_path = __toESM(require("path"), 1);
var import_url = require("url");
var import_fs = require("fs");
var import_meta = {};
var Preset = class {
  constructor(ctx) {
    this.ctx = ctx;
    ctx.on("dispose", () => {
      this._aborter?.abort();
    });
  }
  static {
    __name(this, "Preset");
  }
  _presets = [];
  _aborter = null;
  async loadAllPreset() {
    await this._checkPresetDir();
    const presetDir = this.resolvePresetDir();
    const files = await import_promises.default.readdir(presetDir);
    this._presets.length = 0;
    for (const file of files) {
      const extension = import_path.default.extname(file);
      if (extension !== ".yml") {
        continue;
      }
      const rawText = await import_promises.default.readFile(
        import_path.default.join(presetDir, file),
        "utf-8"
      );
      const preset = loadPreset(rawText);
      preset.path = import_path.default.join(presetDir, file);
      this._presets.push(preset);
    }
    this.ctx.schema.set(
      "character-preset",
      import_koishi4.Schema.union(
        this._presets.map((preset) => preset.name).concat("无").map((name2) => import_koishi4.Schema.const(name2))
      )
    );
    this.ctx.emit("chatluna_character/preset_updated");
  }
  async getPreset(triggerKeyword, loadForDisk = true, throwError = true) {
    if (loadForDisk) {
      await this.loadAllPreset();
    }
    return this.getPresetForCache(triggerKeyword, throwError);
  }
  watchPreset() {
    let fsWait = false;
    if (this._aborter != null) {
      this._aborter.abort();
    }
    this._aborter = new AbortController();
    (0, import_fs.watch)(
      this.resolvePresetDir(),
      {
        signal: this._aborter.signal
      },
      async (event, filename) => {
        if (filename) {
          if (fsWait) return;
          fsWait = setTimeout(() => {
            fsWait = false;
          }, 100);
          await this.loadAllPreset();
          this.ctx.chatluna_character.logger.debug(
            `trigger full reload preset by ${filename}`
          );
          return;
        }
        await this.loadAllPreset();
        this.ctx.chatluna_character.logger.debug(
          `trigger full reload preset`
        );
      }
    );
  }
  async init() {
    await this.loadAllPreset();
    this.watchPreset();
  }
  getPresetForCache(triggerKeyword, throwError = true) {
    const preset = this._presets.find(
      (preset2) => preset2.name === triggerKeyword
    );
    if (preset) {
      return preset;
    }
    if (throwError) {
      throw new import_error.ChatLunaError(
        import_error.ChatLunaErrorCode.PREST_NOT_FOUND,
        new Error(`No preset found for keyword ${triggerKeyword}`)
      );
    }
    return void 0;
  }
  async getDefaultPreset() {
    if (this._presets.length === 0) {
      await this.loadAllPreset();
    }
    const preset = this._presets.find((preset2) => preset2.name === "默认");
    if (preset) {
      return preset;
    } else {
      await this._copyDefaultPresets();
      return this.getDefaultPreset();
    }
  }
  async getAllPreset() {
    await this.loadAllPreset();
    return this._presets.map((preset) => preset.name);
  }
  async resetDefaultPreset() {
    await this._copyDefaultPresets();
  }
  resolvePresetDir() {
    return import_path.default.resolve(this.ctx.baseDir, "data/chathub/character/presets");
  }
  async _checkPresetDir() {
    const presetDir = import_path.default.join(this.resolvePresetDir());
    try {
      await import_promises.default.access(presetDir);
    } catch (err) {
      if (err.code === "ENOENT") {
        await import_promises.default.mkdir(presetDir, { recursive: true });
        await this._copyDefaultPresets();
      } else {
        throw err;
      }
    }
  }
  async _copyDefaultPresets() {
    const currentPresetDir = import_path.default.join(this.resolvePresetDir());
    const dirname = __dirname?.length > 0 ? __dirname : (0, import_url.fileURLToPath)(import_meta.url);
    const defaultPresetDir = import_path.default.join(dirname, "../resources/presets");
    const files = await import_promises.default.readdir(defaultPresetDir);
    for (const file of files) {
      const filePath = import_path.default.join(defaultPresetDir, file);
      const fileStat = await import_promises.default.stat(filePath);
      if (fileStat.isFile()) {
        await import_promises.default.mkdir(currentPresetDir, { recursive: true });
        this.ctx.chatluna_character.logger.debug(
          `copy preset file ${filePath} to ${currentPresetDir}`
        );
        await import_promises.default.copyFile(filePath, import_path.default.join(currentPresetDir, file));
      }
    }
  }
};
function loadPreset(text) {
  const rawPreset = (0, import_js_yaml.load)(text);
  return {
    name: rawPreset.name,
    nick_name: rawPreset.nick_name,
    input: import_prompts.PromptTemplate.fromTemplate(rawPreset.input),
    system: import_prompts.PromptTemplate.fromTemplate(rawPreset.system),
    mute_keyword: rawPreset.mute_keyword ?? [],
    status: rawPreset?.status
  };
}
__name(loadPreset, "loadPreset");

// src/service/sticker.ts
var import_koishi5 = require("koishi");
var import_path2 = __toESM(require("path"), 1);
var import_promises2 = __toESM(require("fs/promises"), 1);
var import_url2 = require("url");
var import_meta2 = {};
var StickerService = class {
  constructor(_ctx, _config) {
    this._ctx = _ctx;
    this._config = _config;
  }
  static {
    __name(this, "StickerService");
  }
  _stickers = {};
  async init() {
    const sickerDir = import_path2.default.resolve(
      this._ctx.baseDir,
      "data/chathub/character/sticker"
    );
    try {
      await import_promises2.default.access(sickerDir);
    } catch (error) {
      await import_promises2.default.mkdir(sickerDir, { recursive: true });
      const dirname = __dirname?.length > 0 ? __dirname : (0, import_url2.fileURLToPath)(import_meta2.url);
      await import_promises2.default.cp(
        import_path2.default.resolve(dirname, "../resources/sticker"),
        sickerDir,
        {
          recursive: true
        }
      );
    }
    const dirs = await import_promises2.default.readdir(sickerDir);
    for (const dirName of dirs) {
      const dir = import_path2.default.resolve(sickerDir, dirName);
      const stats = await import_promises2.default.stat(dir);
      if (stats.isDirectory()) {
        const stickers = await import_promises2.default.readdir(dir);
        this._stickers[dirName] = stickers.map(
          (sticker) => import_path2.default.resolve(dir, sticker)
        );
      }
    }
  }
  getAllStickTypes() {
    return Object.keys(this._stickers);
  }
  async randomStickByType(type) {
    const allStickers = this._stickers[type];
    if (!allStickers) {
      return this.randomStick();
    }
    const index = Math.floor(Math.random() * allStickers.length);
    const sticker = allStickers[index];
    if (!sticker) {
      return void 0;
    }
    this._ctx.root.chatluna_character.logger.debug(
      `send sticker: ${sticker}`
    );
    return import_koishi5.h.image(await (0, import_promises2.readFile)(sticker), `image/${getFileType(sticker)}`);
  }
  async randomStick() {
    const allStickers = Object.values(this._stickers).flat();
    const index = Math.floor(Math.random() * allStickers.length);
    const sticker = allStickers[index];
    if (!sticker) {
      return void 0;
    }
    this._ctx.root.chatluna_character.logger.debug(
      `send sticker: ${sticker}`
    );
    return import_koishi5.h.image(await (0, import_promises2.readFile)(sticker), `image/${getFileType(sticker)}`);
  }
};
function getFileType(path3) {
  const type = path3.split(".").pop().toLocaleLowerCase();
  if (type === "jpg") {
    return "jpeg";
  }
  return type;
}
__name(getFileType, "getFileType");

// src/service/message.ts
var MessageCollector = class extends import_koishi6.Service {
  constructor(ctx, _config) {
    super(ctx, "chatluna_character");
    this.ctx = ctx;
    this._config = _config;
    this.stickerService = new StickerService(ctx, _config);
    this.logger = (0, import_logger.createLogger)(ctx, "chatluna-character");
    this.preset = new Preset(ctx);
  }
  static {
    __name(this, "MessageCollector");
  }
  _messages = {};
  _eventEmitter = new import_events.default();
  _filters = [];
  _groupLocks = {};
  _groupTemp = {};
  stickerService;
  preset;
  addFilter(filter) {
    this._filters.push(filter);
  }
  mute(session, time) {
    const lock = this._getGroupLocks(session.guildId);
    let mute = lock.mute ?? 0;
    if (mute < (/* @__PURE__ */ new Date()).getTime()) {
      mute = (/* @__PURE__ */ new Date()).getTime() + time;
    } else {
      mute = mute + time;
    }
    lock.mute = mute;
  }
  collect(func) {
    this._eventEmitter.on("collect", func);
  }
  getMessages(groupId) {
    return this._messages[groupId];
  }
  isMute(session) {
    const lock = this._getGroupLocks(session.guildId);
    return lock.mute > (/* @__PURE__ */ new Date()).getTime();
  }
  async updateTemp(session, temp) {
    await this._lock(session);
    const groupId = session.guildId;
    this._groupTemp[groupId] = temp;
    await this._unlock(session);
  }
  async getTemp(session) {
    await this._lock(session);
    const groupId = session.guildId;
    const temp = this._groupTemp[groupId] ?? {
      completionMessages: []
    };
    this._groupTemp[groupId] = temp;
    await this._unlock(session);
    return temp;
  }
  _getGroupLocks(groupId) {
    if (!this._groupLocks[groupId]) {
      this._groupLocks[groupId] = {
        lock: false,
        mute: 0
      };
    }
    return this._groupLocks[groupId];
  }
  _lock(session) {
    const groupLock = this._getGroupLocks(session.guildId);
    return new Promise((resolve) => {
      const interval = setInterval(() => {
        if (!groupLock.lock) {
          groupLock.lock = true;
          clearInterval(interval);
          resolve();
        }
      }, 100);
    });
  }
  _unlock(session) {
    const groupLock = this._getGroupLocks(session.guildId);
    return new Promise((resolve) => {
      const interval = setInterval(() => {
        if (groupLock.lock) {
          groupLock.lock = false;
          clearInterval(interval);
          resolve();
        }
      }, 100);
    });
  }
  clear(groupId) {
    if (groupId) {
      this._messages[groupId] = [];
    } else {
      this._messages = {};
    }
    this._groupTemp[groupId] = {
      completionMessages: []
    };
  }
  async broadcastOnBot(session, elements) {
    if (session.isDirect) {
      return;
    }
    await this._lock(session);
    const groupId = session.guildId;
    const maxMessageSize = this._config.maxMessages;
    const groupArray = this._messages[groupId] ? this._messages[groupId] : [];
    const content = mapElementToString(session, session.content, elements);
    if (content.length < 1) {
      await this._unlock(session);
      return;
    }
    const message = {
      content,
      name: session.bot.user.name,
      id: session.bot.selfId ?? "0"
    };
    groupArray.push(message);
    if (groupArray.length > maxMessageSize) {
      while (groupArray.length > maxMessageSize) {
        groupArray.shift();
      }
    }
    this._messages[groupId] = groupArray;
    await this._unlock(session);
  }
  async broadcast(session) {
    if (session.isDirect) {
      return;
    }
    await this._lock(session);
    const groupId = session.guildId;
    const maxMessageSize = this._config.maxMessages;
    const groupArray = this._messages[groupId] ? this._messages[groupId] : [];
    const elements = session.elements ? session.elements : [import_koishi6.h.text(session.content)];
    const content = mapElementToString(session, session.content, elements);
    if (content.length < 1) {
      await this._unlock(session);
      return;
    }
    const message = {
      content,
      name: getNotEmptyString(
        session.author?.nick,
        session.author?.name,
        session.event.user?.name,
        session.username
      ),
      id: session.author.id,
      timestamp: session.event.timestamp,
      quote: session.quote ? {
        content: mapElementToString(
          session,
          session.quote.content,
          session.quote.elements ?? [
            import_koishi6.h.text(session.quote.content)
          ]
        ),
        name: session.quote.user.name,
        id: session.quote.user.id
      } : void 0
    };
    groupArray.push(message);
    if (groupArray.length > maxMessageSize) {
      while (groupArray.length > maxMessageSize) {
        groupArray.shift();
      }
    }
    for (const message2 of groupArray) {
      if (message2.timestamp != null && message2.timestamp < Date.now() - 1e3 * 60 * 60) {
        groupArray.splice(groupArray.indexOf(message2), 1);
      }
    }
    this._messages[groupId] = groupArray;
    if (this._filters.some((func) => func(session, message)) && !this.isMute(session)) {
      this._eventEmitter.emit("collect", session, groupArray);
      await this._unlock(session);
      return true;
    } else {
      await this._unlock(session);
      return this.isMute(session);
    }
  }
};
function mapElementToString(session, content, elements) {
  const filteredBuffer = [];
  if (content.trimEnd().length < 1) {
    return "";
  }
  for (const element of elements) {
    if (element.type === "text") {
      const content2 = element.attrs.content;
      if (content2?.trimEnd()?.length > 0) {
        filteredBuffer.push(content2);
      }
    } else if (element.type === "at") {
      let name2 = element.attrs?.name;
      if (element.attrs.id === session.bot.selfId) {
        name2 = name2 ?? session.bot.user.name ?? "0";
      }
      if (name2 == null || name2.length < 1) {
        name2 = element.attrs.id ?? "0";
      }
      filteredBuffer.push(`${element.attrs.id}`);
    }
  }
  return filteredBuffer.join("");
}
__name(mapElementToString, "mapElementToString");
function getNotEmptyString(...texts) {
  for (const text of texts) {
    if (text && text?.length > 0) {
      return text;
    }
  }
}
__name(getNotEmptyString, "getNotEmptyString");

// src/index.ts
function apply6(ctx, config) {
  const disposables = [];
  ctx.on("ready", async () => {
    ctx.plugin(MessageCollector, config);
  });
  ctx.plugin(
    {
      apply: /* @__PURE__ */ __name(async (ctx2, config2) => {
        await ctx2.chatluna_character.stickerService.init();
        await ctx2.chatluna_character.preset.init();
        await plugins(ctx2, config2);
      }, "apply"),
      inject: Object.assign({}, inject2, {
        chatluna_character: {
          required: true
        }
      }),
      name: "chatluna_character_entry_point"
    },
    config
  );
  disposables.push(
    ctx.middleware(async (session, next) => {
      if (!ctx.chatluna_character) {
        return next();
      }
      if (ctx.bots[session.uid]) {
        return next();
      }
      const guildId = session.guildId;
      if (!config.applyGroup.includes(guildId)) {
        return next();
      }
      if (!await ctx.chatluna_character.broadcast(session)) {
        return next();
      }
    })
  );
  ctx.on("dispose", () => {
    disposables.forEach((disposable) => disposable());
  });
}
__name(apply6, "apply");
var inject = {
  required: ["chatluna"],
  optional: ["chatluna_character", "vits"]
};
var inject2 = {
  chatluna: {
    required: true
  },
  chatluna_character: {
    required: false
  },
  vits: {
    required: false
  }
};
var Config = import_koishi7.Schema.intersect([
  import_koishi7.Schema.object({
    applyGroup: import_koishi7.Schema.array(import_koishi7.Schema.string()).description("应用到的群组"),
    maxMessages: import_koishi7.Schema.number().description("存储在内存里的最大消息数量").default(10).min(3).role("slider").max(100),
    disableChatLuna: import_koishi7.Schema.boolean().default(true).description("在使用此插件的群聊里,是否禁用 ChatLuna 主功能"),
    whiteListDisableChatLuna: import_koishi7.Schema.array(import_koishi7.Schema.string()).description(
      "在使用此插件时,不禁用 ChatLuna 主功能的群聊列表"
    )
  }).description("基础配置"),
  import_koishi7.Schema.object({
    model: import_koishi7.Schema.dynamic("model").description("使用的模型"),
    modelOverride: import_koishi7.Schema.array(
      import_koishi7.Schema.object({
        groupId: import_koishi7.Schema.string().required().description("群组 ID"),
        model: import_koishi7.Schema.dynamic("model").description("模型")
      })
    ).description("针对某个群的模型设置,会覆盖上面的配置"),
    maxTokens: import_koishi7.Schema.number().default(5e3).min(1024).max(32e3).description("使用聊天的最大 token 数")
  }).description("模型配置"),
  import_koishi7.Schema.object({
    isNickname: import_koishi7.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
    isForceMute: import_koishi7.Schema.boolean().description(
      "是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
    ).default(true),
    isAt: import_koishi7.Schema.boolean().description("是否允许 bot 艾特他人").default(true),
    splitVoice: import_koishi7.Schema.boolean().description("是否分段发送语音").default(false),
    messageInterval: import_koishi7.Schema.number().default(14).min(0).role("slider").max(100).description("随机发送消息的间隔"),
    messageProbability: import_koishi7.Schema.number().default(0.1).min(0).max(4).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
    coolDownTime: import_koishi7.Schema.number().default(10).min(1).max(60 * 24).description("冷却发言时间(秒)"),
    typingTime: import_koishi7.Schema.number().default(440).min(100).role("slider").max(1500).description("模拟打字时的间隔(毫秒)"),
    muteTime: import_koishi7.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
    sendStickerProbability: import_koishi7.Schema.number().default(0.6).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
    defaultPreset: import_koishi7.Schema.dynamic("character-preset").description("使用的伪装预设").default("旧梦旧念")
  }).description("对话设置"),
  import_koishi7.Schema.object({
    configs: import_koishi7.Schema.dict(
      import_koishi7.Schema.object({
        maxTokens: import_koishi7.Schema.number().default(4e3).min(1024).max(2e4).description("使用聊天的最大 token 数"),
        isAt: import_koishi7.Schema.boolean().description("是否启用@").default(true),
        splitVoice: import_koishi7.Schema.boolean().description("是否分段发送语言").default(false),
        isNickname: import_koishi7.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
        isForceMute: import_koishi7.Schema.boolean().description(
          "是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
        ).default(true),
        messageInterval: import_koishi7.Schema.number().default(10).min(0).role("slider").max(50).description("随机发送消息的间隔"),
        messageProbability: import_koishi7.Schema.number().default(0.1).min(0).max(1).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
        coolDownTime: import_koishi7.Schema.number().default(10).min(1).max(60 * 24 * 24).description("冷却发言时间(秒)"),
        typingTime: import_koishi7.Schema.number().default(440).min(100).role("slider").max(1700).description("模拟打字时的间隔(毫秒)"),
        muteTime: import_koishi7.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
        sendStickerProbability: import_koishi7.Schema.number().default(0.2).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
        preset: import_koishi7.Schema.dynamic("character-preset").description("使用的伪装预设").default("煕")
      })
    ).role("table").description("分群配置,会覆盖上面的默认配置(键填写群号)")
  }).description("分群配置")
]);
var name = "chatluna-character";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Config,
  apply,
  inject,
  inject2,
  name
});