仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ app_plugins/webhook_notify/bootstrap.php 7.7 KB · 246 行 原始文件
Z zfhsh first commit 1 天前
1<?php
2if (!defined('IN_CRONLITE')) {
3 exit;
4}
5
6mnbt_plugin_register('webhook_notify', ['name' => 'Webhook 通知']);
7
8mnbt_register_menu('admin', [
9 'title' => 'Webhook 通知',
10 'page' => 'settings',
11 'icon' => 'mdi-webhook',
12 'order' => 20,
13 'multitabs' => true,
14]);
15
16mnbt_register_page('admin', 'settings', 'admin/settings.php', 'Webhook 通知');
17mnbt_register_settings_tab([
18 'title' => 'Webhook 通知',
19 'page' => 'settings',
20 'order' => 20,
21]);
22
23mnbt_register_widget('admin', [
24 'title' => 'Webhook 状态',
25 'order' => 10,
26 'class' => 'col-sm-6',
27 'callback' => function () {
28 $url = (string)mnbt_plugin_option_get('webhook_notify', 'url', '');
29 $enabled = mnbt_plugin_option_get('webhook_notify', 'enabled', 'true') === 'true';
30 $logs = mnbt_plugin_option_get('webhook_notify', 'delivery_log', []);
31 $last = is_array($logs) && $logs ? $logs[0] : null;
32 echo '<p class="mb-1">状态:' . ($enabled && $url !== '' ? '<span class="text-success">已配置</span>' : '<span class="text-muted">未配置</span>') . '</p>';
33 echo '<p class="mb-1 small text-muted">URL:' . htmlspecialchars($url !== '' ? $url : '(空)', ENT_QUOTES, 'UTF-8') . '</p>';
34 if ($last) {
35 echo '<p class="mb-0 small">最近:' . htmlspecialchars((string)$last, ENT_QUOTES, 'UTF-8') . '</p>';
36 }
37 echo '<p class="mt-2 mb-0"><a class="btn btn-sm btn-outline-primary multitabs" href="plugin.php?p=webhook_notify&page=settings">打开设置</a></p>';
38 },
39]);
40
41/**
42 * @return array{url:string,secret:string,enabled:bool,events:array}
43 */
44function webhook_notify_config()
45{
46 $events = mnbt_plugin_option_get('webhook_notify', 'events', null);
47 if (!is_array($events)) {
48 $events = [
49 'host.created' => true,
50 'host.paused' => true,
51 'host.unpaused' => true,
52 'host.renewed' => true,
53 'host.deleted' => true,
54 'order.paid' => true,
55 ];
56 }
57 return [
58 'url' => trim((string)mnbt_plugin_option_get('webhook_notify', 'url', '')),
59 'secret' => (string)mnbt_plugin_option_get('webhook_notify', 'secret', ''),
60 'enabled' => mnbt_plugin_option_get('webhook_notify', 'enabled', 'true') === 'true',
61 'events' => $events,
62 ];
63}
64
65function webhook_notify_log_line($line)
66{
67 $log = mnbt_plugin_option_get('webhook_notify', 'delivery_log', []);
68 if (!is_array($log)) {
69 $log = [];
70 }
71 array_unshift($log, date('Y-m-d H:i:s') . ' ' . $line);
72 $log = array_slice($log, 0, 40);
73 mnbt_plugin_option_set('webhook_notify', 'delivery_log', $log);
74}
75
76function webhook_notify_sanitize_host($host)
77{
78 if (!is_array($host)) {
79 return [];
80 }
81 $keep = ['id', 'user', 'ssbt', 'btid', 'sqldz', 'datae', 'qk', 'hxc', 'ymbds', 'data'];
82 $out = [];
83 foreach ($keep as $k) {
84 if (array_key_exists($k, $host)) {
85 $out[$k] = $host[$k];
86 }
87 }
88 return $out;
89}
90
91function webhook_notify_sanitize_order($order)
92{
93 if (!is_array($order)) {
94 return [];
95 }
96 $keep = ['id', 'ddh', 'je', 'lx', 'zffs', 'qk', 'date', 'cs', 'ip'];
97 $out = [];
98 foreach ($keep as $k) {
99 if (array_key_exists($k, $order)) {
100 $out[$k] = $order[$k];
101 }
102 }
103 return $out;
104}
105
106function webhook_notify_dispatch($event, $payload)
107{
108 $cfg = webhook_notify_config();
109 if (!$cfg['enabled'] || $cfg['url'] === '') {
110 return;
111 }
112 $events = $cfg['events'];
113 if (isset($events[$event]) && !$events[$event]) {
114 return;
115 }
116 $body = [
117 'event' => $event,
118 'time' => date('c'),
119 'source' => 'mnbt',
120 'payload' => $payload,
121 ];
122 $json = json_encode($body, JSON_UNESCAPED_UNICODE);
123 $headers = ['Content-Type: application/json; charset=utf-8', 'X-MNBT-Event: ' . $event];
124 if ($cfg['secret'] !== '') {
125 $sig = hash_hmac('sha256', $json, $cfg['secret']);
126 $headers[] = 'X-MNBT-Signature: sha256=' . $sig;
127 }
128 $res = mnbt_http_post($cfg['url'], $json, [
129 'timeout' => 10,
130 'headers' => $headers,
131 'insecure' => mnbt_plugin_option_get('webhook_notify', 'insecure_ssl', 'false') === 'true',
132 ]);
133 if ($res['ok']) {
134 webhook_notify_log_line('OK ' . $event . ' HTTP ' . $res['code']);
135 } else {
136 webhook_notify_log_line('FAIL ' . $event . ' ' . ($res['error'] ?: ('HTTP ' . $res['code'])));
137 }
138}
139
140mnbt_add_action('host.created', function ($host, $ctx = []) {
141 webhook_notify_dispatch('host.created', [
142 'host' => webhook_notify_sanitize_host($host),
143 'ctx' => is_array($ctx) ? $ctx : [],
144 ]);
145});
146
147mnbt_add_action('host.paused', function ($host, $ctx = []) {
148 webhook_notify_dispatch('host.paused', [
149 'host' => webhook_notify_sanitize_host($host),
150 'ctx' => is_array($ctx) ? $ctx : [],
151 ]);
152});
153
154mnbt_add_action('host.unpaused', function ($host, $ctx = []) {
155 webhook_notify_dispatch('host.unpaused', [
156 'host' => webhook_notify_sanitize_host($host),
157 'ctx' => is_array($ctx) ? $ctx : [],
158 ]);
159});
160
161mnbt_add_action('host.renewed', function ($host, $ctx = []) {
162 webhook_notify_dispatch('host.renewed', [
163 'host' => webhook_notify_sanitize_host($host),
164 'ctx' => is_array($ctx) ? $ctx : [],
165 ]);
166});
167
168mnbt_add_action('host.deleted', function ($host, $ctx = []) {
169 webhook_notify_dispatch('host.deleted', [
170 'host' => webhook_notify_sanitize_host($host),
171 'ctx' => is_array($ctx) ? $ctx : [],
172 ]);
173});
174
175mnbt_add_action('order.paid', function ($order, $ctx = []) {
176 webhook_notify_dispatch('order.paid', [
177 'order' => webhook_notify_sanitize_order($order),
178 'ctx' => is_array($ctx) ? $ctx : [],
179 ]);
180});
181
182mnbt_register_ajax('admin', 'p_webhook_notify_save', function () {
183 mnbt_plugin_require_admin();
184 $url = trim((string)($_POST['url'] ?? ''));
185 $secret = (string)($_POST['secret'] ?? '');
186 $enabled = (($_POST['enabled'] ?? '') === 'true' || ($_POST['enabled'] ?? '') === '1') ? 'true' : 'false';
187 $insecure = (($_POST['insecure_ssl'] ?? '') === 'true' || ($_POST['insecure_ssl'] ?? '') === '1') ? 'true' : 'false';
188 if ($url !== '' && !preg_match('#^https?://#i', $url)) {
189 json_exit_error('Webhook URL 必须以 http:// 或 https:// 开头');
190 }
191 if (mb_strlen($url) > 500) {
192 json_exit_error('URL 过长');
193 }
194 $eventKeys = ['host.created', 'host.paused', 'host.unpaused', 'host.renewed', 'host.deleted', 'order.paid'];
195 $events = [];
196 $raw = $_POST['events'] ?? '';
197 if (is_string($raw) && $raw !== '') {
198 $decoded = json_decode($raw, true);
199 if (is_array($decoded)) {
200 foreach ($eventKeys as $k) {
201 $events[$k] = !empty($decoded[$k]);
202 }
203 }
204 }
205 if (!$events) {
206 foreach ($eventKeys as $k) {
207 $events[$k] = true;
208 }
209 }
210 mnbt_plugin_option_set('webhook_notify', 'url', $url);
211 mnbt_plugin_option_set('webhook_notify', 'secret', $secret);
212 mnbt_plugin_option_set('webhook_notify', 'enabled', $enabled);
213 mnbt_plugin_option_set('webhook_notify', 'insecure_ssl', $insecure);
214 mnbt_plugin_option_set('webhook_notify', 'events', $events);
215 json_exit_success('已保存');
216});
217
218mnbt_register_ajax('admin', 'p_webhook_notify_test', function () {
219 mnbt_plugin_require_admin();
220 $cfg = webhook_notify_config();
221 if ($cfg['url'] === '') {
222 json_exit_error('请先填写 Webhook URL 并保存');
223 }
224 $body = [
225 'event' => 'webhook.test',
226 'time' => date('c'),
227 'source' => 'mnbt',
228 'payload' => ['message' => 'MNBT Webhook 测试', 'plugin' => 'webhook_notify'],
229 ];
230 $json = json_encode($body, JSON_UNESCAPED_UNICODE);
231 $headers = ['Content-Type: application/json; charset=utf-8', 'X-MNBT-Event: webhook.test'];
232 if ($cfg['secret'] !== '') {
233 $headers[] = 'X-MNBT-Signature: sha256=' . hash_hmac('sha256', $json, $cfg['secret']);
234 }
235 $res = mnbt_http_post($cfg['url'], $json, [
236 'timeout' => 10,
237 'headers' => $headers,
238 'insecure' => mnbt_plugin_option_get('webhook_notify', 'insecure_ssl', 'false') === 'true',
239 ]);
240 if ($res['ok']) {
241 webhook_notify_log_line('OK webhook.test HTTP ' . $res['code']);
242 json_exit_success('测试成功 HTTP ' . $res['code'], ['body' => mb_substr($res['body'], 0, 200)]);
243 }
244 webhook_notify_log_line('FAIL webhook.test ' . ($res['error'] ?: ('HTTP ' . $res['code'])));
245 json_exit_error('测试失败:' . ($res['error'] ?: ('HTTP ' . $res['code'])));
246});