@whyour/qinglong 2.20.2-2 → 2.21.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.
Files changed (218) hide show
  1. package/.env.example +5 -0
  2. package/AGENTS.md +43 -0
  3. package/CLAUDE.md +43 -0
  4. package/README-en.md +11 -0
  5. package/README.md +11 -0
  6. package/docker/Dockerfile +51 -69
  7. package/docker/Dockerfile.310 +94 -0
  8. package/docker/Dockerfile.debian +120 -0
  9. package/docker/{310.Dockerfile → Dockerfile.debian310} +13 -5
  10. package/docker/docker-entrypoint.sh +95 -3
  11. package/docs/PROJECT_ARCHITECTURE.md +550 -0
  12. package/package.json +28 -25
  13. package/sample/config.sample.sh +12 -1
  14. package/sample/notify.js +81 -9
  15. package/sample/notify.py +54 -11
  16. package/shell/api.sh +44 -20
  17. package/shell/bot.sh +30 -16
  18. package/shell/check.sh +21 -19
  19. package/shell/lang/en.sh +105 -0
  20. package/shell/lang/zh.sh +105 -0
  21. package/shell/otask.sh +114 -28
  22. package/shell/preload/client.js +20 -2
  23. package/shell/preload/esm-loader.mjs +41 -0
  24. package/shell/preload/sitecustomize.js +36 -0
  25. package/shell/pub.sh +8 -8
  26. package/shell/rmlog.sh +5 -5
  27. package/shell/share.sh +49 -30
  28. package/shell/start.sh +40 -27
  29. package/shell/task.sh +10 -2
  30. package/shell/update.sh +22 -22
  31. package/static/build/api/config.js +17 -7
  32. package/static/build/api/cron.js +38 -1
  33. package/static/build/api/dashboard.js +337 -0
  34. package/static/build/api/env.js +40 -2
  35. package/static/build/api/index.js +2 -0
  36. package/static/build/api/log.js +5 -4
  37. package/static/build/api/script.js +34 -9
  38. package/static/build/api/system.js +16 -1
  39. package/static/build/api/user.js +6 -5
  40. package/static/build/app.js +3 -0
  41. package/static/build/config/const.js +7 -6
  42. package/static/build/config/container.js +12 -0
  43. package/static/build/config/grpcCerts.js +150 -0
  44. package/static/build/config/index.js +6 -0
  45. package/static/build/config/util.js +66 -15
  46. package/static/build/data/cron.js +3 -1
  47. package/static/build/data/cronStats.js +59 -0
  48. package/static/build/data/env.js +2 -0
  49. package/static/build/data/notify.js +11 -1
  50. package/static/build/data/runningInstance.js +57 -0
  51. package/static/build/loaders/app.js +1 -1
  52. package/static/build/loaders/db.js +6 -0
  53. package/static/build/loaders/express.js +2 -1
  54. package/static/build/loaders/initData.js +19 -2
  55. package/static/build/loaders/initFile.js +2 -0
  56. package/static/build/schedule/api.js +1 -1
  57. package/static/build/schedule/client.js +9 -1
  58. package/static/build/schedule/health.js +1 -1
  59. package/static/build/services/config.js +19 -5
  60. package/static/build/services/cron.js +118 -8
  61. package/static/build/services/env.js +31 -3
  62. package/static/build/services/grpc.js +32 -6
  63. package/static/build/services/http.js +33 -18
  64. package/static/build/services/notify.js +40 -3
  65. package/static/build/services/open.js +2 -1
  66. package/static/build/services/sshKey.js +6 -3
  67. package/static/build/services/subscription.js +2 -1
  68. package/static/build/services/system.js +22 -7
  69. package/static/build/services/user.js +8 -7
  70. package/static/build/shared/i18n.js +105 -0
  71. package/static/build/shared/pLimit.js +12 -1
  72. package/static/build/shared/runCron.js +5 -0
  73. package/static/build/validation/schedule.js +1 -0
  74. package/static/dist/1089.0ecc5383.async.js +1 -0
  75. package/static/dist/1147.c420132e.async.js +1 -0
  76. package/static/dist/1192.4e5740f2.async.js +1 -0
  77. package/static/dist/1214.c6469b53.async.js +1 -0
  78. package/static/dist/134.54df9e38.async.js +1 -0
  79. package/static/dist/1352.96a77e1d.async.js +1 -0
  80. package/static/dist/{1765.d8e002d7.async.js → 1765.5ac01a93.async.js} +1 -1
  81. package/static/dist/1836.f0b74cf1.async.js +1 -0
  82. package/static/dist/{1885.e0d00d2d.async.js → 1885.e1ea09f6.async.js} +1 -1
  83. package/static/dist/1967.3f3945d0.async.js +1 -0
  84. package/static/dist/{2096.383c1047.async.js → 2096.6d98fd12.async.js} +1 -1
  85. package/static/dist/2208.f7ba3dfa.async.js +1 -0
  86. package/static/dist/2286.164bb089.async.js +1 -0
  87. package/static/dist/2476.4e9b0992.async.js +1 -0
  88. package/static/dist/2537.2b262ee0.async.js +1 -0
  89. package/static/dist/{255.12f03ab2.async.js → 255.8a80b983.async.js} +1 -1
  90. package/static/dist/2635.d4c59d23.async.js +1 -0
  91. package/static/dist/2808.cdc0995c.async.js +1 -0
  92. package/static/dist/{2821.be3dc88e.async.js → 2821.651f31e5.async.js} +1 -1
  93. package/static/dist/2986.4c49eef7.async.js +1 -0
  94. package/static/dist/300.08ac9875.async.js +1 -0
  95. package/static/dist/{3191.70bc19db.async.js → 3191.63263871.async.js} +1 -1
  96. package/static/dist/3714.8d6dba9e.async.js +1 -0
  97. package/static/dist/3906.a5eee612.async.js +1 -0
  98. package/static/dist/{6541.a6d499de.async.js → 3948.4fe809fd.async.js} +1 -1
  99. package/static/dist/{5171.7fc6d0a2.async.js → 4573.16f19278.async.js} +1 -1
  100. package/static/dist/{4859.93e63ea6.async.js → 4859.6592cebb.async.js} +1 -1
  101. package/static/dist/4887.8e3ae573.async.js +1 -0
  102. package/static/dist/{4902.54ecbdb5.async.js → 4902.555f92ab.async.js} +1 -1
  103. package/static/dist/{4934.1ca6b6b0.async.js → 4934.f3539d08.async.js} +1 -1
  104. package/static/dist/5077.cbd111cd.async.js +1 -0
  105. package/static/dist/{6247.b550d996.async.js → 5157.7c5af144.async.js} +1 -1
  106. package/static/dist/{540.481d4708.async.js → 540.19ddf020.async.js} +1 -1
  107. package/static/dist/{5970.10ac4f16.async.js → 5970.d3a6e7bd.async.js} +1 -1
  108. package/static/dist/{6013.2d7bb12a.async.js → 6013.4e38de36.async.js} +1 -1
  109. package/static/dist/{6016.9c379049.async.js → 6016.1e6574b6.async.js} +1 -1
  110. package/static/dist/{6035.5889ddc7.async.js → 6035.32ebfad9.async.js} +1 -1
  111. package/static/dist/6339.ab305e96.async.js +1 -0
  112. package/static/dist/6569.55177f6a.async.js +1 -0
  113. package/static/dist/6610.c111af7e.async.js +1 -0
  114. package/static/dist/{3034.6413c38e.async.js → 6638.87a163d2.async.js} +1 -1
  115. package/static/dist/{6646.5fc37228.async.js → 6646.95e92533.async.js} +1 -1
  116. package/static/dist/6665.1d099ad1.async.js +1 -0
  117. package/static/dist/{7355.b7c0562f.async.js → 7355.f9f263d8.async.js} +1 -1
  118. package/static/dist/{7384.065ccae2.async.js → 7384.1bb449d5.async.js} +1 -1
  119. package/static/dist/7441.ee1bf2bb.async.js +1 -0
  120. package/static/dist/{7508.a31662a3.async.js → 7508.7dcee91c.async.js} +1 -1
  121. package/static/dist/{7802.6b73f16a.async.js → 7802.34255805.async.js} +1 -1
  122. package/static/dist/{7984.e6bb9378.async.js → 7984.594296a5.async.js} +1 -1
  123. package/static/dist/{5653.4fce7ce8.async.js → 8033.5cb31493.async.js} +1 -1
  124. package/static/dist/8147.2fb55202.async.js +1 -0
  125. package/static/dist/8187.48c130d6.async.js +1 -0
  126. package/static/dist/8495.d53e15ca.async.js +1 -0
  127. package/static/dist/8587.315b06c0.async.js +1 -0
  128. package/static/dist/8826.2447a104.async.js +1 -0
  129. package/static/dist/{2742.4852aac8.async.js → 8865.ed665d31.async.js} +1 -1
  130. package/static/dist/901.7ee5c6d3.async.js +1 -0
  131. package/static/dist/9271.231db2ce.async.js +1 -0
  132. package/static/dist/9323.a33f47da.async.js +1 -0
  133. package/static/dist/{9730.30083c91.async.js → 9730.801665a3.async.js} +1 -1
  134. package/static/dist/{9761.627ca3b5.async.js → 9761.360a19d8.async.js} +1 -1
  135. package/static/dist/index.html +2 -2
  136. package/static/dist/layouts__index.a7a2bfe0.async.js +1 -0
  137. package/static/dist/layouts__index.adf0692f.chunk.css +1 -0
  138. package/static/dist/preload_helper.0431c0f3.js +1 -0
  139. package/static/dist/{src__pages__config__index.622b6ee8.async.js → src__pages__config__index.a22ff7dc.async.js} +1 -1
  140. package/static/dist/{src__pages__crontab__const.323d5124.async.js → src__pages__crontab__const.aba07deb.async.js} +1 -1
  141. package/static/dist/src__pages__crontab__detail.b9c36808.async.js +1 -0
  142. package/static/dist/src__pages__crontab__index.fed99fd0.async.js +1 -0
  143. package/static/dist/{src__pages__crontab__logModal.5e6a4bf2.async.js → src__pages__crontab__logModal.0b8cce8c.async.js} +1 -1
  144. package/static/dist/src__pages__crontab__modal.f7041c7c.async.js +1 -0
  145. package/static/dist/src__pages__crontab__type.d7af36e5.async.js +1 -0
  146. package/static/dist/{src__pages__crontab__viewCreateModal.ffcf7a24.async.js → src__pages__crontab__viewCreateModal.4d589f66.async.js} +1 -1
  147. package/static/dist/{src__pages__crontab__viewManageModal.c2724575.async.js → src__pages__crontab__viewManageModal.0e317746.async.js} +1 -1
  148. package/static/dist/src__pages__dashboard__index.b30f2f47.async.js +1 -0
  149. package/static/dist/{src__pages__dependence__logModal.f123e2ac.async.js → src__pages__dependence__logModal.0681830b.async.js} +1 -1
  150. package/static/dist/src__pages__dependence__modal.11124896.async.js +1 -0
  151. package/static/dist/src__pages__env__editNameModal.5d264b25.async.js +1 -0
  152. package/static/dist/src__pages__env__index.baa27d4e.async.js +1 -0
  153. package/static/dist/src__pages__env__modal.7f2ef1bc.async.js +1 -0
  154. package/static/dist/src__pages__error__index.f156b45e.async.js +1 -0
  155. package/static/dist/src__pages__initialization__index.e96d4ba8.async.js +1 -0
  156. package/static/dist/{src__pages__log__index.cf00c9af.async.js → src__pages__log__index.e0978bae.async.js} +1 -1
  157. package/static/dist/{src__pages__login__index.cd6e3152.async.js → src__pages__login__index.8c813eb1.async.js} +1 -1
  158. package/static/dist/{src__pages__script__components__UnsupportedFilePreview__index.39074c68.async.js → src__pages__script__components__UnsupportedFilePreview__index.c347a13a.async.js} +1 -1
  159. package/static/dist/src__pages__script__editNameModal.3e7e100d.async.js +1 -0
  160. package/static/dist/src__pages__script__index.2765d1b8.async.js +1 -0
  161. package/static/dist/src__pages__script__renameModal.5e987ef5.async.js +1 -0
  162. package/static/dist/src__pages__script__saveModal.3f9d23d6.async.js +1 -0
  163. package/static/dist/src__pages__script__setting.a535793a.async.js +1 -0
  164. package/static/dist/src__pages__setting__appModal.251cd14f.async.js +1 -0
  165. package/static/dist/src__pages__setting__dependence.8a7a9529.async.js +1 -0
  166. package/static/dist/src__pages__setting__index.cc85fdfb.async.js +1 -0
  167. package/static/dist/src__pages__setting__notification.390fc905.async.js +1 -0
  168. package/static/dist/src__pages__setting__other.b22d2165.async.js +1 -0
  169. package/static/dist/src__pages__setting__security.598720a8.async.js +1 -0
  170. package/static/dist/src__pages__setting__systemLog.67406721.async.js +1 -0
  171. package/static/dist/{src__pages__subscription__logModal.0caa7283.async.js → src__pages__subscription__logModal.3864b37f.async.js} +1 -1
  172. package/static/dist/src__pages__subscription__modal.3562c670.async.js +1 -0
  173. package/static/dist/{umi.ef8199a4.js → umi.ca04a019.js} +1 -1
  174. package/version.yaml +33 -4
  175. package/sample/notify.py.save +0 -1010
  176. package/static/dist/105.85a5c47a.async.js +0 -1
  177. package/static/dist/1083.f86ce804.async.js +0 -1
  178. package/static/dist/1147.32f41a88.async.js +0 -1
  179. package/static/dist/1352.ab6da08e.async.js +0 -1
  180. package/static/dist/1690.f0290540.async.js +0 -1
  181. package/static/dist/1742.6cbe5aca.async.js +0 -1
  182. package/static/dist/2208.8e3a7325.async.js +0 -1
  183. package/static/dist/5312.74b95311.async.js +0 -1
  184. package/static/dist/5691.931f59c5.async.js +0 -1
  185. package/static/dist/6159.55cb068a.async.js +0 -1
  186. package/static/dist/7025.f4080d63.async.js +0 -1
  187. package/static/dist/739.6be5552a.async.js +0 -1
  188. package/static/dist/7571.4f6240b1.async.js +0 -1
  189. package/static/dist/786.59fc381c.async.js +0 -1
  190. package/static/dist/8317.c44c1ebd.async.js +0 -1
  191. package/static/dist/8826.0291edfd.async.js +0 -1
  192. package/static/dist/955.3c9481f7.async.js +0 -1
  193. package/static/dist/layouts__index.1fce90e0.chunk.css +0 -1
  194. package/static/dist/layouts__index.8dcf1576.async.js +0 -1
  195. package/static/dist/preload_helper.116a62f6.js +0 -1
  196. package/static/dist/src__pages__crontab__detail.b07f0c0a.async.js +0 -1
  197. package/static/dist/src__pages__crontab__index.2e2e1096.async.js +0 -1
  198. package/static/dist/src__pages__crontab__modal.4d8c2a22.async.js +0 -1
  199. package/static/dist/src__pages__crontab__type.db7c1858.async.js +0 -1
  200. package/static/dist/src__pages__dependence__modal.631ffb5b.async.js +0 -1
  201. package/static/dist/src__pages__env__editNameModal.ff85ef8c.async.js +0 -1
  202. package/static/dist/src__pages__env__index.a0a2fece.async.js +0 -1
  203. package/static/dist/src__pages__env__modal.d1004662.async.js +0 -1
  204. package/static/dist/src__pages__error__index.1bc3c90b.async.js +0 -1
  205. package/static/dist/src__pages__initialization__index.8b1cbaf9.async.js +0 -1
  206. package/static/dist/src__pages__script__editNameModal.53424d49.async.js +0 -1
  207. package/static/dist/src__pages__script__index.e65df827.async.js +0 -1
  208. package/static/dist/src__pages__script__renameModal.4bbe7fb1.async.js +0 -1
  209. package/static/dist/src__pages__script__saveModal.cf449f3c.async.js +0 -1
  210. package/static/dist/src__pages__script__setting.b345d59a.async.js +0 -1
  211. package/static/dist/src__pages__setting__appModal.03faec89.async.js +0 -1
  212. package/static/dist/src__pages__setting__dependence.4495c7b6.async.js +0 -1
  213. package/static/dist/src__pages__setting__index.6919c399.async.js +0 -1
  214. package/static/dist/src__pages__setting__notification.d6a3884f.async.js +0 -1
  215. package/static/dist/src__pages__setting__other.0d931d6f.async.js +0 -1
  216. package/static/dist/src__pages__setting__security.91cb545f.async.js +0 -1
  217. package/static/dist/src__pages__setting__systemLog.1d433a48.async.js +0 -1
  218. package/static/dist/src__pages__subscription__modal.0b84f6cf.async.js +0 -1
@@ -1,1010 +0,0 @@
1
- #!/usr/bin/env python3
2
- # _*_ coding:utf-8 _*_
3
- import base64
4
- import hashlib
5
- import hmac
6
- import json
7
- import os
8
- import re
9
- import threading
10
- import time
11
- import urllib.parse
12
- import smtplib
13
- from email.mime.text import MIMEText
14
- from email.header import Header
15
- from email.utils import formataddr
16
-
17
- import requests
18
-
19
- # 原先的 print 函数和主线程的锁
20
- _print = print
21
- mutex = threading.Lock()
22
-
23
-
24
- # 定义新的 print 函数
25
- def print(text, *args, **kw):
26
- """
27
- 使输出有序进行,不出现多线程同一时间输出导致错乱的问题。
28
- """
29
- with mutex:
30
- _print(text, *args, **kw)
31
-
32
-
33
- # 通知服务
34
- # fmt: off
35
- push_config = {
36
- 'HITOKOTO': True, # 启用一言(随机句子)
37
-
38
- 'BARK_PUSH': '', # bark IP 或设备码,例:https://api.day.app/DxHcxxxxxRxxxxxxcm/
39
- 'BARK_ARCHIVE': '', # bark 推送是否存档
40
- 'BARK_GROUP': '', # bark 推送分组
41
- 'BARK_SOUND': '', # bark 推送声音
42
- 'BARK_ICON': '', # bark 推送图标
43
- 'BARK_LEVEL': '', # bark 推送时效性
44
- 'BARK_URL': '', # bark 推送跳转URL
45
-
46
- 'CONSOLE': False, # 控制台输出
47
-
48
- 'DD_BOT_SECRET': '', # 钉钉机器人的 DD_BOT_SECRET
49
- 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN
50
-
51
- 'FSKEY': '', # 飞书机器人的 FSKEY
52
-
53
- 'GOBOT_URL': '', # go-cqhttp
54
- # 推送到个人QQ:http://127.0.0.1/send_private_msg
55
- # 群:http://127.0.0.1/send_group_msg
56
- 'GOBOT_QQ': '', # go-cqhttp 的推送群或用户
57
- # GOBOT_URL 设置 /send_private_msg 时填入 user_id=个人QQ
58
- # /send_group_msg 时填入 group_id=QQ群
59
- 'GOBOT_TOKEN': '', # go-cqhttp 的 access_token
60
-
61
- 'GOTIFY_URL': '', # gotify地址,如https://push.example.de:8080
62
- 'GOTIFY_TOKEN': '', # gotify的消息应用token
63
- 'GOTIFY_PRIORITY': 0, # 推送消息优先级,默认为0
64
-
65
- 'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY
66
-
67
- 'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版
68
-
69
- 'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY
70
- 'DEER_URL': '', # PushDeer 的 PUSHDEER_URL
71
-
72
- 'CHAT_URL': '', # synology chat url
73
- 'CHAT_TOKEN': '', # synology chat token
74
-
75
- 'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌
76
- 'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码
77
-
78
- 'WE_PLUS_BOT_TOKEN': '', # 微加机器人的用户令牌
79
- 'WE_PLUS_BOT_RECEIVER': '', # 微加机器人的消息接收者
80
- 'WE_PLUS_BOT_VERSION': 'pro', # 微加机器人的调用版本
81
-
82
- 'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY
83
- 'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE
84
-
85
- 'QYWX_ORIGIN': '', # 企业微信代理地址
86
-
87
- 'QYWX_AM': '', # 企业微信应用
88
-
89
- 'QYWX_KEY': '', # 企业微信机器人
90
-
91
- 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ
92
- 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534
93
- 'TG_API_HOST': '', # tg 代理 api
94
- 'TG_PROXY_AUTH': '', # tg 代理认证参数
95
- 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST
96
- 'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT
97
-
98
- 'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about
99
- 'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact
100
- 'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好
101
-
102
- 'SMTP_SERVER': '', # SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465
103
- 'SMTP_SSL': 'false', # SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false
104
- 'SMTP_EMAIL': '', # SMTP 收发件邮箱,通知将会由自己发给自己
105
- 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
106
- 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写
107
-
108
- 'PUSHME_KEY': '', # PushMe 的 PUSHME_KEY
109
- 'PUSHME_URL': '', # PushMe 的 PUSHME_URL
110
-
111
- 'CHRONOCAT_QQ': '', # qq号
112
- 'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token
113
- 'CHRONOCAT_URL': '', # CHRONOCAT的url地址
114
-
115
- 'WEBHOOK_URL': '', # 自定义通知 请求地址
116
- 'WEBHOOK_BODY': '', # 自定义通知 请求体
117
- 'WEBHOOK_HEADERS': '', # 自定义通知 请求头
118
- 'WEBHOOK_METHOD': '', # 自定义通知 请求方法
119
- 'WEBHOOK_CONTENT_TYPE': '' # 自定义通知 content-type
120
-
121
- 'NTFY_URL': '', # ntfy地址,如https://ntfy.sh
122
- 'NTFY_TOPIC': '', # ntfy的消息应用topic
123
- 'NTFY_PRIORITY': '3', # 推送消息优先级,默认为3
124
- }
125
- # fmt: on
126
-
127
- for k in push_config:
128
- if os.getenv(k):
129
- v = os.getenv(k)
130
- push_config[k] = v
131
-
132
-
133
- def bark(title: str, content: str) -> None:
134
- """
135
- 使用 bark 推送消息。
136
- """
137
- if not push_config.get("BARK_PUSH"):
138
- print("bark 服务的 BARK_PUSH 未设置!!\n取消推送")
139
- return
140
- print("bark 服务启动")
141
-
142
- if push_config.get("BARK_PUSH").startswith("http"):
143
- url = f'{push_config.get("BARK_PUSH")}'
144
- else:
145
- url = f'https://api.day.app/{push_config.get("BARK_PUSH")}'
146
-
147
- bark_params = {
148
- "BARK_ARCHIVE": "isArchive",
149
- "BARK_GROUP": "group",
150
- "BARK_SOUND": "sound",
151
- "BARK_ICON": "icon",
152
- "BARK_LEVEL": "level",
153
- "BARK_URL": "url",
154
- }
155
- data = {
156
- "title": title,
157
- "body": content,
158
- }
159
- for pair in filter(
160
- lambda pairs: pairs[0].startswith("BARK_")
161
- and pairs[0] != "BARK_PUSH"
162
- and pairs[1]
163
- and bark_params.get(pairs[0]),
164
- push_config.items(),
165
- ):
166
- data[bark_params.get(pair[0])] = pair[1]
167
- headers = {"Content-Type": "application/json;charset=utf-8"}
168
- response = requests.post(
169
- url=url, data=json.dumps(data), headers=headers, timeout=15
170
- ).json()
171
-
172
- if response["code"] == 200:
173
- print("bark 推送成功!")
174
- else:
175
- print("bark 推送失败!")
176
-
177
-
178
- def console(title: str, content: str) -> None:
179
- """
180
- 使用 控制台 推送消息。
181
- """
182
- print(f"{title}\n\n{content}")
183
-
184
-
185
- def dingding_bot(title: str, content: str) -> None:
186
- """
187
- 使用 钉钉机器人 推送消息。
188
- """
189
- if not push_config.get("DD_BOT_SECRET") or not push_config.get("DD_BOT_TOKEN"):
190
- print("钉钉机器人 服务的 DD_BOT_SECRET 或者 DD_BOT_TOKEN 未设置!!\n取消推送")
191
- return
192
- print("钉钉机器人 服务启动")
193
-
194
- timestamp = str(round(time.time() * 1000))
195
- secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8")
196
- string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET"))
197
- string_to_sign_enc = string_to_sign.encode("utf-8")
198
- hmac_code = hmac.new(
199
- secret_enc, string_to_sign_enc, digestmod=hashlib.sha256
200
- ).digest()
201
- sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
202
- url = f'https://oapi.dingtalk.com/robot/send?access_token={push_config.get("DD_BOT_TOKEN")}&timestamp={timestamp}&sign={sign}'
203
- headers = {"Content-Type": "application/json;charset=utf-8"}
204
- data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
205
- response = requests.post(
206
- url=url, data=json.dumps(data), headers=headers, timeout=15
207
- ).json()
208
-
209
- if not response["errcode"]:
210
- print("钉钉机器人 推送成功!")
211
- else:
212
- print("钉钉机器人 推送失败!")
213
-
214
-
215
- def feishu_bot(title: str, content: str) -> None:
216
- """
217
- 使用 飞书机器人 推送消息。
218
- """
219
- if not push_config.get("FSKEY"):
220
- print("飞书 服务的 FSKEY 未设置!!\n取消推送")
221
- return
222
- print("飞书 服务启动")
223
-
224
- url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}'
225
- data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}}
226
- response = requests.post(url, data=json.dumps(data)).json()
227
-
228
- if response.get("StatusCode") == 0 or response.get("code") == 0:
229
- print("飞书 推送成功!")
230
- else:
231
- print("飞书 推送失败!错误信息如下:\n", response)
232
-
233
-
234
- def go_cqhttp(title: str, content: str) -> None:
235
- """
236
- 使用 go_cqhttp 推送消息。
237
- """
238
- if not push_config.get("GOBOT_URL") or not push_config.get("GOBOT_QQ"):
239
- print("go-cqhttp 服务的 GOBOT_URL 或 GOBOT_QQ 未设置!!\n取消推送")
240
- return
241
- print("go-cqhttp 服务启动")
242
-
243
- url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}'
244
- response = requests.get(url).json()
245
-
246
- if response["status"] == "ok":
247
- print("go-cqhttp 推送成功!")
248
- else:
249
- print("go-cqhttp 推送失败!")
250
-
251
-
252
- def gotify(title: str, content: str) -> None:
253
- """
254
- 使用 gotify 推送消息。
255
- """
256
- if not push_config.get("GOTIFY_URL") or not push_config.get("GOTIFY_TOKEN"):
257
- print("gotify 服务的 GOTIFY_URL 或 GOTIFY_TOKEN 未设置!!\n取消推送")
258
- return
259
- print("gotify 服务启动")
260
-
261
- url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}'
262
- data = {
263
- "title": title,
264
- "message": content,
265
- "priority": push_config.get("GOTIFY_PRIORITY"),
266
- }
267
- response = requests.post(url, data=data).json()
268
-
269
- if response.get("id"):
270
- print("gotify 推送成功!")
271
- else:
272
- print("gotify 推送失败!")
273
-
274
-
275
- def iGot(title: str, content: str) -> None:
276
- """
277
- 使用 iGot 推送消息。
278
- """
279
- if not push_config.get("IGOT_PUSH_KEY"):
280
- print("iGot 服务的 IGOT_PUSH_KEY 未设置!!\n取消推送")
281
- return
282
- print("iGot 服务启动")
283
-
284
- url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}'
285
- data = {"title": title, "content": content}
286
- headers = {"Content-Type": "application/x-www-form-urlencoded"}
287
- response = requests.post(url, data=data, headers=headers).json()
288
-
289
- if response["ret"] == 0:
290
- print("iGot 推送成功!")
291
- else:
292
- print(f'iGot 推送失败!{response["errMsg"]}')
293
-
294
-
295
- def serverJ(title: str, content: str) -> None:
296
- """
297
- 通过 serverJ 推送消息。
298
- """
299
- if not push_config.get("PUSH_KEY"):
300
- print("serverJ 服务的 PUSH_KEY 未设置!!\n取消推送")
301
- return
302
- print("serverJ 服务启动")
303
-
304
- data = {"text": title, "desp": content.replace("\n", "\n\n")}
305
- if push_config.get("PUSH_KEY").startswith("sctp"):
306
- url = f'https://{push_config.get("PUSH_KEY")}.push.ft07.com/send'
307
- else:
308
- url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send'
309
- response = requests.post(url, data=data).json()
310
-
311
- if response.get("errno") == 0 or response.get("code") == 0:
312
- print("serverJ 推送成功!")
313
- else:
314
- print(f'serverJ 推送失败!错误码:{response["message"]}')
315
-
316
-
317
- def pushdeer(title: str, content: str) -> None:
318
- """
319
- 通过PushDeer 推送消息
320
- """
321
- if not push_config.get("DEER_KEY"):
322
- print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送")
323
- return
324
- print("PushDeer 服务启动")
325
- data = {
326
- "text": title,
327
- "desp": content,
328
- "type": "markdown",
329
- "pushkey": push_config.get("DEER_KEY"),
330
- }
331
- url = "https://api2.pushdeer.com/message/push"
332
- if push_config.get("DEER_URL"):
333
- url = push_config.get("DEER_URL")
334
-
335
- response = requests.post(url, data=data).json()
336
-
337
- if len(response.get("content").get("result")) > 0:
338
- print("PushDeer 推送成功!")
339
- else:
340
- print("PushDeer 推送失败!错误信息:", response)
341
-
342
-
343
- def chat(title: str, content: str) -> None:
344
- """
345
- 通过Chat 推送消息
346
- """
347
- if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"):
348
- print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送")
349
- return
350
- print("chat 服务启动")
351
- data = "payload=" + json.dumps({"text": title + "\n" + content})
352
- url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
353
- response = requests.post(url, data=data)
354
-
355
- if response.status_code == 200:
356
- print("Chat 推送成功!")
357
- else:
358
- print("Chat 推送失败!错误信息:", response)
359
-
360
-
361
- def pushplus_bot(title: str, content: str) -> None:
362
- """
363
- 通过 push+ 推送消息。
364
- """
365
- if not push_config.get("PUSH_PLUS_TOKEN"):
366
- print("PUSHPLUS 服务的 PUSH_PLUS_TOKEN 未设置!!\n取消推送")
367
- return
368
- print("PUSHPLUS 服务启动")
369
-
370
- url = "http://www.pushplus.plus/send"
371
- data = {
372
- "token": push_config.get("PUSH_PLUS_TOKEN"),
373
- "title": title,
374
- "content": content,
375
- "topic": push_config.get("PUSH_PLUS_USER"),
376
- }
377
- body = json.dumps(data).encode(encoding="utf-8")
378
- headers = {"Content-Type": "application/json"}
379
- response = requests.post(url=url, data=body, headers=headers).json()
380
-
381
- if response["code"] == 200:
382
- print("PUSHPLUS 推送成功!")
383
-
384
- else:
385
- url_old = "http://pushplus.hxtrip.com/send"
386
- headers["Accept"] = "application/json"
387
- response = requests.post(url=url_old, data=body, headers=headers).json()
388
-
389
- if response["code"] == 200:
390
- print("PUSHPLUS(hxtrip) 推送成功!")
391
-
392
- else:
393
- print("PUSHPLUS 推送失败!")
394
-
395
-
396
- def weplus_bot(title: str, content: str) -> None:
397
- """
398
- 通过 微加机器人 推送消息。
399
- """
400
- if not push_config.get("WE_PLUS_BOT_TOKEN"):
401
- print("微加机器人 服务的 WE_PLUS_BOT_TOKEN 未设置!!\n取消推送")
402
- return
403
- print("微加机器人 服务启动")
404
-
405
- template = "txt"
406
- if len(content) > 800:
407
- template = "html"
408
-
409
- url = "https://www.weplusbot.com/send"
410
- data = {
411
- "token": push_config.get("WE_PLUS_BOT_TOKEN"),
412
- "title": title,
413
- "content": content,
414
- "template": template,
415
- "receiver": push_config.get("WE_PLUS_BOT_RECEIVER"),
416
- "version": push_config.get("WE_PLUS_BOT_VERSION"),
417
- }
418
- body = json.dumps(data).encode(encoding="utf-8")
419
- headers = {"Content-Type": "application/json"}
420
- response = requests.post(url=url, data=body, headers=headers).json()
421
-
422
- if response["code"] == 200:
423
- print("微加机器人 推送成功!")
424
- else:
425
- print("微加机器人 推送失败!")
426
-
427
-
428
- def qmsg_bot(title: str, content: str) -> None:
429
- """
430
- 使用 qmsg 推送消息。
431
- """
432
- if not push_config.get("QMSG_KEY") or not push_config.get("QMSG_TYPE"):
433
- print("qmsg 的 QMSG_KEY 或者 QMSG_TYPE 未设置!!\n取消推送")
434
- return
435
- print("qmsg 服务启动")
436
-
437
- url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}'
438
- payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")}
439
- response = requests.post(url=url, params=payload).json()
440
-
441
- if response["code"] == 0:
442
- print("qmsg 推送成功!")
443
- else:
444
- print(f'qmsg 推送失败!{response["reason"]}')
445
-
446
-
447
- def wecom_app(title: str, content: str) -> None:
448
- """
449
- 通过 企业微信 APP 推送消息。
450
- """
451
- if not push_config.get("QYWX_AM"):
452
- print("QYWX_AM 未设置!!\n取消推送")
453
- return
454
- QYWX_AM_AY = re.split(",", push_config.get("QYWX_AM"))
455
- if 4 < len(QYWX_AM_AY) > 5:
456
- print("QYWX_AM 设置错误!!\n取消推送")
457
- return
458
- print("企业微信 APP 服务启动")
459
-
460
- corpid = QYWX_AM_AY[0]
461
- corpsecret = QYWX_AM_AY[1]
462
- touser = QYWX_AM_AY[2]
463
- agentid = QYWX_AM_AY[3]
464
- try:
465
- media_id = QYWX_AM_AY[4]
466
- except IndexError:
467
- media_id = ""
468
- wx = WeCom(corpid, corpsecret, agentid)
469
- # 如果没有配置 media_id 默认就以 text 方式发送
470
- if not media_id:
471
- message = title + "\n\n" + content
472
- response = wx.send_text(message, touser)
473
- else:
474
- response = wx.send_mpnews(title, content, media_id, touser)
475
-
476
- if response == "ok":
477
- print("企业微信推送成功!")
478
- else:
479
- print("企业微信推送失败!错误信息如下:\n", response)
480
-
481
-
482
- class WeCom:
483
- def __init__(self, corpid, corpsecret, agentid):
484
- self.CORPID = corpid
485
- self.CORPSECRET = corpsecret
486
- self.AGENTID = agentid
487
- self.ORIGIN = "https://qyapi.weixin.qq.com"
488
- if push_config.get("QYWX_ORIGIN"):
489
- self.ORIGIN = push_config.get("QYWX_ORIGIN")
490
-
491
- def get_access_token(self):
492
- url = f"{self.ORIGIN}/cgi-bin/gettoken"
493
- values = {
494
- "corpid": self.CORPID,
495
- "corpsecret": self.CORPSECRET,
496
- }
497
- req = requests.post(url, params=values)
498
- data = json.loads(req.text)
499
- return data["access_token"]
500
-
501
- def send_text(self, message, touser="@all"):
502
- send_url = (
503
- f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}"
504
- )
505
- send_values = {
506
- "touser": touser,
507
- "msgtype": "text",
508
- "agentid": self.AGENTID,
509
- "text": {"content": message},
510
- "safe": "0",
511
- }
512
- send_msges = bytes(json.dumps(send_values), "utf-8")
513
- respone = requests.post(send_url, send_msges)
514
- respone = respone.json()
515
- return respone["errmsg"]
516
-
517
- def send_mpnews(self, title, message, media_id, touser="@all"):
518
- send_url = (
519
- f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}"
520
- )
521
- send_values = {
522
- "touser": touser,
523
- "msgtype": "mpnews",
524
- "agentid": self.AGENTID,
525
- "mpnews": {
526
- "articles": [
527
- {
528
- "title": title,
529
- "thumb_media_id": media_id,
530
- "author": "Author",
531
- "content_source_url": "",
532
- "content": message.replace("\n", "<br/>"),
533
- "digest": message,
534
- }
535
- ]
536
- },
537
- }
538
- send_msges = bytes(json.dumps(send_values), "utf-8")
539
- respone = requests.post(send_url, send_msges)
540
- respone = respone.json()
541
- return respone["errmsg"]
542
-
543
-
544
- def wecom_bot(title: str, content: str) -> None:
545
- """
546
- 通过 企业微信机器人 推送消息。
547
- """
548
- if not push_config.get("QYWX_KEY"):
549
- print("企业微信机器人 服务的 QYWX_KEY 未设置!!\n取消推送")
550
- return
551
- print("企业微信机器人服务启动")
552
-
553
- origin = "https://qyapi.weixin.qq.com"
554
- if push_config.get("QYWX_ORIGIN"):
555
- origin = push_config.get("QYWX_ORIGIN")
556
-
557
- url = f"{origin}/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}"
558
- headers = {"Content-Type": "application/json;charset=utf-8"}
559
- data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
560
- response = requests.post(
561
- url=url, data=json.dumps(data), headers=headers, timeout=15
562
- ).json()
563
-
564
- if response["errcode"] == 0:
565
- print("企业微信机器人推送成功!")
566
- else:
567
- print("企业微信机器人推送失败!")
568
-
569
-
570
- def telegram_bot(title: str, content: str) -> None:
571
- """
572
- 使用 telegram 机器人 推送消息。
573
- """
574
- if not push_config.get("TG_BOT_TOKEN") or not push_config.get("TG_USER_ID"):
575
- print("tg 服务的 bot_token 或者 user_id 未设置!!\n取消推送")
576
- return
577
- print("tg 服务启动")
578
-
579
- if push_config.get("TG_API_HOST"):
580
- url = f"{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
581
- else:
582
- url = (
583
- f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage"
584
- )
585
- headers = {"Content-Type": "application/x-www-form-urlencoded"}
586
- payload = {
587
- "chat_id": str(push_config.get("TG_USER_ID")),
588
- "text": f"{title}\n\n{content}",
589
- "disable_web_page_preview": "true",
590
- }
591
- proxies = None
592
- if push_config.get("TG_PROXY_HOST") and push_config.get("TG_PROXY_PORT"):
593
- if push_config.get("TG_PROXY_AUTH") is not None and "@" not in push_config.get(
594
- "TG_PROXY_HOST"
595
- ):
596
- push_config["TG_PROXY_HOST"] = (
597
- push_config.get("TG_PROXY_AUTH")
598
- + "@"
599
- + push_config.get("TG_PROXY_HOST")
600
- )
601
- proxyStr = "http://{}:{}".format(
602
- push_config.get("TG_PROXY_HOST"), push_config.get("TG_PROXY_PORT")
603
- )
604
- proxies = {"http": proxyStr, "https": proxyStr}
605
- response = requests.post(
606
- url=url, headers=headers, params=payload, proxies=proxies
607
- ).json()
608
-
609
- if response["ok"]:
610
- print("tg 推送成功!")
611
- else:
612
- print("tg 推送失败!")
613
-
614
-
615
- def aibotk(title: str, content: str) -> None:
616
- """
617
- 使用 智能微秘书 推送消息。
618
- """
619
- if (
620
- not push_config.get("AIBOTK_KEY")
621
- or not push_config.get("AIBOTK_TYPE")
622
- or not push_config.get("AIBOTK_NAME")
623
- ):
624
- print(
625
- "智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送"
626
- )
627
- return
628
- print("智能微秘书 服务启动")
629
-
630
- if push_config.get("AIBOTK_TYPE") == "room":
631
- url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
632
- data = {
633
- "apiKey": push_config.get("AIBOTK_KEY"),
634
- "roomName": push_config.get("AIBOTK_NAME"),
635
- "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"},
636
- }
637
- else:
638
- url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
639
- data = {
640
- "apiKey": push_config.get("AIBOTK_KEY"),
641
- "name": push_config.get("AIBOTK_NAME"),
642
- "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"},
643
- }
644
- body = json.dumps(data).encode(encoding="utf-8")
645
- headers = {"Content-Type": "application/json"}
646
- response = requests.post(url=url, data=body, headers=headers).json()
647
- print(response)
648
- if response["code"] == 0:
649
- print("智能微秘书 推送成功!")
650
- else:
651
- print(f'智能微秘书 推送失败!{response["error"]}')
652
-
653
-
654
- def smtp(title: str, content: str) -> None:
655
- """
656
- 使用 SMTP 邮件 推送消息。
657
- """
658
- if (
659
- not push_config.get("SMTP_SERVER")
660
- or not push_config.get("SMTP_SSL")
661
- or not push_config.get("SMTP_EMAIL")
662
- or not push_config.get("SMTP_PASSWORD")
663
- or not push_config.get("SMTP_NAME")
664
- ):
665
- print(
666
- "SMTP 邮件 的 SMTP_SERVER 或者 SMTP_SSL 或者 SMTP_EMAIL 或者 SMTP_PASSWORD 或者 SMTP_NAME 未设置!!\n取消推送"
667
- )
668
- return
669
- print("SMTP 邮件 服务启动")
670
-
671
- message = MIMEText(content, "plain", "utf-8")
672
- message["From"] = formataddr(
673
- (
674
- Header(push_config.get("SMTP_NAME"), "utf-8").encode(),
675
- push_config.get("SMTP_EMAIL"),
676
- )
677
- )
678
- message["To"] = formataddr(
679
- (
680
- Header(push_config.get("SMTP_NAME"), "utf-8").encode(),
681
- push_config.get("SMTP_EMAIL"),
682
- )
683
- )
684
- message["Subject"] = Header(title, "utf-8")
685
-
686
- try:
687
- smtp_server = (
688
- smtplib.SMTP_SSL(push_config.get("SMTP_SERVER"))
689
- if push_config.get("SMTP_SSL") == "true"
690
- else smtplib.SMTP(push_config.get("SMTP_SERVER"))
691
- )
692
- smtp_server.login(
693
- push_config.get("SMTP_EMAIL"), push_config.get("SMTP_PASSWORD")
694
- )
695
- smtp_server.sendmail(
696
- push_config.get("SMTP_EMAIL"),
697
- push_config.get("SMTP_EMAIL"),
698
- message.as_bytes(),
699
- )
700
- smtp_server.close()
701
- print("SMTP 邮件 推送成功!")
702
- except Exception as e:
703
- print(f"SMTP 邮件 推送失败!{e}")
704
-
705
-
706
- def pushme(title: str, content: str) -> None:
707
- """
708
- 使用 PushMe 推送消息。
709
- """
710
- if not push_config.get("PUSHME_KEY"):
711
- print("PushMe 服务的 PUSHME_KEY 未设置!!\n取消推送")
712
- return
713
- print("PushMe 服务启动")
714
-
715
- url = (
716
- push_config.get("PUSHME_URL")
717
- if push_config.get("PUSHME_URL")
718
- else "https://push.i-i.me/"
719
- )
720
- data = {
721
- "push_key": push_config.get("PUSHME_KEY"),
722
- "title": title,
723
- "content": content,
724
- "date": push_config.get("date") if push_config.get("date") else "",
725
- "type": push_config.get("type") if push_config.get("type") else "",
726
- }
727
- response = requests.post(url, data=data)
728
-
729
- if response.status_code == 200 and response.text == "success":
730
- print("PushMe 推送成功!")
731
- else:
732
- print(f"PushMe 推送失败!{response.status_code} {response.text}")
733
-
734
-
735
- def chronocat(title: str, content: str) -> None:
736
- """
737
- 使用 CHRONOCAT 推送消息。
738
- """
739
- if (
740
- not push_config.get("CHRONOCAT_URL")
741
- or not push_config.get("CHRONOCAT_QQ")
742
- or not push_config.get("CHRONOCAT_TOKEN")
743
- ):
744
- print("CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送")
745
- return
746
-
747
- print("CHRONOCAT 服务启动")
748
-
749
- user_ids = re.findall(r"user_id=(\d+)", push_config.get("CHRONOCAT_QQ"))
750
- group_ids = re.findall(r"group_id=(\d+)", push_config.get("CHRONOCAT_QQ"))
751
-
752
- url = f'{push_config.get("CHRONOCAT_URL")}/api/message/send'
753
- headers = {
754
- "Content-Type": "application/json",
755
- "Authorization": f'Bearer {push_config.get("CHRONOCAT_TOKEN")}',
756
- }
757
-
758
- for chat_type, ids in [(1, user_ids), (2, group_ids)]:
759
- if not ids:
760
- continue
761
- for chat_id in ids:
762
- data = {
763
- "peer": {"chatType": chat_type, "peerUin": chat_id},
764
- "elements": [
765
- {
766
- "elementType": 1,
767
- "textElement": {"content": f"{title}\n\n{content}"},
768
- }
769
- ],
770
- }
771
- response = requests.post(url, headers=headers, data=json.dumps(data))
772
- if response.status_code == 200:
773
- if chat_type == 1:
774
- print(f"QQ个人消息:{ids}推送成功!")
775
- else:
776
- print(f"QQ群消息:{ids}推送成功!")
777
- else:
778
- if chat_type == 1:
779
- print(f"QQ个人消息:{ids}推送失败!")
780
- else:
781
- print(f"QQ群消息:{ids}推送失败!")
782
-
783
- def ntfy(title: str, content: str) -> None:
784
- """
785
- 使用 Ntfy 推送消息。
786
- """
787
- if not push_config.get("Ntfy_T"):
788
- print("PushMe 服务的 PUSHME_KEY 未设置!!\n取消推送")
789
- return
790
- print("PushMe 服务启动")
791
-
792
- url = push_config.get("PUSHME_URL") if push_config.get("PUSHME_URL") else "https://push.i-i.me/"
793
- data = {
794
- "push_key": push_config.get("PUSHME_KEY"),
795
- "title": title,
796
- "content": content,
797
- "date": push_config.get("date") if push_config.get("date") else "",
798
- "type": push_config.get("type") if push_config.get("type") else "",
799
- }
800
- response = requests.post(url, data=data)
801
-
802
- if response.status_code == 200 and response.text == "success":
803
- print("PushMe 推送成功!")
804
- else:
805
- print(f"PushMe 推送失败!{response.status_code} {response.text}")
806
-
807
-
808
- def parse_headers(headers):
809
- if not headers:
810
- return {}
811
-
812
- parsed = {}
813
- lines = headers.split("\n")
814
-
815
- for line in lines:
816
- i = line.find(":")
817
- if i == -1:
818
- continue
819
-
820
- key = line[:i].strip().lower()
821
- val = line[i + 1 :].strip()
822
- parsed[key] = parsed.get(key, "") + ", " + val if key in parsed else val
823
-
824
- return parsed
825
-
826
-
827
- def parse_string(input_string, value_format_fn=None):
828
- matches = {}
829
- pattern = r"(\w+):\s*((?:(?!\n\w+:).)*)"
830
- regex = re.compile(pattern)
831
- for match in regex.finditer(input_string):
832
- key, value = match.group(1).strip(), match.group(2).strip()
833
- try:
834
- value = value_format_fn(value) if value_format_fn else value
835
- json_value = json.loads(value)
836
- matches[key] = json_value
837
- except:
838
- matches[key] = value
839
- return matches
840
-
841
-
842
- def parse_body(body, content_type, value_format_fn=None):
843
- if not body or content_type == "text/plain":
844
- return value_format_fn(body) if value_format_fn and body else body
845
-
846
- parsed = parse_string(body, value_format_fn)
847
-
848
- if content_type == "application/x-www-form-urlencoded":
849
- data = urllib.parse.urlencode(parsed, doseq=True)
850
- return data
851
-
852
- if content_type == "application/json":
853
- data = json.dumps(parsed)
854
- return data
855
-
856
- return parsed
857
-
858
-
859
- def custom_notify(title: str, content: str) -> None:
860
- """
861
- 通过 自定义通知 推送消息。
862
- """
863
- if not push_config.get("WEBHOOK_URL") or not push_config.get("WEBHOOK_METHOD"):
864
- print("自定义通知的 WEBHOOK_URL 或 WEBHOOK_METHOD 未设置!!\n取消推送")
865
- return
866
-
867
- print("自定义通知服务启动")
868
-
869
- WEBHOOK_URL = push_config.get("WEBHOOK_URL")
870
- WEBHOOK_METHOD = push_config.get("WEBHOOK_METHOD")
871
- WEBHOOK_CONTENT_TYPE = push_config.get("WEBHOOK_CONTENT_TYPE")
872
- WEBHOOK_BODY = push_config.get("WEBHOOK_BODY")
873
- WEBHOOK_HEADERS = push_config.get("WEBHOOK_HEADERS")
874
-
875
- if "$title" not in WEBHOOK_URL and "$title" not in WEBHOOK_BODY:
876
- print("请求头或者请求体中必须包含 $title 和 $content")
877
- return
878
-
879
- headers = parse_headers(WEBHOOK_HEADERS)
880
- body = parse_body(
881
- WEBHOOK_BODY,
882
- WEBHOOK_CONTENT_TYPE,
883
- lambda v: v.replace("$title", title.replace("\n", "\\n")).replace(
884
- "$content", content.replace("\n", "\\n")
885
- ),
886
- )
887
- formatted_url = WEBHOOK_URL.replace(
888
- "$title", urllib.parse.quote_plus(title)
889
- ).replace("$content", urllib.parse.quote_plus(content))
890
- response = requests.request(
891
- method=WEBHOOK_METHOD, url=formatted_url, headers=headers, timeout=15, data=body
892
- )
893
-
894
- if response.status_code == 200:
895
- print("自定义通知推送成功!")
896
- else:
897
- print(f"自定义通知推送失败!{response.status_code} {response.text}")
898
-
899
-
900
- def one() -> str:
901
- """
902
- 获取一条一言。
903
- :return:
904
- """
905
- url = "https://v1.hitokoto.cn/"
906
- res = requests.get(url).json()
907
- return res["hitokoto"] + " ----" + res["from"]
908
-
909
-
910
- def add_notify_function():
911
- notify_function = []
912
- if push_config.get("BARK_PUSH"):
913
- notify_function.append(bark)
914
- if push_config.get("CONSOLE"):
915
- notify_function.append(console)
916
- if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"):
917
- notify_function.append(dingding_bot)
918
- if push_config.get("FSKEY"):
919
- notify_function.append(feishu_bot)
920
- if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"):
921
- notify_function.append(go_cqhttp)
922
- if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"):
923
- notify_function.append(gotify)
924
- if push_config.get("IGOT_PUSH_KEY"):
925
- notify_function.append(iGot)
926
- if push_config.get("PUSH_KEY"):
927
- notify_function.append(serverJ)
928
- if push_config.get("DEER_KEY"):
929
- notify_function.append(pushdeer)
930
- if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"):
931
- notify_function.append(chat)
932
- if push_config.get("PUSH_PLUS_TOKEN"):
933
- notify_function.append(pushplus_bot)
934
- if push_config.get("WE_PLUS_BOT_TOKEN"):
935
- notify_function.append(weplus_bot)
936
- if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"):
937
- notify_function.append(qmsg_bot)
938
- if push_config.get("QYWX_AM"):
939
- notify_function.append(wecom_app)
940
- if push_config.get("QYWX_KEY"):
941
- notify_function.append(wecom_bot)
942
- if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
943
- notify_function.append(telegram_bot)
944
- if (
945
- push_config.get("AIBOTK_KEY")
946
- and push_config.get("AIBOTK_TYPE")
947
- and push_config.get("AIBOTK_NAME")
948
- ):
949
- notify_function.append(aibotk)
950
- if (
951
- push_config.get("SMTP_SERVER")
952
- and push_config.get("SMTP_SSL")
953
- and push_config.get("SMTP_EMAIL")
954
- and push_config.get("SMTP_PASSWORD")
955
- and push_config.get("SMTP_NAME")
956
- ):
957
- notify_function.append(smtp)
958
- if push_config.get("PUSHME_KEY"):
959
- notify_function.append(pushme)
960
- if (
961
- push_config.get("CHRONOCAT_URL")
962
- and push_config.get("CHRONOCAT_QQ")
963
- and push_config.get("CHRONOCAT_TOKEN")
964
- ):
965
- notify_function.append(chronocat)
966
- if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"):
967
- notify_function.append(custom_notify)
968
-
969
- if not notify_function:
970
- print(f"无推送渠道,请检查通知变量是否正确")
971
- return notify_function
972
-
973
-
974
- def send(title: str, content: str, ignore_default_config: bool = False, **kwargs):
975
- if kwargs:
976
- global push_config
977
- if ignore_default_config:
978
- push_config = kwargs # 清空从环境变量获取的配置
979
- else:
980
- push_config.update(kwargs)
981
-
982
- if not content:
983
- print(f"{title} 推送内容为空!")
984
- return
985
-
986
- # 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔
987
- skipTitle = os.getenv("SKIP_PUSH_TITLE")
988
- if skipTitle:
989
- if title in re.split("\n", skipTitle):
990
- print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!")
991
- return
992
-
993
- hitokoto = push_config.get("HITOKOTO")
994
- content += "\n\n" + one() if hitokoto != "false" else ""
995
-
996
- notify_function = add_notify_function()
997
- ts = [
998
- threading.Thread(target=mode, args=(title, content), name=mode.__name__)
999
- for mode in notify_function
1000
- ]
1001
- [t.start() for t in ts]
1002
- [t.join() for t in ts]
1003
-
1004
-
1005
- def main():
1006
- send("title", "content")
1007
-
1008
-
1009
- if __name__ == "__main__":
1010
- main()