koishi-plugin-filter-pro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{send as j,icons as A}from"@koishijs/client";import{defineComponent as H,computed as $,ref as T,resolveComponent as k,createBlock as N,openBlock as f,withCtx as s,createElementVNode as t,createTextVNode as c,createVNode as d,createCommentVNode as C,createElementBlock as w,Fragment as G,renderList as J,normalizeClass as O,toDisplayString as _,withDirectives as I,vModelText as R}from"vue";const K={class:"fp-layout"},Q={class:"fp-header"},W={class:"actions"},X={class:"fp-main"},Y={class:"rule-list"},Z=["onClick"],h={class:"top"},ee={class:"name"},te={class:"meta"},le={class:"editor-body"},oe={class:"editor-grid"},ne={class:"field"},ae={class:"field"},ie={key:0,class:"field"},se={class:"field"},de={class:"field"},re={class:"field switch"},ue=["checked"],pe={key:0,class:"field"},ve={class:"expr-wrap"},ce={class:"footer-actions"},fe=H({__name:"page",setup(m){const i=j,V={bypass:"放行",block:"拦截"},x=[{label:"全局",value:"global"},{label:"插件",value:"plugin"}],E=[{label:"放行(bypass)",value:"bypass"},{label:"拦截(block)",value:"block"}],P=$(()=>[{label:"请选择插件实例",value:""},...B.value.map(l=>({label:l.label,value:l.key}))]),U=T([]),B=T([]),u=T(""),b=$(()=>[...U.value].sort((l,e)=>l.priority-e.priority||l.id.localeCompare(e.id))),n=$(()=>b.value.find(l=>l.id===u.value));function S(){return{type:"group",operator:"and",children:[{type:"compare",field:"guildId",operator:"eq",value:""}]}}async function g(){var a;const[l,e]=await Promise.all([i("filter-pro/list"),i("filter-pro/targets")]);B.value=e,U.value=l,(!u.value||!U.value.some(r=>r.id===u.value))&&(u.value=((a=b.value[0])==null?void 0:a.id)||"")}async function q(){var l,e;await i("filter-pro/create",{name:"new-rule",enabled:true,priority:(((l=b.value.at(-1))==null?void 0:l.priority)??-1)+1,action:"block",target:{type:"global",value:""},condition:S(),response:""}),await g(),u.value=((e=b.value.at(-1))==null?void 0:e.id)||u.value}async function z(l,e){const a=e.target;l.enabled=!!(a!=null&&a.checked),await i("filter-pro/toggle",{id:l.id,enabled:l.enabled})}async function D(l){const e=l.target.type==="global"?{type:"global",value:""}:{type:"plugin",value:l.target.value||""};await i("filter-pro/update",{id:l.id,name:l.name,enabled:l.enabled,priority:l.priority,action:l.action,target:e,condition:l.condition,response:l.response||""}),await g()}async function M(l){await i("filter-pro/delete",l),await g()}async function F(l,e){const a=b.value,r=a.findIndex(y=>y.id===l);if(r<0)return;const p=r+e;if(p<0||p>=a.length)return;const v=[...a];[v[r],v[p]]=[v[p],v[r]],await i("filter-pro/reorder",v.map(y=>y.id)),await g()}return g(),(l,e)=>{const a=k("k-button"),r=k("k-card"),p=k("FpSelect"),v=k("ExprEditor"),y=k("k-layout");return f(),N(y,null,{default:s(()=>[t("div",K,[t("div",Q,[e[16]||(e[16]=t("div",null,[t("h2",null,"Filter Pro"),t("p",null,[c("持久化目录:"),t("code",null,"data/filterpro")])],-1)),t("div",W,[d(a,{onClick:q},{default:s(()=>[...e[12]||(e[12]=[c("新建规则",-1)])]),_:1}),d(a,{onClick:e[0]||(e[0]=o=>n.value&&D(n.value)),disabled:!n.value},{default:s(()=>[...e[13]||(e[13]=[c("保存",-1)])]),_:1},8,["disabled"]),d(a,{onClick:e[1]||(e[1]=o=>n.value&&M(n.value.id)),disabled:!n.value},{default:s(()=>[...e[14]||(e[14]=[c("删除",-1)])]),_:1},8,["disabled"]),d(a,{onClick:g},{default:s(()=>[...e[15]||(e[15]=[c("刷新",-1)])]),_:1})])]),t("div",X,[d(r,{class:"panel list"},{header:s(()=>[...e[17]||(e[17]=[t("div",{class:"panel-title"},"规则列表",-1)])]),default:s(()=>[t("div",Y,[(f(true),w(G,null,J(b.value,o=>(f(),w("button",{key:o.id,class:O(["rule-item",{active:u.value===o.id}]),onClick:_e=>u.value=o.id},[t("div",h,[t("span",ee,_(o.name||"未命名规则"),1),t("span",{class:O(["badge",o.action])},_(V[o.action]),3)]),t("div",te,[t("span",null,"#"+_(o.priority),1),t("span",null,_(o.target.type==="global"?"全局":o.target.value||"未指定插件"),1),t("span",null,_(o.enabled?"启用":"停用"),1)])],10,Z))),128))])]),_:1}),n.value?(f(),N(r,{key:0,class:"panel editor"},{header:s(()=>[...e[18]||(e[18]=[t("div",{class:"panel-title"},"规则编辑",-1)])]),default:s(()=>[t("div",le,[t("div",oe,[t("label",ne,[e[19]||(e[19]=t("span",null,"规则名",-1)),I(t("input",{class:"input","onUpdate:modelValue":e[2]||(e[2]=o=>n.value.name=o),placeholder:"输入规则名"},null,512),[[R,n.value.name]])]),t("label",ae,[e[20]||(e[20]=t("span",null,"目标",-1)),d(p,{modelValue:n.value.target.type,"onUpdate:modelValue":e[3]||(e[3]=o=>n.value.target.type=o),options:x},null,8,["modelValue"])]),n.value.target.type==="plugin"?(f(),w("label",ie,[e[21]||(e[21]=t("span",null,"插件实例",-1)),d(p,{modelValue:n.value.target.value,"onUpdate:modelValue":e[4]||(e[4]=o=>n.value.target.value=o),options:P.value},null,8,["modelValue","options"])])):C("v-if",true),t("label",se,[e[22]||(e[22]=t("span",null,"动作",-1)),d(p,{modelValue:n.value.action,"onUpdate:modelValue":e[5]||(e[5]=o=>n.value.action=o),options:E},null,8,["modelValue"])]),t("label",de,[e[23]||(e[23]=t("span",null,"优先级",-1)),I(t("input",{class:"input",type:"number","onUpdate:modelValue":e[6]||(e[6]=o=>n.value.priority=o)},null,512),[[R,n.value.priority,void 0,{number:true}]])]),t("label",re,[e[24]||(e[24]=t("span",null,"启用状态",-1)),t("input",{type:"checkbox",checked:n.value.enabled,onChange:e[7]||(e[7]=o=>z(n.value,o))},null,40,ue)])]),n.value.action==="block"?(f(),w("label",pe,[e[25]||(e[25]=t("span",null,"拦截响应",-1)),I(t("input",{class:"input","onUpdate:modelValue":e[8]||(e[8]=o=>n.value.response=o),placeholder:"可选,留空则静默拦截"},null,512),[[R,n.value.response]])])):C("v-if",true),t("div",ve,[e[26]||(e[26]=t("div",{class:"panel-title small"},"条件表达式",-1)),d(v,{modelValue:n.value.condition,"onUpdate:modelValue":e[9]||(e[9]=o=>n.value.condition=o)},null,8,["modelValue"])]),t("div",ce,[d(a,{onClick:e[10]||(e[10]=o=>F(n.value.id,-1))},{default:s(()=>[...e[27]||(e[27]=[c("上移",-1)])]),_:1}),d(a,{onClick:e[11]||(e[11]=o=>F(n.value.id,1))},{default:s(()=>[...e[28]||(e[28]=[c("下移",-1)])]),_:1})])])]),_:1})):C("v-if",true)])])]),_:1})}}}),L=(m,i)=>{const V=m.__vccOpts||m;for(const[x,E]of i)V[x]=E;return V},me=L(fe,[["__scopeId","data-v-c2842c3d"]]),be={},ge={class:"k-icon",viewBox:"0 0 512 512",xmlns:"http://www.w3.org/2000/svg"};function ye(m,i){return f(),w("svg",ge,[C(" 漏斗主体 "),i[0]||(i[0]=t("path",{fill:"none",stroke:"currentColor","stroke-width":"32","stroke-linejoin":"round",d:"M487.976 0H24.028C2.71 0-8.047 25.866 7.058 40.971L192 225.941V432c0 7.831 3.821 15.17 10.237 19.662l80 55.98C298.02 518.69 320 507.493 320 487.98V225.941l184.947-184.97C520.021 25.896 509.338 0 487.976 0z"},null,-1))])}const ke=L(be,[["render",ye]]),Ce=m=>{A.register("activity:filter-pro",ke),m.page({name:"规则集",path:"/filter-pro",icon:"activity:filter-pro",order:320,component:me})};export{Ce as default};
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .fp-layout[data-v-c2842c3d]{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;padding:12px;color:var(--k-text-normal, inherit);box-sizing:border-box;overflow:hidden}.fp-header[data-v-c2842c3d]{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:12px}.fp-header h2[data-v-c2842c3d]{margin:0;font-size:20px}.fp-header p[data-v-c2842c3d]{margin:4px 0 0;color:var(--k-text-secondary, #888)}.actions[data-v-c2842c3d]{display:flex;gap:8px}.fp-main[data-v-c2842c3d]{display:grid;grid-template-columns:320px 1fr;gap:12px;min-height:0;flex:1;overflow:hidden}.list[data-v-c2842c3d],.editor[data-v-c2842c3d]{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden}.list[data-v-c2842c3d] .k-card-body,.editor[data-v-c2842c3d] .k-card-body{display:flex;flex-direction:column;flex:1;min-height:0;overflow:auto}.panel[data-v-c2842c3d]{background:var(--k-card-bg, transparent);border:1px solid var(--k-card-border, rgba(127, 127, 127, .35));border-radius:10px}.panel-title[data-v-c2842c3d]{font-weight:600}.panel-title.small[data-v-c2842c3d]{margin-bottom:8px}.rule-list[data-v-c2842c3d]{display:flex;flex-direction:column;gap:8px}.editor-body[data-v-c2842c3d]{display:flex;flex-direction:column}.rule-item[data-v-c2842c3d]{width:100%;border:1px solid var(--k-card-border, rgba(127, 127, 127, .35));background:var(--k-input-bg, transparent);color:var(--k-text-normal, inherit);border-radius:8px;padding:8px;text-align:left;cursor:pointer}.rule-item.active[data-v-c2842c3d]{border-color:var(--k-color-primary, #4f7cff);box-shadow:0 0 0 2px color-mix(in srgb,var(--k-color-primary, #4f7cff) 20%,transparent)}.rule-item .top[data-v-c2842c3d]{display:flex;align-items:center;justify-content:space-between}.rule-item .name[data-v-c2842c3d]{font-weight:600}.rule-item .meta[data-v-c2842c3d]{margin-top:4px;display:flex;gap:8px;flex-wrap:wrap;color:var(--k-text-secondary, #888);font-size:12px}.badge[data-v-c2842c3d]{border-radius:999px;padding:2px 8px;font-size:12px}.badge.bypass[data-v-c2842c3d]{background:color-mix(in srgb,#1abc9c 20%,transparent)}.badge.block[data-v-c2842c3d]{background:color-mix(in srgb,#e74c3c 20%,transparent)}.editor-grid[data-v-c2842c3d]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:12px}.field[data-v-c2842c3d]{display:flex;flex-direction:column;gap:6px}.field.switch[data-v-c2842c3d]{justify-content:flex-end}.input[data-v-c2842c3d]{width:100%;min-width:0;box-sizing:border-box;color:var(--k-text-normal, inherit);background:var(--k-input-bg, transparent);border:1px solid var(--k-card-border, rgba(127, 127, 127, .35));border-radius:6px;padding:6px 10px}.expr-wrap[data-v-c2842c3d]{margin:8px 0 12px}.footer-actions[data-v-c2842c3d]{display:flex;gap:8px;flex-wrap:wrap}@media (max-width: 980px){.fp-main[data-v-c2842c3d],.editor-grid[data-v-c2842c3d]{grid-template-columns:1fr}}
package/lib/index.cjs ADDED
@@ -0,0 +1,497 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __export = (target, all) => {
7
+ for (var name2 in all)
8
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Config: () => Config,
24
+ apply: () => apply,
25
+ name: () => name
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var import_koishi = require("koishi");
29
+ var import_plugin_console = require("@koishijs/plugin-console");
30
+ var import_node_path = require("node:path");
31
+ var import_promises = require("node:fs/promises");
32
+ var name = "filter-pro";
33
+ var Config = import_koishi.Schema.object({
34
+ filename: import_koishi.Schema.string().default("rules.json").description("持久化文件名(位于 data/filterpro/ 下)。")
35
+ });
36
+ var kRecord = /* @__PURE__ */ Symbol.for("koishi.loader.record");
37
+ var FilterProProvider = class extends import_plugin_console.DataService {
38
+ constructor(ctx, state) {
39
+ super(ctx, "filter-pro");
40
+ this.state = state;
41
+ }
42
+ static {
43
+ __name(this, "FilterProProvider");
44
+ }
45
+ async get() {
46
+ return sortRules(this.state.rules).map(cloneRule);
47
+ }
48
+ };
49
+ function defaultExpr() {
50
+ return {
51
+ type: "group",
52
+ operator: "and",
53
+ children: [{ type: "compare", field: "guildId", operator: "eq", value: "" }]
54
+ };
55
+ }
56
+ __name(defaultExpr, "defaultExpr");
57
+ function cloneRule(rule) {
58
+ return {
59
+ id: rule.id,
60
+ name: rule.name,
61
+ enabled: rule.enabled,
62
+ priority: rule.priority,
63
+ action: rule.action,
64
+ target: { ...rule.target },
65
+ condition: cloneExpr(rule.condition),
66
+ response: rule.response
67
+ };
68
+ }
69
+ __name(cloneRule, "cloneRule");
70
+ function cloneExpr(expr) {
71
+ if (expr.type === "group") {
72
+ return {
73
+ type: "group",
74
+ operator: expr.operator,
75
+ children: expr.children.map(cloneExpr)
76
+ };
77
+ }
78
+ if (expr.type === "not") {
79
+ return { type: "not", child: cloneExpr(expr.child) };
80
+ }
81
+ return {
82
+ type: "compare",
83
+ field: expr.field,
84
+ operator: expr.operator,
85
+ value: expr.value
86
+ };
87
+ }
88
+ __name(cloneExpr, "cloneExpr");
89
+ function sortRules(rules) {
90
+ return [...rules].sort(
91
+ (a, b) => a.priority - b.priority || a.id.localeCompare(b.id)
92
+ );
93
+ }
94
+ __name(sortRules, "sortRules");
95
+ function normalizeExpr(input) {
96
+ if (!input || typeof input !== "object") return defaultExpr();
97
+ const data = input;
98
+ if (data.type === "group") {
99
+ const operator2 = data.operator === "or" ? "or" : "and";
100
+ const children = Array.isArray(data.children) ? data.children.map(normalizeExpr) : [];
101
+ return { type: "group", operator: operator2, children };
102
+ }
103
+ if (data.type === "not") {
104
+ return { type: "not", child: normalizeExpr(data.child) };
105
+ }
106
+ const allowed = [
107
+ "eq",
108
+ "ne",
109
+ "includes",
110
+ "regex",
111
+ "gt",
112
+ "gte",
113
+ "lt",
114
+ "lte",
115
+ "exists"
116
+ ];
117
+ const operator = allowed.includes(data.operator) ? data.operator : "eq";
118
+ return {
119
+ type: "compare",
120
+ field: typeof data.field === "string" ? data.field : "content",
121
+ operator,
122
+ value: data.value
123
+ };
124
+ }
125
+ __name(normalizeExpr, "normalizeExpr");
126
+ function normalizeRule(input) {
127
+ const rawTarget = input.target || { type: "global" };
128
+ const rawValue = typeof rawTarget.value === "string" ? rawTarget.value.trim() : "";
129
+ const normalizedValue = rawTarget.type === "plugin" ? rawValue : "";
130
+ const target = {
131
+ type: rawTarget.type === "plugin" ? "plugin" : "global",
132
+ value: normalizedValue
133
+ };
134
+ return {
135
+ id: input.id || import_koishi.Random.id(8),
136
+ name: input.name || "new-rule",
137
+ enabled: input.enabled ?? true,
138
+ priority: input.priority ?? 0,
139
+ action: input.action || "block",
140
+ target,
141
+ condition: normalizeExpr(input.condition),
142
+ response: input.response ?? ""
143
+ };
144
+ }
145
+ __name(normalizeRule, "normalizeRule");
146
+ function matchTarget(target, vars) {
147
+ if (!target || target.type === "global") return true;
148
+ const pluginKey = typeof vars.pluginKey === "string" ? vars.pluginKey : "";
149
+ const value = target.value?.trim();
150
+ if (!value) return false;
151
+ return pluginKey === value;
152
+ }
153
+ __name(matchTarget, "matchTarget");
154
+ function collectPluginTargets(ctx) {
155
+ const result = [];
156
+ const dedup = /* @__PURE__ */ new Set();
157
+ const visited = /* @__PURE__ */ new Set();
158
+ const walk = /* @__PURE__ */ __name((cursor) => {
159
+ const record = cursor?.scope?.[kRecord];
160
+ if (!record || visited.has(record)) return;
161
+ visited.add(record);
162
+ for (const [fullKey, fork] of Object.entries(record)) {
163
+ const key = String(fullKey).replace(/^~/, "");
164
+ const [name2, ident = ""] = key.split(":", 2);
165
+ if (name2 && name2 !== "group" && !dedup.has(key)) {
166
+ dedup.add(key);
167
+ result.push({
168
+ key,
169
+ name: name2,
170
+ ident,
171
+ label: ident ? `${name2}:${ident}` : name2
172
+ });
173
+ }
174
+ walk(fork?.ctx);
175
+ }
176
+ }, "walk");
177
+ walk(ctx.root);
178
+ return result.sort((a, b) => a.key.localeCompare(b.key));
179
+ }
180
+ __name(collectPluginTargets, "collectPluginTargets");
181
+ function createPluginResolver(ctx) {
182
+ let targets = [];
183
+ const byScope = /* @__PURE__ */ new Map();
184
+ const rebuild = /* @__PURE__ */ __name(() => {
185
+ targets = collectPluginTargets(ctx);
186
+ byScope.clear();
187
+ const visited = /* @__PURE__ */ new Set();
188
+ const walk = /* @__PURE__ */ __name((cursor) => {
189
+ const record = cursor?.scope?.[kRecord];
190
+ if (!record || visited.has(record)) return;
191
+ visited.add(record);
192
+ for (const [fullKey, fork] of Object.entries(record)) {
193
+ const key = String(fullKey).replace(/^~/, "");
194
+ const target = targets.find((item) => item.key === key);
195
+ if (target && fork?.ctx?.scope) {
196
+ byScope.set(fork.ctx.scope, target);
197
+ }
198
+ walk(fork?.ctx);
199
+ }
200
+ }, "walk");
201
+ walk(ctx.root);
202
+ }, "rebuild");
203
+ const resolveByCommand = /* @__PURE__ */ __name((command) => {
204
+ let scope = command.caller.scope;
205
+ while (scope) {
206
+ const found = byScope.get(scope);
207
+ if (found) return found;
208
+ scope = scope.parent?.scope;
209
+ }
210
+ }, "resolveByCommand");
211
+ return {
212
+ rebuild,
213
+ list: /* @__PURE__ */ __name(() => targets, "list"),
214
+ resolveByCommand
215
+ };
216
+ }
217
+ __name(createPluginResolver, "createPluginResolver");
218
+ function getByPath(source, path) {
219
+ if (!path) return void 0;
220
+ const parts = path.split(".");
221
+ let cursor = source;
222
+ for (const part of parts) {
223
+ if (!cursor || typeof cursor !== "object") return void 0;
224
+ cursor = cursor[part];
225
+ }
226
+ return cursor;
227
+ }
228
+ __name(getByPath, "getByPath");
229
+ function coerceNumber(value) {
230
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
231
+ if (typeof value === "string" && value.trim()) {
232
+ const parsed = Number(value);
233
+ return Number.isFinite(parsed) ? parsed : null;
234
+ }
235
+ return null;
236
+ }
237
+ __name(coerceNumber, "coerceNumber");
238
+ function evaluateCompare(left, expr) {
239
+ if (expr.operator === "exists") {
240
+ return left !== void 0 && left !== null;
241
+ }
242
+ const right = expr.value;
243
+ if (expr.operator === "eq") return left === right;
244
+ if (expr.operator === "ne") return left !== right;
245
+ if (expr.operator === "includes") {
246
+ if (typeof left === "string") return left.includes(String(right ?? ""));
247
+ if (Array.isArray(left)) return left.includes(right);
248
+ return false;
249
+ }
250
+ if (expr.operator === "regex") {
251
+ if (typeof left !== "string") return false;
252
+ try {
253
+ return new RegExp(String(right ?? "")).test(left);
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+ const ln = coerceNumber(left);
259
+ const rn = coerceNumber(right);
260
+ if (ln === null || rn === null) return false;
261
+ if (expr.operator === "gt") return ln > rn;
262
+ if (expr.operator === "gte") return ln >= rn;
263
+ if (expr.operator === "lt") return ln < rn;
264
+ if (expr.operator === "lte") return ln <= rn;
265
+ return false;
266
+ }
267
+ __name(evaluateCompare, "evaluateCompare");
268
+ function evaluateExpr(expr, vars) {
269
+ if (expr.type === "group") {
270
+ if (!expr.children.length) return true;
271
+ if (expr.operator === "and")
272
+ return expr.children.every((child) => evaluateExpr(child, vars));
273
+ return expr.children.some((child) => evaluateExpr(child, vars));
274
+ }
275
+ if (expr.type === "not") {
276
+ return !evaluateExpr(expr.child, vars);
277
+ }
278
+ return evaluateCompare(getByPath(vars, expr.field), expr);
279
+ }
280
+ __name(evaluateExpr, "evaluateExpr");
281
+ async function readRules(file) {
282
+ try {
283
+ const raw = await (0, import_promises.readFile)(file, "utf8");
284
+ const parsed = JSON.parse(raw);
285
+ if (!Array.isArray(parsed)) return [];
286
+ return parsed.map((item) => normalizeRule(item));
287
+ } catch {
288
+ return null;
289
+ }
290
+ }
291
+ __name(readRules, "readRules");
292
+ function createPersister(file) {
293
+ let queue = Promise.resolve();
294
+ return async (rules) => {
295
+ queue = queue.catch(() => {
296
+ }).then(async () => {
297
+ await (0, import_promises.writeFile)(
298
+ file,
299
+ JSON.stringify(rules.map(cloneRule), null, 2),
300
+ "utf8"
301
+ );
302
+ });
303
+ await queue;
304
+ };
305
+ }
306
+ __name(createPersister, "createPersister");
307
+ function apply(ctx, config = {}) {
308
+ const baseDir = ctx.baseDir || process.cwd();
309
+ const dataDir = (0, import_node_path.join)(baseDir, "data", "filterpro");
310
+ const dataFile = (0, import_node_path.join)(dataDir, config.filename || "rules.json");
311
+ const state = { rules: [] };
312
+ const persist = createPersister(dataFile);
313
+ const pluginResolver = createPluginResolver(ctx);
314
+ const initialize = /* @__PURE__ */ __name(async () => {
315
+ await (0, import_promises.mkdir)(dataDir, { recursive: true });
316
+ const loaded = await readRules(dataFile);
317
+ if (loaded) {
318
+ state.rules = loaded;
319
+ return;
320
+ }
321
+ state.rules = [];
322
+ await persist(state.rules);
323
+ }, "initialize");
324
+ const ready = initialize().catch((error) => {
325
+ ctx.logger("filter-pro").warn("failed to initialize persistent rules: %s", String(error));
326
+ state.rules = [];
327
+ });
328
+ const refreshPluginTargets = /* @__PURE__ */ __name(async () => {
329
+ await ready;
330
+ pluginResolver.rebuild();
331
+ }, "refreshPluginTargets");
332
+ void refreshPluginTargets();
333
+ ctx.on("ready", refreshPluginTargets);
334
+ ctx.on("internal/fork", refreshPluginTargets);
335
+ ctx.on("internal/update", refreshPluginTargets);
336
+ ctx.on("internal/runtime", refreshPluginTargets);
337
+ ctx.middleware(async (session, next) => {
338
+ await ready;
339
+ const vars = {
340
+ platform: session.platform,
341
+ selfId: session.selfId,
342
+ userId: session.userId,
343
+ channelId: session.channelId,
344
+ guildId: session.guildId,
345
+ isDirect: session.isDirect,
346
+ content: session.content,
347
+ subtype: session.subtype,
348
+ event: session.event,
349
+ author: session.author,
350
+ quote: session.quote
351
+ };
352
+ for (const rule of sortRules(state.rules)) {
353
+ if (!rule.enabled) continue;
354
+ if (rule.target.type !== "global") continue;
355
+ if (!matchTarget(rule.target, vars)) continue;
356
+ const matched = evaluateExpr(rule.condition, vars);
357
+ if (!matched) continue;
358
+ if (rule.action === "bypass") return next();
359
+ if (rule.response) return rule.response;
360
+ return "";
361
+ }
362
+ return next();
363
+ }, true);
364
+ ctx.before("command/execute", async (argv) => {
365
+ await ready;
366
+ const plugin = pluginResolver.resolveByCommand(argv.command);
367
+ const vars = {
368
+ platform: argv.session.platform,
369
+ selfId: argv.session.selfId,
370
+ userId: argv.session.userId,
371
+ channelId: argv.session.channelId,
372
+ guildId: argv.session.guildId,
373
+ isDirect: argv.session.isDirect,
374
+ content: argv.session.content,
375
+ subtype: argv.session.subtype,
376
+ event: argv.session.event,
377
+ author: argv.session.author,
378
+ quote: argv.session.quote,
379
+ commandName: argv.command.name,
380
+ pluginKey: plugin?.key,
381
+ pluginName: plugin?.name
382
+ };
383
+ for (const rule of sortRules(state.rules)) {
384
+ if (!rule.enabled) continue;
385
+ if (rule.target.type === "global") continue;
386
+ if (!matchTarget(rule.target, vars)) continue;
387
+ const matched = evaluateExpr(rule.condition, vars);
388
+ if (!matched) continue;
389
+ if (rule.action === "bypass") return;
390
+ if (rule.response) return rule.response;
391
+ return "";
392
+ }
393
+ });
394
+ ctx.inject(["console"], (ctx2) => {
395
+ ctx2.console.addEntry({
396
+ dev: (0, import_node_path.resolve)(__dirname, "../client/index.ts"),
397
+ prod: (0, import_node_path.resolve)(__dirname, "../dist")
398
+ });
399
+ const provider = new FilterProProvider(ctx2, state);
400
+ const authority = 3;
401
+ const addListener = ctx2.console.addListener.bind(ctx2.console);
402
+ const refresh = /* @__PURE__ */ __name(async () => {
403
+ await persist(state.rules);
404
+ provider.refresh();
405
+ }, "refresh");
406
+ addListener(
407
+ "filter-pro/list",
408
+ async () => {
409
+ await ready;
410
+ return provider.get();
411
+ },
412
+ { authority }
413
+ );
414
+ addListener(
415
+ "filter-pro/targets",
416
+ async () => {
417
+ await refreshPluginTargets();
418
+ return pluginResolver.list();
419
+ },
420
+ { authority }
421
+ );
422
+ addListener(
423
+ "filter-pro/create",
424
+ async (input) => {
425
+ await ready;
426
+ const rule = normalizeRule(input || {});
427
+ state.rules.push(rule);
428
+ await refresh();
429
+ return cloneRule(rule);
430
+ },
431
+ { authority }
432
+ );
433
+ addListener(
434
+ "filter-pro/update",
435
+ async (input) => {
436
+ await ready;
437
+ if (!input?.id) return null;
438
+ const index = state.rules.findIndex((item) => item.id === input.id);
439
+ if (index < 0) return null;
440
+ const prev = state.rules[index];
441
+ state.rules[index] = normalizeRule({ ...prev, ...input, id: prev.id });
442
+ await refresh();
443
+ return cloneRule(state.rules[index]);
444
+ },
445
+ { authority }
446
+ );
447
+ addListener(
448
+ "filter-pro/delete",
449
+ async (id) => {
450
+ await ready;
451
+ const index = state.rules.findIndex((item) => item.id === id);
452
+ if (index < 0) return false;
453
+ state.rules.splice(index, 1);
454
+ await refresh();
455
+ return true;
456
+ },
457
+ { authority }
458
+ );
459
+ addListener(
460
+ "filter-pro/reorder",
461
+ async (ids) => {
462
+ await ready;
463
+ if (!Array.isArray(ids)) return provider.get();
464
+ const rank = new Map(ids.map((id, index) => [id, index]));
465
+ state.rules.sort((a, b) => {
466
+ const ai = rank.has(a.id) ? rank.get(a.id) : Number.MAX_SAFE_INTEGER;
467
+ const bi = rank.has(b.id) ? rank.get(b.id) : Number.MAX_SAFE_INTEGER;
468
+ return ai - bi;
469
+ });
470
+ for (let i = 0; i < state.rules.length; i++) state.rules[i].priority = i;
471
+ await refresh();
472
+ return provider.get();
473
+ },
474
+ { authority }
475
+ );
476
+ addListener(
477
+ "filter-pro/toggle",
478
+ async (payload) => {
479
+ await ready;
480
+ if (!payload?.id) return null;
481
+ const item = state.rules.find((rule) => rule.id === payload.id);
482
+ if (!item) return null;
483
+ item.enabled = !!payload.enabled;
484
+ await refresh();
485
+ return cloneRule(item);
486
+ },
487
+ { authority }
488
+ );
489
+ });
490
+ }
491
+ __name(apply, "apply");
492
+ // Annotate the CommonJS export names for ESM import in node:
493
+ 0 && (module.exports = {
494
+ Config,
495
+ apply,
496
+ name
497
+ });
package/lib/index.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { Awaitable, Context, Schema } from "koishi";
2
+ import { DataService } from "@koishijs/plugin-console";
3
+
4
+ //#region src/index.d.ts
5
+ declare const name = "filter-pro";
6
+ type RuleAction = 'bypass' | 'block';
7
+ type GroupOperator = 'and' | 'or';
8
+ type CompareOperator = 'eq' | 'ne' | 'includes' | 'regex' | 'gt' | 'gte' | 'lt' | 'lte' | 'exists';
9
+ type TargetType = 'global' | 'plugin';
10
+ interface RuleTarget {
11
+ type: TargetType;
12
+ value?: string;
13
+ }
14
+ interface GroupExpr {
15
+ type: 'group';
16
+ operator: GroupOperator;
17
+ children: RuleExpr[];
18
+ }
19
+ interface NotExpr {
20
+ type: 'not';
21
+ child: RuleExpr;
22
+ }
23
+ interface CompareExpr {
24
+ type: 'compare';
25
+ field: string;
26
+ operator: CompareOperator;
27
+ value?: unknown;
28
+ }
29
+ type RuleExpr = GroupExpr | NotExpr | CompareExpr;
30
+ interface RuleItem {
31
+ id: string;
32
+ name: string;
33
+ enabled: boolean;
34
+ priority: number;
35
+ action: RuleAction;
36
+ target: RuleTarget;
37
+ condition: RuleExpr;
38
+ response?: string;
39
+ }
40
+ interface Config {
41
+ filename?: string;
42
+ }
43
+ declare const Config: Schema<Config>;
44
+ interface RuleInput {
45
+ id?: string;
46
+ name?: string;
47
+ enabled?: boolean;
48
+ priority?: number;
49
+ action?: RuleAction;
50
+ target?: RuleTarget;
51
+ condition?: RuleExpr;
52
+ response?: string;
53
+ }
54
+ interface RuleState {
55
+ rules: RuleItem[];
56
+ }
57
+ interface PluginTargetOption {
58
+ key: string;
59
+ name: string;
60
+ ident: string;
61
+ label: string;
62
+ }
63
+ declare module '@koishijs/plugin-console' {
64
+ namespace Console {
65
+ interface Services {
66
+ 'filter-pro': FilterProProvider;
67
+ }
68
+ interface Events {
69
+ 'filter-pro/list': () => Awaitable<RuleItem[]>;
70
+ 'filter-pro/targets': () => Awaitable<PluginTargetOption[]>;
71
+ 'filter-pro/create': (input: RuleInput) => Awaitable<RuleItem>;
72
+ 'filter-pro/update': (input: RuleInput) => Awaitable<RuleItem | null>;
73
+ 'filter-pro/delete': (id: string) => Awaitable<boolean>;
74
+ 'filter-pro/reorder': (ids: string[]) => Awaitable<RuleItem[]>;
75
+ 'filter-pro/toggle': (payload: {
76
+ id: string;
77
+ enabled: boolean;
78
+ }) => Awaitable<RuleItem | null>;
79
+ }
80
+ }
81
+ }
82
+ declare class FilterProProvider extends DataService<RuleItem[]> {
83
+ private state;
84
+ constructor(ctx: Context, state: RuleState);
85
+ get(): Promise<RuleItem[]>;
86
+ }
87
+ declare function apply(ctx: Context, config?: Config): void;
88
+ //#endregion
89
+ export { CompareExpr, CompareOperator, Config, GroupExpr, GroupOperator, NotExpr, RuleAction, RuleExpr, RuleItem, RuleTarget, TargetType, apply, name };
package/lib/index.mjs ADDED
@@ -0,0 +1,473 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import { Random, Schema } from "koishi";
6
+ import { DataService } from "@koishijs/plugin-console";
7
+ import { resolve, join } from "node:path";
8
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
9
+ var name = "filter-pro";
10
+ var Config = Schema.object({
11
+ filename: Schema.string().default("rules.json").description("持久化文件名(位于 data/filterpro/ 下)。")
12
+ });
13
+ var kRecord = /* @__PURE__ */ Symbol.for("koishi.loader.record");
14
+ var FilterProProvider = class extends DataService {
15
+ constructor(ctx, state) {
16
+ super(ctx, "filter-pro");
17
+ this.state = state;
18
+ }
19
+ static {
20
+ __name(this, "FilterProProvider");
21
+ }
22
+ async get() {
23
+ return sortRules(this.state.rules).map(cloneRule);
24
+ }
25
+ };
26
+ function defaultExpr() {
27
+ return {
28
+ type: "group",
29
+ operator: "and",
30
+ children: [{ type: "compare", field: "guildId", operator: "eq", value: "" }]
31
+ };
32
+ }
33
+ __name(defaultExpr, "defaultExpr");
34
+ function cloneRule(rule) {
35
+ return {
36
+ id: rule.id,
37
+ name: rule.name,
38
+ enabled: rule.enabled,
39
+ priority: rule.priority,
40
+ action: rule.action,
41
+ target: { ...rule.target },
42
+ condition: cloneExpr(rule.condition),
43
+ response: rule.response
44
+ };
45
+ }
46
+ __name(cloneRule, "cloneRule");
47
+ function cloneExpr(expr) {
48
+ if (expr.type === "group") {
49
+ return {
50
+ type: "group",
51
+ operator: expr.operator,
52
+ children: expr.children.map(cloneExpr)
53
+ };
54
+ }
55
+ if (expr.type === "not") {
56
+ return { type: "not", child: cloneExpr(expr.child) };
57
+ }
58
+ return {
59
+ type: "compare",
60
+ field: expr.field,
61
+ operator: expr.operator,
62
+ value: expr.value
63
+ };
64
+ }
65
+ __name(cloneExpr, "cloneExpr");
66
+ function sortRules(rules) {
67
+ return [...rules].sort(
68
+ (a, b) => a.priority - b.priority || a.id.localeCompare(b.id)
69
+ );
70
+ }
71
+ __name(sortRules, "sortRules");
72
+ function normalizeExpr(input) {
73
+ if (!input || typeof input !== "object") return defaultExpr();
74
+ const data = input;
75
+ if (data.type === "group") {
76
+ const operator2 = data.operator === "or" ? "or" : "and";
77
+ const children = Array.isArray(data.children) ? data.children.map(normalizeExpr) : [];
78
+ return { type: "group", operator: operator2, children };
79
+ }
80
+ if (data.type === "not") {
81
+ return { type: "not", child: normalizeExpr(data.child) };
82
+ }
83
+ const allowed = [
84
+ "eq",
85
+ "ne",
86
+ "includes",
87
+ "regex",
88
+ "gt",
89
+ "gte",
90
+ "lt",
91
+ "lte",
92
+ "exists"
93
+ ];
94
+ const operator = allowed.includes(data.operator) ? data.operator : "eq";
95
+ return {
96
+ type: "compare",
97
+ field: typeof data.field === "string" ? data.field : "content",
98
+ operator,
99
+ value: data.value
100
+ };
101
+ }
102
+ __name(normalizeExpr, "normalizeExpr");
103
+ function normalizeRule(input) {
104
+ const rawTarget = input.target || { type: "global" };
105
+ const rawValue = typeof rawTarget.value === "string" ? rawTarget.value.trim() : "";
106
+ const normalizedValue = rawTarget.type === "plugin" ? rawValue : "";
107
+ const target = {
108
+ type: rawTarget.type === "plugin" ? "plugin" : "global",
109
+ value: normalizedValue
110
+ };
111
+ return {
112
+ id: input.id || Random.id(8),
113
+ name: input.name || "new-rule",
114
+ enabled: input.enabled ?? true,
115
+ priority: input.priority ?? 0,
116
+ action: input.action || "block",
117
+ target,
118
+ condition: normalizeExpr(input.condition),
119
+ response: input.response ?? ""
120
+ };
121
+ }
122
+ __name(normalizeRule, "normalizeRule");
123
+ function matchTarget(target, vars) {
124
+ if (!target || target.type === "global") return true;
125
+ const pluginKey = typeof vars.pluginKey === "string" ? vars.pluginKey : "";
126
+ const value = target.value?.trim();
127
+ if (!value) return false;
128
+ return pluginKey === value;
129
+ }
130
+ __name(matchTarget, "matchTarget");
131
+ function collectPluginTargets(ctx) {
132
+ const result = [];
133
+ const dedup = /* @__PURE__ */ new Set();
134
+ const visited = /* @__PURE__ */ new Set();
135
+ const walk = /* @__PURE__ */ __name((cursor) => {
136
+ const record = cursor?.scope?.[kRecord];
137
+ if (!record || visited.has(record)) return;
138
+ visited.add(record);
139
+ for (const [fullKey, fork] of Object.entries(record)) {
140
+ const key = String(fullKey).replace(/^~/, "");
141
+ const [name2, ident = ""] = key.split(":", 2);
142
+ if (name2 && name2 !== "group" && !dedup.has(key)) {
143
+ dedup.add(key);
144
+ result.push({
145
+ key,
146
+ name: name2,
147
+ ident,
148
+ label: ident ? `${name2}:${ident}` : name2
149
+ });
150
+ }
151
+ walk(fork?.ctx);
152
+ }
153
+ }, "walk");
154
+ walk(ctx.root);
155
+ return result.sort((a, b) => a.key.localeCompare(b.key));
156
+ }
157
+ __name(collectPluginTargets, "collectPluginTargets");
158
+ function createPluginResolver(ctx) {
159
+ let targets = [];
160
+ const byScope = /* @__PURE__ */ new Map();
161
+ const rebuild = /* @__PURE__ */ __name(() => {
162
+ targets = collectPluginTargets(ctx);
163
+ byScope.clear();
164
+ const visited = /* @__PURE__ */ new Set();
165
+ const walk = /* @__PURE__ */ __name((cursor) => {
166
+ const record = cursor?.scope?.[kRecord];
167
+ if (!record || visited.has(record)) return;
168
+ visited.add(record);
169
+ for (const [fullKey, fork] of Object.entries(record)) {
170
+ const key = String(fullKey).replace(/^~/, "");
171
+ const target = targets.find((item) => item.key === key);
172
+ if (target && fork?.ctx?.scope) {
173
+ byScope.set(fork.ctx.scope, target);
174
+ }
175
+ walk(fork?.ctx);
176
+ }
177
+ }, "walk");
178
+ walk(ctx.root);
179
+ }, "rebuild");
180
+ const resolveByCommand = /* @__PURE__ */ __name((command) => {
181
+ let scope = command.caller.scope;
182
+ while (scope) {
183
+ const found = byScope.get(scope);
184
+ if (found) return found;
185
+ scope = scope.parent?.scope;
186
+ }
187
+ }, "resolveByCommand");
188
+ return {
189
+ rebuild,
190
+ list: /* @__PURE__ */ __name(() => targets, "list"),
191
+ resolveByCommand
192
+ };
193
+ }
194
+ __name(createPluginResolver, "createPluginResolver");
195
+ function getByPath(source, path) {
196
+ if (!path) return void 0;
197
+ const parts = path.split(".");
198
+ let cursor = source;
199
+ for (const part of parts) {
200
+ if (!cursor || typeof cursor !== "object") return void 0;
201
+ cursor = cursor[part];
202
+ }
203
+ return cursor;
204
+ }
205
+ __name(getByPath, "getByPath");
206
+ function coerceNumber(value) {
207
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
208
+ if (typeof value === "string" && value.trim()) {
209
+ const parsed = Number(value);
210
+ return Number.isFinite(parsed) ? parsed : null;
211
+ }
212
+ return null;
213
+ }
214
+ __name(coerceNumber, "coerceNumber");
215
+ function evaluateCompare(left, expr) {
216
+ if (expr.operator === "exists") {
217
+ return left !== void 0 && left !== null;
218
+ }
219
+ const right = expr.value;
220
+ if (expr.operator === "eq") return left === right;
221
+ if (expr.operator === "ne") return left !== right;
222
+ if (expr.operator === "includes") {
223
+ if (typeof left === "string") return left.includes(String(right ?? ""));
224
+ if (Array.isArray(left)) return left.includes(right);
225
+ return false;
226
+ }
227
+ if (expr.operator === "regex") {
228
+ if (typeof left !== "string") return false;
229
+ try {
230
+ return new RegExp(String(right ?? "")).test(left);
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
235
+ const ln = coerceNumber(left);
236
+ const rn = coerceNumber(right);
237
+ if (ln === null || rn === null) return false;
238
+ if (expr.operator === "gt") return ln > rn;
239
+ if (expr.operator === "gte") return ln >= rn;
240
+ if (expr.operator === "lt") return ln < rn;
241
+ if (expr.operator === "lte") return ln <= rn;
242
+ return false;
243
+ }
244
+ __name(evaluateCompare, "evaluateCompare");
245
+ function evaluateExpr(expr, vars) {
246
+ if (expr.type === "group") {
247
+ if (!expr.children.length) return true;
248
+ if (expr.operator === "and")
249
+ return expr.children.every((child) => evaluateExpr(child, vars));
250
+ return expr.children.some((child) => evaluateExpr(child, vars));
251
+ }
252
+ if (expr.type === "not") {
253
+ return !evaluateExpr(expr.child, vars);
254
+ }
255
+ return evaluateCompare(getByPath(vars, expr.field), expr);
256
+ }
257
+ __name(evaluateExpr, "evaluateExpr");
258
+ async function readRules(file) {
259
+ try {
260
+ const raw = await readFile(file, "utf8");
261
+ const parsed = JSON.parse(raw);
262
+ if (!Array.isArray(parsed)) return [];
263
+ return parsed.map((item) => normalizeRule(item));
264
+ } catch {
265
+ return null;
266
+ }
267
+ }
268
+ __name(readRules, "readRules");
269
+ function createPersister(file) {
270
+ let queue = Promise.resolve();
271
+ return async (rules) => {
272
+ queue = queue.catch(() => {
273
+ }).then(async () => {
274
+ await writeFile(
275
+ file,
276
+ JSON.stringify(rules.map(cloneRule), null, 2),
277
+ "utf8"
278
+ );
279
+ });
280
+ await queue;
281
+ };
282
+ }
283
+ __name(createPersister, "createPersister");
284
+ function apply(ctx, config = {}) {
285
+ const baseDir = ctx.baseDir || process.cwd();
286
+ const dataDir = join(baseDir, "data", "filterpro");
287
+ const dataFile = join(dataDir, config.filename || "rules.json");
288
+ const state = { rules: [] };
289
+ const persist = createPersister(dataFile);
290
+ const pluginResolver = createPluginResolver(ctx);
291
+ const initialize = /* @__PURE__ */ __name(async () => {
292
+ await mkdir(dataDir, { recursive: true });
293
+ const loaded = await readRules(dataFile);
294
+ if (loaded) {
295
+ state.rules = loaded;
296
+ return;
297
+ }
298
+ state.rules = [];
299
+ await persist(state.rules);
300
+ }, "initialize");
301
+ const ready = initialize().catch((error) => {
302
+ ctx.logger("filter-pro").warn("failed to initialize persistent rules: %s", String(error));
303
+ state.rules = [];
304
+ });
305
+ const refreshPluginTargets = /* @__PURE__ */ __name(async () => {
306
+ await ready;
307
+ pluginResolver.rebuild();
308
+ }, "refreshPluginTargets");
309
+ void refreshPluginTargets();
310
+ ctx.on("ready", refreshPluginTargets);
311
+ ctx.on("internal/fork", refreshPluginTargets);
312
+ ctx.on("internal/update", refreshPluginTargets);
313
+ ctx.on("internal/runtime", refreshPluginTargets);
314
+ ctx.middleware(async (session, next) => {
315
+ await ready;
316
+ const vars = {
317
+ platform: session.platform,
318
+ selfId: session.selfId,
319
+ userId: session.userId,
320
+ channelId: session.channelId,
321
+ guildId: session.guildId,
322
+ isDirect: session.isDirect,
323
+ content: session.content,
324
+ subtype: session.subtype,
325
+ event: session.event,
326
+ author: session.author,
327
+ quote: session.quote
328
+ };
329
+ for (const rule of sortRules(state.rules)) {
330
+ if (!rule.enabled) continue;
331
+ if (rule.target.type !== "global") continue;
332
+ if (!matchTarget(rule.target, vars)) continue;
333
+ const matched = evaluateExpr(rule.condition, vars);
334
+ if (!matched) continue;
335
+ if (rule.action === "bypass") return next();
336
+ if (rule.response) return rule.response;
337
+ return "";
338
+ }
339
+ return next();
340
+ }, true);
341
+ ctx.before("command/execute", async (argv) => {
342
+ await ready;
343
+ const plugin = pluginResolver.resolveByCommand(argv.command);
344
+ const vars = {
345
+ platform: argv.session.platform,
346
+ selfId: argv.session.selfId,
347
+ userId: argv.session.userId,
348
+ channelId: argv.session.channelId,
349
+ guildId: argv.session.guildId,
350
+ isDirect: argv.session.isDirect,
351
+ content: argv.session.content,
352
+ subtype: argv.session.subtype,
353
+ event: argv.session.event,
354
+ author: argv.session.author,
355
+ quote: argv.session.quote,
356
+ commandName: argv.command.name,
357
+ pluginKey: plugin?.key,
358
+ pluginName: plugin?.name
359
+ };
360
+ for (const rule of sortRules(state.rules)) {
361
+ if (!rule.enabled) continue;
362
+ if (rule.target.type === "global") continue;
363
+ if (!matchTarget(rule.target, vars)) continue;
364
+ const matched = evaluateExpr(rule.condition, vars);
365
+ if (!matched) continue;
366
+ if (rule.action === "bypass") return;
367
+ if (rule.response) return rule.response;
368
+ return "";
369
+ }
370
+ });
371
+ ctx.inject(["console"], (ctx2) => {
372
+ ctx2.console.addEntry({
373
+ dev: resolve(__dirname, "../client/index.ts"),
374
+ prod: resolve(__dirname, "../dist")
375
+ });
376
+ const provider = new FilterProProvider(ctx2, state);
377
+ const authority = 3;
378
+ const addListener = ctx2.console.addListener.bind(ctx2.console);
379
+ const refresh = /* @__PURE__ */ __name(async () => {
380
+ await persist(state.rules);
381
+ provider.refresh();
382
+ }, "refresh");
383
+ addListener(
384
+ "filter-pro/list",
385
+ async () => {
386
+ await ready;
387
+ return provider.get();
388
+ },
389
+ { authority }
390
+ );
391
+ addListener(
392
+ "filter-pro/targets",
393
+ async () => {
394
+ await refreshPluginTargets();
395
+ return pluginResolver.list();
396
+ },
397
+ { authority }
398
+ );
399
+ addListener(
400
+ "filter-pro/create",
401
+ async (input) => {
402
+ await ready;
403
+ const rule = normalizeRule(input || {});
404
+ state.rules.push(rule);
405
+ await refresh();
406
+ return cloneRule(rule);
407
+ },
408
+ { authority }
409
+ );
410
+ addListener(
411
+ "filter-pro/update",
412
+ async (input) => {
413
+ await ready;
414
+ if (!input?.id) return null;
415
+ const index = state.rules.findIndex((item) => item.id === input.id);
416
+ if (index < 0) return null;
417
+ const prev = state.rules[index];
418
+ state.rules[index] = normalizeRule({ ...prev, ...input, id: prev.id });
419
+ await refresh();
420
+ return cloneRule(state.rules[index]);
421
+ },
422
+ { authority }
423
+ );
424
+ addListener(
425
+ "filter-pro/delete",
426
+ async (id) => {
427
+ await ready;
428
+ const index = state.rules.findIndex((item) => item.id === id);
429
+ if (index < 0) return false;
430
+ state.rules.splice(index, 1);
431
+ await refresh();
432
+ return true;
433
+ },
434
+ { authority }
435
+ );
436
+ addListener(
437
+ "filter-pro/reorder",
438
+ async (ids) => {
439
+ await ready;
440
+ if (!Array.isArray(ids)) return provider.get();
441
+ const rank = new Map(ids.map((id, index) => [id, index]));
442
+ state.rules.sort((a, b) => {
443
+ const ai = rank.has(a.id) ? rank.get(a.id) : Number.MAX_SAFE_INTEGER;
444
+ const bi = rank.has(b.id) ? rank.get(b.id) : Number.MAX_SAFE_INTEGER;
445
+ return ai - bi;
446
+ });
447
+ for (let i = 0; i < state.rules.length; i++) state.rules[i].priority = i;
448
+ await refresh();
449
+ return provider.get();
450
+ },
451
+ { authority }
452
+ );
453
+ addListener(
454
+ "filter-pro/toggle",
455
+ async (payload) => {
456
+ await ready;
457
+ if (!payload?.id) return null;
458
+ const item = state.rules.find((rule) => rule.id === payload.id);
459
+ if (!item) return null;
460
+ item.enabled = !!payload.enabled;
461
+ await refresh();
462
+ return cloneRule(item);
463
+ },
464
+ { authority }
465
+ );
466
+ });
467
+ }
468
+ __name(apply, "apply");
469
+ export {
470
+ Config,
471
+ apply,
472
+ name
473
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "koishi-plugin-filter-pro",
3
+ "description": "Ruleset middleware plugin for Koishi",
4
+ "version": "1.0.0",
5
+ "main": "lib/index.cjs",
6
+ "module": "lib/index.mjs",
7
+ "types": "lib/index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/index.d.ts",
12
+ "require": "./lib/index.cjs",
13
+ "import": "./lib/index.mjs"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "lib",
19
+ "dist"
20
+ ],
21
+ "license": "MIT",
22
+ "scripts": {
23
+ "build": "yarn rolldown -c rolldown.config.js",
24
+ "publish": "yarn npm publish",
25
+ "lint": "biome check && biome lint",
26
+ "lint-fix": "biome format --write && biome lint --write"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Hoshino-Yumetsuki/koishi-plugin-filter-pro",
31
+ "directory": "/"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/Hoshino-Yumetsuki/koishi-plugin-filter-pro/issues"
35
+ },
36
+ "homepage": "https://github.com/Hoshino-Yumetsuki/koishi-plugin-filter-pro",
37
+ "keywords": [
38
+ "bot",
39
+ "chatbot",
40
+ "koishi",
41
+ "plugin",
42
+ "ruleset",
43
+ "filter",
44
+ "middleware"
45
+ ],
46
+ "koishi": {
47
+ "browser": true,
48
+ "category": "tool"
49
+ },
50
+ "peerDependencies": {
51
+ "@koishijs/plugin-console": "^5.11.0",
52
+ "koishi": "^4.18.10"
53
+ },
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^2.3.14",
56
+ "@koishijs/client": "^5.11.0",
57
+ "@koishijs/plugin-console": "^5.30.11",
58
+ "@koishijs/plugin-proxy-agent": "^0.3.3",
59
+ "@types/node": "^25.2.1",
60
+ "@vitejs/plugin-vue": "^6.0.4",
61
+ "koishi": "^4.18.10",
62
+ "rolldown": "^1.0.0-rc.3",
63
+ "rolldown-plugin-dts": "^0.22.1"
64
+ }
65
+ }