From 533e12a796b30e0a349304d35886006958d341ff Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 15 Oct 2023 20:42:05 +0800 Subject: [PATCH] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E6=8E=A8=E9=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89=20webhook=20=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sample/notify.js | 165 +++++++++++++++++++++++++++++++++++++++++++++++ sample/notify.py | 164 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 307 insertions(+), 22 deletions(-) diff --git a/sample/notify.js b/sample/notify.js index 82662745ec4..c13c22d6d57 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -11,6 +11,7 @@ */ const querystring = require('querystring'); +const got = require('got'); const $ = new Env(); const timeout = 15000; //超时时间(单位毫秒) // =======================================gotify通知设置区域============================================== @@ -159,6 +160,14 @@ let CHRONOCAT_URL = ''; // CHRONOCAT Red协议连接地址 let CHRONOCAT_TOKEN = ''; //CHRONOCAT 生成的访问密钥 let CHRONOCAT_QQ = ''; // 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如:user_id=xxx;group_id=xxxx;group_id=xxxxx +// =======================================自定义通知设置区域======================================= +// 自定义通知 接收回调的URL +let WEBHOOK_URL = ''; +let WEBHOOK_BODY = ''; +let WEBHOOK_HEADERS = ''; +let WEBHOOK_METHOD = ''; +let WEBHOOK_CONTENT_TYPE = ''; + //==========================云端环境变量的判断与接收========================= if (process.env.GOTIFY_URL) { GOTIFY_URL = process.env.GOTIFY_URL; @@ -325,6 +334,22 @@ if (process.env.CHRONOCAT_QQ) { if (process.env.CHRONOCAT_TOKEN) { CHRONOCAT_TOKEN = process.env.CHRONOCAT_TOKEN; } + +if (process.env.WEBHOOK_URL) { + WEBHOOK_URL = process.env.WEBHOOK_URL; +} +if (process.env.WEBHOOK_BODY) { + WEBHOOK_BODY = process.env.WEBHOOK_BODY; +} +if (process.env.WEBHOOK_HEADERS) { + WEBHOOK_HEADERS = process.env.WEBHOOK_HEADERS; +} +if (process.env.WEBHOOK_METHOD) { + WEBHOOK_METHOD = process.env.WEBHOOK_METHOD; +} +if (process.env.WEBHOOK_CONTENT_TYPE) { + WEBHOOK_CONTENT_TYPE = process.env.WEBHOOK_CONTENT_TYPE; +} //==========================云端环境变量的判断与接收========================= /** @@ -375,6 +400,7 @@ async function sendNotify( smtpNotify(text, desp), //SMTP 邮件 PushMeNotify(text, desp, params), //PushMe ChronocatNotify(text, desp), // Chronocat + webhookNotify(text, desp), //自定义通知 ]); } @@ -1266,6 +1292,145 @@ function ChronocatNotify(title, desp) { }); } +function webhookNotify(text, desp) { + return new Promise((resolve) => { + const { formatBody, formatUrl } = formatNotifyContentFun( + WEBHOOK_URL, + WEBHOOK_BODY, + text, + desp, + ); + if (!formatUrl && !formatBody) { + resolve(); + return; + } + const headers = parseHeaders(WEBHOOK_HEADERS); + const body = parseBody(formatBody, WEBHOOK_CONTENT_TYPE); + const bodyParam = formatBodyFun(WEBHOOK_CONTENT_TYPE, body); + const options = { + method: WEBHOOK_METHOD, + headers, + allowGetBody: true, + ...bodyParam, + timeout, + retry: 1, + }; + + if (WEBHOOK_METHOD) { + got(formatUrl, options).then((resp) => { + try { + if (resp.statusCode !== 200) { + console.log('自定义发送通知消息失败!!\n'); + console.log(resp.body); + } else { + console.log('自定义发送通知消息成功🎉。\n'); + console.log(resp.body); + } + } catch (e) { + $.logErr(e, resp); + } finally { + resolve(resp.body); + } + }); + } else { + resolve(); + } + }); +} + +function parseHeaders(headers) { + if (!headers) return {}; + + const parsed = {}; + let key; + let val; + let i; + + headers && + headers.split('\n').forEach(function parser(line) { + i = line.indexOf(':'); + key = line.substring(0, i).trim().toLowerCase(); + val = line.substring(i + 1).trim(); + + if (!key) { + return; + } + + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + }); + + return parsed; +} + +function parseBody(body, contentType) { + if (!body) return ''; + + const parsed = {}; + let key; + let val; + let i; + + body && + body.split('\n').forEach(function parser(line) { + i = line.indexOf(':'); + key = line.substring(0, i).trim().toLowerCase(); + val = line.substring(i + 1).trim(); + + if (!key || parsed[key]) { + return; + } + + try { + const jsonValue = JSON.parse(val); + parsed[key] = jsonValue; + } catch (error) { + parsed[key] = val; + } + }); + + switch (contentType) { + case 'multipart/form-data': + return Object.keys(parsed).reduce((p, c) => { + p.append(c, parsed[c]); + return p; + }, new FormData()); + case 'application/x-www-form-urlencoded': + return Object.keys(parsed).reduce((p, c) => { + return p ? `${p}&${c}=${parsed[c]}` : `${c}=${parsed[c]}`; + }); + } + + return parsed; +} + +function formatBodyFun(contentType, body) { + if (!body) return {}; + switch (contentType) { + case 'application/json': + return { json: body }; + case 'multipart/form-data': + return { form: body }; + case 'application/x-www-form-urlencoded': + return { body }; + } + return {}; +} + +function formatNotifyContentFun(url, body, title, content) { + if (!url.includes('$title') && !body.includes('$title')) { + return {}; + } + + return { + formatUrl: url + .replaceAll('$title', encodeURIComponent(title)) + .replaceAll('$content', encodeURIComponent(content)), + formatBody: body + .replaceAll('$title', title) + .replaceAll('$content', content), + }; +} + module.exports = { sendNotify, BARK_PUSH, diff --git a/sample/notify.py b/sample/notify.py index 8b5c748ed07..aa53f28b572 100644 --- a/sample/notify.py +++ b/sample/notify.py @@ -101,10 +101,17 @@ def print(text, *args, **kw): 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写 - 'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY - 'CHRONOCAT_QQ': '', # qq号 - 'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token - 'CHRONOCAT_URL': '' # CHRONOCAT的url地址 + 'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY + + 'CHRONOCAT_QQ': '', # qq号 + 'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token + 'CHRONOCAT_URL': '', # CHRONOCAT的url地址 + + 'WEBHOOK_URL': '', # 自定义通知 请求地址 + 'WEBHOOK_BODY': '', # 自定义通知 请求体 + 'WEBHOOK_HEADERS': '', # 自定义通知 请求头 + 'WEBHOOK_METHOD': '', # 自定义通知 请求方法 + 'WEBHOOK_CONTENT_TYPE': '' # 自定义通知 content-type } notify_function = [] # fmt: on @@ -449,7 +456,9 @@ def get_access_token(self): return data["access_token"] def send_text(self, message, touser="@all"): - send_url = f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + send_url = ( + f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + ) send_values = { "touser": touser, "msgtype": "text", @@ -463,7 +472,9 @@ def send_text(self, message, touser="@all"): return respone["errmsg"] def send_mpnews(self, title, message, media_id, touser="@all"): - send_url = f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + send_url = ( + f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + ) send_values = { "touser": touser, "msgtype": "mpnews", @@ -646,6 +657,7 @@ def smtp(title: str, content: str) -> None: except Exception as e: print(f"SMTP 邮件 推送失败!{e}") + def pushme(title: str, content: str) -> None: """ 使用 PushMe 推送消息。 @@ -667,12 +679,16 @@ def pushme(title: str, content: str) -> None: else: print(f"PushMe 推送失败!{response.status_code} {response.text}") + def chronocat(title: str, content: str) -> None: """ 使用 CHRONOCAT 推送消息。 """ - if not push_config.get("CHRONOCAT_URL") or not push_config.get("CHRONOCAT_QQ") or not push_config.get( - "CHRONOCAT_TOKEN"): + if ( + not push_config.get("CHRONOCAT_URL") + or not push_config.get("CHRONOCAT_QQ") + or not push_config.get("CHRONOCAT_TOKEN") + ): print("CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送") return @@ -683,8 +699,8 @@ def chronocat(title: str, content: str) -> None: url = f'{push_config.get("CHRONOCAT_URL")}/api/message/send' headers = { - 'Content-Type': 'application/json', - 'Authorization': f'Bearer {push_config.get("CHRONOCAT_TOKEN")}' + "Content-Type": "application/json", + "Authorization": f'Bearer {push_config.get("CHRONOCAT_TOKEN")}', } for chat_type, ids in [(1, user_ids), (2, group_ids)]: @@ -692,31 +708,127 @@ def chronocat(title: str, content: str) -> None: continue for chat_id in ids: data = { - "peer": { - "chatType": chat_type, - "peerUin": chat_id - }, + "peer": {"chatType": chat_type, "peerUin": chat_id}, "elements": [ { "elementType": 1, - "textElement": { - "content": f'{title}\n\n{content}' - } + "textElement": {"content": f"{title}\n\n{content}"}, } - ] + ], } response = requests.post(url, headers=headers, data=json.dumps(data)) if response.status_code == 200: if chat_type == 1: - print(f'QQ个人消息:{ids}推送成功!') + print(f"QQ个人消息:{ids}推送成功!") else: - print(f'QQ群消息:{ids}推送成功!') + print(f"QQ群消息:{ids}推送成功!") else: if chat_type == 1: - print(f'QQ个人消息:{ids}推送失败!') + print(f"QQ个人消息:{ids}推送失败!") else: - print(f'QQ群消息:{ids}推送失败!') + print(f"QQ群消息:{ids}推送失败!") + + +def parse_headers(headers): + if not headers: + return {} + + parsed = {} + lines = headers.split("\n") + + for line in lines: + i = line.find(":") + if i == -1: + continue + + key = line[:i].strip().lower() + val = line[i + 1 :].strip() + parsed[key] = parsed.get(key, "") + ", " + val if key in parsed else val + + return parsed + + +def parse_body(body, content_type): + if not body: + return "" + + parsed = {} + lines = body.split("\n") + + for line in lines: + i = line.find(":") + if i == -1: + continue + + key = line[:i].strip().lower() + val = line[i + 1 :].strip() + + if not key or key in parsed: + continue + + try: + json_value = json.loads(val) + parsed[key] = json_value + except: + parsed[key] = val + if content_type == "application/x-www-form-urlencoded": + data = urlencode(parsed, doseq=True) + return data + + if content_type == "application/json": + data = json.dumps(parsed) + return data + + return parsed + + +def format_notify_content(url, body, title, content): + if "$title" not in url and "$title" not in body: + return {} + + formatted_url = url.replace("$title", urllib.parse.quote_plus(title)).replace( + "$content", urllib.parse.quote_plus(content) + ) + formatted_body = body.replace("$title", title).replace("$content", content) + + return formatted_url, formatted_body + + +def custom_notify(title: str, content: str) -> None: + """ + 通过 自定义通知 推送消息。 + """ + if not push_config.get("WEBHOOK_URL") or not push_config.get("WEBHOOK_METHOD"): + print("自定义通知的 WEBHOOK_URL 或 WEBHOOK_METHOD 未设置!!\n取消推送") + return + + print("自定义通知服务启动") + + WEBHOOK_URL = push_config.get("WEBHOOK_URL") + WEBHOOK_METHOD = push_config.get("WEBHOOK_METHOD") + WEBHOOK_CONTENT_TYPE = push_config.get("WEBHOOK_CONTENT_TYPE") + WEBHOOK_BODY = push_config.get("WEBHOOK_BODY") + WEBHOOK_HEADERS = push_config.get("WEBHOOK_HEADERS") + + formatUrl, formatBody = format_notify_content( + WEBHOOK_URL, WEBHOOK_BODY, title, content + ) + + if not formatUrl and not formatBody: + print("请求头或者请求体中必须包含 $title 和 $content") + return + + headers = parse_headers(WEBHOOK_HEADERS) + body = parse_body(formatBody, WEBHOOK_CONTENT_TYPE) + response = requests.request( + method=WEBHOOK_METHOD, url=formatUrl, headers=headers, timeout=15, data=body + ) + + if response.status_code == 200: + print("自定义通知推送成功!") + else: + print(f"自定义通知推送失败!{response.status_code} {response.text}") def one() -> str: @@ -775,6 +887,14 @@ def one() -> str: notify_function.append(smtp) if push_config.get("PUSHME_KEY"): notify_function.append(pushme) +if ( + push_config.get("CHRONOCAT_URL") + and push_config.get("CHRONOCAT_QQ") + and push_config.get("CHRONOCAT_TOKEN") +): + notify_function.append(chronocat) +if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"): + notify_function.append(custom_notify) def send(title: str, content: str) -> None: