Add HeXi bot codebase: custom plugins, web frontends, tests

- hexi core: message handling, rate limiting, cooldown, plugin manager
- Custom plugins: BF stats, daily check-in, quotes, persona cards, etc.
- Community plugins vendored under hexi/plugins with local fixes
- Web admin frontends (learning-chat, persona-admin), unified hexi/web
- Tests for rate_limit/cooldown/memes/persona; poetry.lock

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-01 13:13:40 +08:00
co-authored by Claude
parent 1783c60afa
commit b61d09f09f
3201 changed files with 160436 additions and 171 deletions
@@ -0,0 +1,77 @@
/// <reference path="./globalThis.d.ts" />
(() => {
/**
* @template {any[]} A
* @template R
* @param {(...args: A) => Promise<R>} func
* @returns {(...args: A) => Promise<R | undefined>}
*/
function wrapErr(func) {
return async (...args) => {
try {
return await func(...args);
} catch (e) {
console.error(e);
}
};
}
/**
* @param {string} url
*/
async function makeObjUrlFromUrl(url) {
const res = await fetch(url);
const blob = await res.blob();
return URL.createObjectURL(blob);
}
/** @typedef {(elem: HTMLElement, objUrl: string) => Promise<any>} PropSetterType */
/** @type {Record<string, PropSetterType>} */
const propSetterMap = {
'data-background-image': async (elem, objUrl) => {
elem.style.backgroundImage = `url(${objUrl})`;
},
'data-src': async (elem, objUrl) => {
if (elem instanceof HTMLImageElement) elem.src = objUrl;
},
};
/**
* @param {HTMLElement} elem
* @param {string} attr
* @param {PropSetterType} setter
*/
async function lazyLoadOne(elem, attr, setter) {
const url = elem.getAttribute(attr);
if (!url) return;
const objUrl = await makeObjUrlFromUrl(url);
await setter(elem, objUrl);
elem.removeAttribute(attr);
}
/**
* @param {string} attr
*/
async function lazyLoad(attr) {
/** @type {HTMLElement[]} */
// @ts-ignore
const elements = [...document.body.querySelectorAll(`[${attr}]`)].filter(
(v) => v instanceof HTMLElement
);
const tasks = elements.map((v) =>
wrapErr(lazyLoadOne)(v, attr, propSetterMap[attr])
);
await Promise.all(tasks);
}
async function lazyLoadAll() {
await Promise.all(Object.keys(propSetterMap).map(lazyLoad));
}
// 使用 globalThis.plugins.push 注册插件
// 只有这样才能保证你的插件运行完成之后才会网页截图
globalThis.plugins.push(async () => {
await lazyLoadAll();
});
})();