仰望星辰工作室

clearlove2.1

clearlove2.1/ internal/plugin/hub.go 6.1 KB · 246 行 原始文件
1// 事件总线与过滤器链。
2//
3// 同时服务两种形态:
4// - A 型:http 钩子(Notify)与 html/filter 静态片段(CallHTML/CallFilter)
5// - B 型:clv.on / clv.slot / clv.filter / clv.middleware 注册的脚本回调
6//
7// 约定:插件故障不影响站点 —— 事件与中间件失败只记日志,过滤器失败放行原值。
8package plugin
9
10import (
11 "context"
12
13 "clearlove/internal/models"
14)
15
16func withMeta(event string, payload map[string]any) map[string]any {
17 out := make(map[string]any, len(payload)+2)
18 for k, v := range payload {
19 out[k] = v
20 }
21 out["event"] = event
22 out["time"] = models.Now()
23 return out
24}
25
26// Emit 广播事件(A 型 Webhook + B 型 clv.on),B 型回调异步执行
27func Emit(event string, payload map[string]any) {
28 // A 型:现有 Webhook 行为保持不变
29 Notify(event, payload)
30
31 for _, h := range EventHooks(event) {
32 h := h
33 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
34 continue
35 }
36 go func() {
37 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
38 defer cancel()
39 _, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
40 _, err := rt.callFn(h.Fn, withMeta(event, payload))
41 return nil, err
42 })
43 if err != nil {
44 noteFailure(h.App, err)
45 return
46 }
47 noteSuccess(h.App)
48 }()
49 }
50}
51
52// ApplyFilterStr 字符串过滤器(filter.content / filter.nickname 等)。
53// filter.content 会先执行 A 型声明式正则改写,再走 B 型运行时过滤器。
54func ApplyFilterStr(name, value string) string {
55 v := value
56 if name == "filter.content" {
57 v = CallFilter(v)
58 }
59 if len(FilterHooks(name)) == 0 {
60 return v
61 }
62 out := runFilterChain(name, v, nil)
63 if s, ok := out.(string); ok {
64 return s
65 }
66 return v
67}
68
69// ApplyFilterValue 通用过滤器(map / 布尔 等)
70func ApplyFilterValue(name string, value any, extra map[string]any) any {
71 if len(FilterHooks(name)) == 0 {
72 return value
73 }
74 return runFilterChain(name, value, extra)
75}
76
77// ApplyFilterBool 布尔过滤器(带上下文字段,如 filter.post.visible)
78func ApplyFilterBool(name string, value bool, extra map[string]any) bool {
79 if len(FilterHooks(name)) == 0 {
80 return value
81 }
82 out := runFilterChain(name, value, extra)
83 if b, ok := out.(bool); ok {
84 return b
85 }
86 return value
87}
88
89// FilterVeto 布尔型过滤器:任一插件返回 false 即否决
90func FilterVeto(name string, allow bool) bool {
91 for _, h := range FilterHooks(name) {
92 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
93 continue
94 }
95 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
96 v, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
97 return rt.callFn(h.Fn, allow)
98 })
99 cancel()
100 if err != nil {
101 noteFailure(h.App, err)
102 continue
103 }
104 noteSuccess(h.App)
105 switch t := v.(type) {
106 case bool:
107 if !t {
108 return false
109 }
110 case nil:
111 // 未表态
112 default:
113 if !boolOf(t) {
114 return false
115 }
116 }
117 }
118 return allow
119}
120
121func runFilterChain(name string, value any, extra map[string]any) any {
122 cur := value
123 for _, h := range FilterHooks(name) {
124 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
125 continue
126 }
127 h := h
128 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
129 out, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
130 if extra == nil {
131 return rt.callFn(h.Fn, cur)
132 }
133 return rt.callFn(h.Fn, cur, extra)
134 })
135 cancel()
136 if err != nil {
137 noteFailure(h.App, err)
138 continue // 放行原值,不因插件故障阻断站点
139 }
140 noteSuccess(h.App)
141 if out == nil {
142 continue // 返回 undefined 表示不改
143 }
144 cur = out
145 }
146 return cur
147}
148
149// SlotHTML 计算某个 UI 注入点的完整 HTML(A 型静态片段 + B 型动态渲染)
150func SlotHTML(name string, data any) string {
151 out := CallHTML(name)
152 for _, h := range SlotHooks(name) {
153 h := h
154 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
155 continue
156 }
157 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
158 v, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
159 return rt.callFn(h.Fn, data)
160 })
161 cancel()
162 if err != nil {
163 noteFailure(h.App, err)
164 continue
165 }
166 noteSuccess(h.App)
167 if v == nil {
168 continue
169 }
170 out += strOf(v)
171 }
172 return out
173}
174
175// RunMiddlewareAfter 执行 http.after 中间件(只观察,忽略返回值与异常细节)
176func RunMiddlewareAfter(req map[string]any) {
177 for _, h := range MiddlewareHooks("http.after") {
178 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
179 continue
180 }
181 h := h
182 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
183 _, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
184 rt.cur = nil
185 return rt.callFn(h.Fn, req)
186 })
187 cancel()
188 if err != nil {
189 noteFailure(h.App, err)
190 continue
191 }
192 noteSuccess(h.App)
193 }
194}
195
196// MiddlewareResult http.before 中间件的短路指令
197type MiddlewareResult struct {
198 Abort bool
199 Status int
200 Headers map[string]string
201 Body string
202 JSON any
203 Redirect string
204}
205
206// RunMiddlewareBefore 执行 http.before 中间件;返回 nil 表示继续正常处理
207func RunMiddlewareBefore(req map[string]any) *MiddlewareResult {
208 for _, h := range MiddlewareHooks("http.before") {
209 if h.App == nil || h.App.Worker == nil || h.App.Worker.Closed() {
210 continue
211 }
212 h := h
213 ctx, cancel := context.WithTimeout(context.Background(), h.App.Plugin.Timeout())
214 v, err := h.App.Worker.Do(ctx, func(rt *jsRuntime) (any, error) {
215 return rt.callFn(h.Fn, req)
216 })
217 cancel()
218 if err != nil {
219 noteFailure(h.App, err)
220 continue
221 }
222 noteSuccess(h.App)
223 m, ok := v.(map[string]any)
224 if !ok || !boolOf(m["abort"]) {
225 continue
226 }
227 res := &MiddlewareResult{
228 Abort: true,
229 Status: intOf(m["status"]),
230 Body: strOf(m["body"]),
231 JSON: m["json"],
232 Redirect: strOf(m["redirect"]),
233 }
234 if res.Status == 0 {
235 res.Status = 200
236 }
237 if hdrs, ok := m["headers"].(map[string]any); ok {
238 res.Headers = make(map[string]string, len(hdrs))
239 for k, hv := range hdrs {
240 res.Headers[k] = strOf(hv)
241 }
242 }
243 return res
244 }
245 return nil
246}