UNPKG

koishi-plugin-ff14calendar

Version:

一个适用于 [Koishi](https://koishi.chat/) 的 FFXIV 日历推送插件,也支持其他 Webcal/ics 日历订阅,自动推送和手动查询活动,兼容 QQ(OneBot)、Discord 等多平台。

169 lines (167 loc) 7.7 kB
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 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/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var import_node_cron = __toESM(require("node-cron")); var import_axios = __toESM(require("axios")); var import_ical = __toESM(require("ical")); var import_https_proxy_agent = require("https-proxy-agent"); var Config = import_koishi.Schema.object({ webcalUrl: import_koishi.Schema.string().description("Webcal/ics 日历订阅地址(支持 http/https/webcal)"), targetId: import_koishi.Schema.array(import_koishi.Schema.string()).role("textarea").description("目标频道/群号,每行一个,格式如 onebot:123456789 或 discord:频道ID"), cronTime: import_koishi.Schema.string().default("0 8 * * *").description("定时任务的 cron 表达式(如每天8点:0 8 * * *)"), messageTemplate: import_koishi.Schema.string().default("{summary}\n开始时间: {start}\n结束时间: {end}\n地点: {location}").description("输出内容模板,可用变量:{summary}、{start}、{end}、{location}"), proxy: import_koishi.Schema.string().description("可选,访问日历时使用的 http/https 代理地址(如 http://127.0.0.1:7890)").required(false) }); async function getTodayEvents(webcalUrl, messageTemplate, logger, proxy) { try { logger.info("开始获取日历数据..."); const url = webcalUrl.replace(/^webcal:/, "https:"); logger.info("请求日历地址: %s", url); const axiosOptions = {}; if (proxy) { axiosOptions.httpsAgent = new import_https_proxy_agent.HttpsProxyAgent(proxy); logger.info("使用代理: %s", proxy); } const res = await import_axios.default.get(url, axiosOptions); logger.info("获取到日历原始数据,长度: %d", res.data.length); const data = import_ical.default.parseICS(res.data); logger.info("解析日历数据完成"); const now = /* @__PURE__ */ new Date(); const ongoingEvents = []; const upcomingEvents = []; for (const k in data) { const event = data[k]; if (event.type === "VEVENT" && event.start) { const start = new Date(event.start); const end = event.end ? new Date(event.end) : null; if (start <= now && (!end || now < end)) { ongoingEvents.push(event); } else if (start > now) { upcomingEvents.push(event); } } } upcomingEvents.sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()); const upcomingToShow = upcomingEvents.slice(0, 3); let result = ""; if (ongoingEvents.length) { const messages = ongoingEvents.map((event) => { const start = new Date(event.start); const end = event.end ? new Date(event.end) : null; return messageTemplate.replace("{summary}", event.summary || "无标题").replace("{start}", start.toLocaleString()).replace("{end}", end ? end.toLocaleString() : "无").replace("{location}", event.location || "无"); }); result += `【正在进行的活动】 ${messages.join("\n\n")} `; } else { result += "【正在进行的活动】\n没有找到正在进行的活动。\n"; } if (upcomingToShow.length) { const messages = upcomingToShow.map((event) => { const start = new Date(event.start); const end = event.end ? new Date(event.end) : null; return messageTemplate.replace("{summary}", event.summary || "无标题").replace("{start}", start.toLocaleString()).replace("{end}", end ? end.toLocaleString() : "无").replace("{location}", event.location || "无"); }); result += ` 【即将开始的活动】 ${messages.join("\n\n")}`; } else { result += "\n【即将开始的活动】\n没有找到即将开始的活动。"; } logger.info("匹配到 %d 个正在进行的活动,%d 个即将开始的活动", ongoingEvents.length, upcomingToShow.length); return result.trim(); } catch (err) { logger.error("日历读取失败: %o", err); return "日历读取失败: " + (err?.message || err); } } __name(getTodayEvents, "getTodayEvents"); function parseTarget(targetId) { if (!targetId || typeof targetId !== "string" || !targetId.includes(":")) { return null; } const [platform, id] = targetId.split(":"); if (!platform || !id) return null; return { platform, id }; } __name(parseTarget, "parseTarget"); var scheduledTask; function apply(ctx, config) { const logger = ctx.logger("webcal"); if (!import_node_cron.default.validate(config.cronTime)) { logger.error("定时任务未注册,cron 表达式无效: %s", config.cronTime); return; } if (scheduledTask) scheduledTask.stop(); scheduledTask = import_node_cron.default.schedule(config.cronTime, async () => { logger.info("定时任务触发,开始获取并发送日历事项"); const message = await getTodayEvents(config.webcalUrl, config.messageTemplate, logger, config.proxy); let sent = false; for (const t of config.targetId) { const target = parseTarget(t); if (target) { const { platform, id } = target; const bot = ctx.bots.find((bot2) => bot2.platform === platform); if (bot) { await bot.sendMessage(id, message); logger.info("日历事项已发送到 %s 平台: %s", platform, id); sent = true; } else { logger.error("未找到平台 %s 的 bot,无法发送消息", platform); } } } if (!sent) { logger.warn("未配置有效 targetId,定时任务仅生成消息但未发送。"); } }); ctx.command("webcal.today").alias("最近活动").channelFields(["id"]).action(async () => { logger.info("手动指令触发,开始获取日历事项"); try { const message = await getTodayEvents(config.webcalUrl, config.messageTemplate, logger, config.proxy); return message; } catch (err) { logger.error("指令执行异常: %o", err); return "发生未知错误: " + (err?.message || err); } }); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply });