koishi-plugin-autochat
Version:
202 lines (200 loc) • 8.81 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name,
reusable: () => reusable
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
var import_koishi_plugin_chatluna = require("koishi-plugin-chatluna");
var import_chains = require("koishi-plugin-chatluna/chains");
var name = "autochat";
var reusable = true;
var inject = ["chatluna", "database"];
var Config = import_koishi.Schema.object({
startTime: import_koishi.Schema.string().role("time").description("定时任务的起始时间,格式 HH:mm。").default("08:00"),
endTime: import_koishi.Schema.string().role("time").description("定时任务的结束时间,格式 HH:mm。").default("22:00"),
interval: import_koishi.Schema.number().description("在指定时间段内,按固定间隔发送对话的间隔(小时)。设为 0 可禁用。").default(4),
randomSends: import_koishi.Schema.number().description("在指定时间段内,每日额外随机发送的对话次数。").default(2),
prompt: import_koishi.Schema.string().description("发送给 AI 的内容模板,`{time}` 将被替换为当前时间。").default("现在是北京时间 {time},请记录下来。"),
channelId: import_koishi.Schema.string().description("要把回复发送到的频道 ID(QQ 号或群号)").required(),
channelType: import_koishi.Schema.union(["group", "private"]).description("发送到群聊还是私聊").default("group"),
botSelfId: import_koishi.Schema.string().description("(可选)指定用于发送消息的机器人 selfId,如果留空则使用第一个可用机器人。").default("")
});
function buildSession(ctx, bot, config, content) {
const s = bot.session();
if (config.channelType === "group") {
s.guildId = config.channelId;
s.channelId = config.channelId;
s.userId = "autochat";
} else {
s.guildId = "0";
s.channelId = config.channelId;
s.userId = config.channelId;
}
s.content = content;
return s;
}
__name(buildSession, "buildSession");
function apply(ctx, config) {
let intervalTimeout = null;
let randomTimeouts = [];
let dailyResetTimeout = null;
const loadedPlatforms = /* @__PURE__ */ new Set();
const events = {
"llm-queue-waiting": /* @__PURE__ */ __name(async () => {
}, "llm-queue-waiting"),
"llm-used-token-count": /* @__PURE__ */ __name(async () => {
}, "llm-used-token-count")
};
const cleanup = /* @__PURE__ */ __name(() => {
if (intervalTimeout) clearTimeout(intervalTimeout);
if (dailyResetTimeout) clearTimeout(dailyResetTimeout);
randomTimeouts.forEach(clearTimeout);
intervalTimeout = null;
dailyResetTimeout = null;
randomTimeouts = [];
}, "cleanup");
async function sendOnce(customPrompt, sessionSource) {
const bot = ctx.bots.find((b) => !config.botSelfId || b.selfId === config.botSelfId);
if (!bot) {
ctx.logger.warn("[autochat] 没有找到可用的 bot,取消发送。");
return;
}
const timeStr = (/* @__PURE__ */ new Date()).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
const message = customPrompt && customPrompt.length > 0 ? customPrompt : config.prompt.replace("{time}", timeStr);
const vSession = sessionSource ?? buildSession(ctx, bot, config, message);
let room;
if (config.channelType === "private") {
room = await (0, import_chains.queryJoinedConversationRoom)(ctx, vSession);
} else {
room = await (0, import_chains.queryPublicConversationRoom)(ctx, vSession);
}
if (!room) {
ctx.logger.warn("[autochat] 找不到对应房间,已跳过。");
return;
}
try {
const platform = room.model.split("/")[0];
if (!loadedPlatforms.has(platform)) {
ctx.logger.info(`[autochat] 等待平台 ${platform} 加载...`);
await ctx.chatluna.awaitLoadPlatform(platform, 6e4);
loadedPlatforms.add(platform);
ctx.logger.info(`[autochat] 平台 ${platform} 已就绪。`);
}
} catch {
}
try {
ctx.logger.info(`[autochat] 向房间发送: ${message}`);
const replyMsg = await ctx.chatluna.chat(
vSession,
room,
{ role: "user", content: message },
events,
false
);
const reply = replyMsg?.content ?? "";
if (!reply) return;
ctx.logger.info(`[autochat] ChatLuna 回复: ${reply}`);
const targetId = (config.channelType === "private" ? "private:" : "group:") + config.channelId;
await bot.sendMessage(targetId, reply);
} catch (err) {
ctx.logger.error("[autochat] 调用 ChatLuna 失败", err);
}
}
__name(sendOnce, "sendOnce");
const scheduleAllTasks = /* @__PURE__ */ __name(() => {
cleanup();
const [startHour, startMinute] = config.startTime.split(":").map(Number);
const [endHour, endMinute] = config.endTime.split(":").map(Number);
if ([startHour, startMinute, endHour, endMinute].some(isNaN)) {
ctx.logger.warn("[autochat] 无效的时间配置,定时任务已停止。");
return;
}
const now = /* @__PURE__ */ new Date();
const startTimeToday = new Date(now);
startTimeToday.setHours(startHour, startMinute, 0, 0);
const endTimeToday = new Date(now);
endTimeToday.setHours(endHour, endMinute, 0, 0);
if (startTimeToday.getTime() >= endTimeToday.getTime()) {
ctx.logger.warn("[autochat] 起始时间必须早于结束时间,定时任务已停止。");
return;
}
if (config.interval > 0) {
let nextSendTime = new Date(startTimeToday);
while (nextSendTime.getTime() < now.getTime()) {
nextSendTime.setHours(nextSendTime.getHours() + config.interval);
}
if (nextSendTime.getTime() > endTimeToday.getTime()) {
nextSendTime = new Date(startTimeToday);
nextSendTime.setDate(nextSendTime.getDate() + 1);
}
const delay = nextSendTime.getTime() - now.getTime();
ctx.logger.info(`[autochat] 下次固定间隔发送已安排在 ${nextSendTime.toLocaleString("zh-CN")}`);
intervalTimeout = setTimeout(() => {
sendOnce();
scheduleAllTasks();
}, delay);
}
if (config.randomSends > 0) {
const scheduleWindowStart = Math.max(now.getTime(), startTimeToday.getTime());
const scheduleWindowEnd = endTimeToday.getTime();
if (scheduleWindowStart < scheduleWindowEnd) {
let scheduledCount = 0;
for (let i = 0; i < config.randomSends; i++) {
const randomTimestamp = scheduleWindowStart + Math.random() * (scheduleWindowEnd - scheduleWindowStart);
const delay = randomTimestamp - now.getTime();
if (delay > 0) {
const handle = setTimeout(() => sendOnce(), delay);
randomTimeouts.push(handle);
scheduledCount++;
}
}
if (scheduledCount > 0) {
ctx.logger.info(`[autochat] 已安排 ${scheduledCount} 次随机发送。`);
}
}
}
const nextMidnight = new Date(now);
nextMidnight.setDate(nextMidnight.getDate() + 1);
nextMidnight.setHours(0, 1, 0, 0);
const delayToMidnight = nextMidnight.getTime() - now.getTime();
dailyResetTimeout = setTimeout(scheduleAllTasks, delayToMidnight);
ctx.logger.info(`[autochat] 已安排在 ${nextMidnight.toLocaleString("zh-CN")} 重新规划次日任务。`);
}, "scheduleAllTasks");
ctx.on("ready", scheduleAllTasks);
ctx.on("dispose", cleanup);
ctx.command("autochat [prompt:text]", "立即触发一次自动聊天", { authority: 1 }).usage("示例: /autochat 现在几点?").action(async ({ session }, prompt) => {
await sendOnce(prompt, session);
});
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name,
reusable
});