From 2158a3227e1158078434dac2a0b538700bd3b433 Mon Sep 17 00:00:00 2001
From: Zebartin <16185081+Zebartin@users.noreply.github.com>
Date: Tue, 18 Mar 2025 21:55:45 +0800
Subject: [PATCH] =?UTF-8?q?feat(push):=20=E4=BD=BF=E7=94=A8Apprise?=
=?UTF-8?q?=E6=8E=A8=E9=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/deploy.yml | 3 +-
Dockerfile | 7 +-
README.md | 1 +
apprise_plugins/pushplus.py | 25 ++++++
apprise_plugins/wxpusherspt.py | 16 ++++
config.py | 2 +
main.py | 6 +-
push.py | 142 +++++++++++----------------------
requirements.txt | 3 +
9 files changed, 102 insertions(+), 103 deletions(-)
create mode 100644 apprise_plugins/pushplus.py
create mode 100644 apprise_plugins/wxpusherspt.py
create mode 100644 requirements.txt
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 8922c1db..ea89370c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -28,12 +28,13 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install certifi==2024.8.30 charset-normalizer==3.4.0 idna==3.10 requests==2.32.3 urllib3==2.2.3
+ pip install -r ./requirements
- name: Run deployment script
env:
WXREAD_CURL_BASH: ${{ secrets.WXREAD_CURL_BASH }}
PUSH_METHOD: ${{ secrets.PUSH_METHOD }}
+ APPRISE_URLS: ${{ secrets.APPRISE_URLS }}
PUSHPLUS_TOKEN: ${{ secrets.PUSHPLUS_TOKEN }}
WXPUSHER_SPT: ${{ secrets.WXPUSHER_SPT }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
index 4e605031..167aa71a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,15 +12,14 @@ RUN apt-get update && apt-get install -y cron && rm -rf /var/lib/apt/lists/*
ENV PATH="/usr/local/bin:${PATH}"
# 复制项目文件
-COPY main.py push.py config.py ./
+COPY main.py push.py config.py requirements.txt ./
+COPY apprise_plugins ./apprise_plugins
# 创建日志目录并设置权限
RUN mkdir -p /app/logs && chmod 777 /app/logs
# 安装 Python 依赖
-RUN pip install --no-cache-dir \
- requests>=2.32.3 \
- urllib3>=2.2.3
+RUN pip install --no-cache-dir -r ./requirements.txt
# 创建 cron 任务(每天凌晨1点执行)
RUN echo "0 1 * * * cd /app && /usr/local/bin/python3 main.py >> /app/logs/\$(date +\%Y-\%m-\%d).log 2>&1" > /etc/cron.d/wxread-cron
diff --git a/README.md b/README.md
index 603130fb..f5aa0efd 100644
--- a/README.md
+++ b/README.md
@@ -49,6 +49,7 @@
| `WXREAD_CURL_BASH` | `read` 接口 `curl_bash`数据 | **必填**,必须提供有效指令 | secrets |
| `READ_NUM` | 阅读次数(每次 30 秒) | **可选**,阅读时长,默认 60 分钟 | variables |
| `PUSH_METHOD` | `pushplus`/`wxpusher`/`telegram` | **可选**,推送方式,3选1,默认不推送 | secrets |
+| `APPRISE_URLS` | Apprise支持的各类通知URL,包括Discord、Slack、Gotify、电子邮件等等,具体见[列表说明](https://github.com/caronc/apprise?tab=readme-ov-file#supported-notifications)
此外本项目还额外支持`pushplus://{token}`和`wxpusherspt://{SPT}`两种形式 | **可选**,可以同时指定多个URL,不同URL之间以`,`隔开
当`PUSH_METHOD`不为空时此项无效 | secrets |
| `PUSHPLUS_TOKEN` | PushPlus 的 token | 当 `PUSH_METHOD=pushplus` 时必填,[获取地址](https://www.pushplus.plus/uc.html) | secrets |
| `WXPUSHER_SPT` | WxPusher 的token | 当 `PUSH_METHOD=wxpusher` 时必填,[获取地址](https://wxpusher.zjiecode.com/docs/#/?id=获取spt) | secrets |
| `TELEGRAM_BOT_TOKEN`
`TELEGRAM_CHAT_ID`
`http_proxy`/`https_proxy`(可选)| 群组id以及机器人token | 当 `PUSH_METHOD=telegram` 时必填,[配置文档](https://www.nodeseek.com/post-22475-1) | secrets |
diff --git a/apprise_plugins/pushplus.py b/apprise_plugins/pushplus.py
new file mode 100644
index 00000000..d8c5c1ae
--- /dev/null
+++ b/apprise_plugins/pushplus.py
@@ -0,0 +1,25 @@
+import logging
+
+import requests
+from apprise.decorators import notify
+
+logger = logging.getLogger(__name__)
+
+
+@notify(on='pushplus', name='pushplus')
+def notify_pushplus(body: str, title: str, notify_type: str, meta: dict, *args, **kwargs):
+ """PushPlus消息推送"""
+ pushplus_url = "https://www.pushplus.plus/send"
+ token = meta.get('host', '')
+ response = requests.post(
+ pushplus_url,
+ json={
+ "token": token,
+ "title": title,
+ "content": body
+ },
+ headers={'Content-Type': 'application/json'},
+ timeout=10
+ )
+ response.raise_for_status()
+ logger.info("✅ PushPlus响应: %s", response.text)
diff --git a/apprise_plugins/wxpusherspt.py b/apprise_plugins/wxpusherspt.py
new file mode 100644
index 00000000..1f859d6d
--- /dev/null
+++ b/apprise_plugins/wxpusherspt.py
@@ -0,0 +1,16 @@
+import logging
+
+import requests
+from apprise.decorators import notify
+
+logger = logging.getLogger(__name__)
+
+
+@notify(on='wxpusherspt', name='WxPusher SPT')
+def notify_wxpusher_spt(body: str, title: str, notify_type: str, meta: dict, *args, **kwargs):
+ """WxPusher消息推送(极简方式)"""
+ spt = meta.get('host', '')
+ wxpusher_simple_url = f"https://wxpusher.zjiecode.com/api/send/message/{spt}/{body}"
+ response = requests.get(wxpusher_simple_url, timeout=10)
+ response.raise_for_status()
+ logger.info("✅ WxPusher响应: %s", response.text)
diff --git a/config.py b/config.py
index ba702b21..e77577d3 100644
--- a/config.py
+++ b/config.py
@@ -11,6 +11,8 @@
READ_NUM = int(os.getenv('READ_NUM') or 120)
# 需要推送时可选,可选pushplus、wxpusher、telegram
PUSH_METHOD = "" or os.getenv('PUSH_METHOD')
+# 通用推送配置
+APPRISE_URLS = [u.strip() for u in os.getenv("APPRISE_URLS", '').split(',')]
# pushplus推送时需填
PUSHPLUS_TOKEN = "" or os.getenv("PUSHPLUS_TOKEN")
# telegram推送时需填
diff --git a/main.py b/main.py
index a89466cc..bf4a67bc 100644
--- a/main.py
+++ b/main.py
@@ -78,12 +78,10 @@ def get_wr_skey():
else:
ERROR_CODE = "❌ 无法获取新密钥或者WXREAD_CURL_BASH配置有误,终止运行。"
logging.error(ERROR_CODE)
- push(ERROR_CODE, PUSH_METHOD)
+ push(ERROR_CODE)
raise Exception(ERROR_CODE)
data.pop('s')
logging.info("🎉 阅读脚本已完成!")
-if PUSH_METHOD not in (None, ''):
- logging.info("⏱️ 开始推送...")
- push(f"🎉 微信读书自动阅读完成!\n⏱️ 阅读时长:{(index - 1) * 0.5}分钟。", PUSH_METHOD)
+push(f"🎉 微信读书自动阅读完成!\n⏱️ 阅读时长:{(index - 1) * 0.5}分钟。")
diff --git a/push.py b/push.py
index 02a967f9..f4008769 100644
--- a/push.py
+++ b/push.py
@@ -1,108 +1,62 @@
-# push.py 支持 PushPlus 、wxpusher、Telegram 的消息推送模块
+# push.py 支持 Apprise、PushPlus 、wxpusher spt 的消息推送模块
+import logging
import os
import random
import time
-import json
-import requests
-import logging
-from config import PUSHPLUS_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_BOT_TOKEN, WXPUSHER_SPT
-
-logger = logging.getLogger(__name__)
+from apprise import Apprise, AppriseAsset
-class PushNotification:
- def __init__(self):
- self.pushplus_url = "https://www.pushplus.plus/send"
- self.telegram_url = "https://api.telegram.org/bot{}/sendMessage"
- self.headers = {'Content-Type': 'application/json'}
- # 从环境变量获取代理设置
- self.proxies = {
- 'http': os.getenv('http_proxy'),
- 'https': os.getenv('https_proxy')
- }
- self.wxpusher_simple_url = "https://wxpusher.zjiecode.com/api/send/message/{}/{}"
+from config import (APPRISE_URLS, PUSH_METHOD, PUSHPLUS_TOKEN,
+ TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, WXPUSHER_SPT)
- def push_pushplus(self, content, token):
- """PushPlus消息推送"""
- attempts = 5
- for attempt in range(attempts):
- try:
- response = requests.post(
- self.pushplus_url,
- data=json.dumps({
- "token": token,
- "title": "微信阅读推送...",
- "content": content
- }).encode('utf-8'),
- headers=self.headers,
- timeout=10
- )
- response.raise_for_status()
- logger.info("✅ PushPlus响应: %s", response.text)
- break # 成功推送,跳出循环
- except requests.exceptions.RequestException as e:
- logger.error("❌ PushPlus推送失败: %s", e)
- if attempt < attempts - 1: # 如果不是最后一次尝试
- sleep_time = random.randint(180, 360) # 随机3到6分钟
- logger.info("将在 %d 秒后重试...", sleep_time)
- time.sleep(sleep_time)
+logger = logging.getLogger(__name__)
- def push_telegram(self, content, bot_token, chat_id):
- """Telegram消息推送,失败时自动尝试直连"""
- url = self.telegram_url.format(bot_token)
- payload = {"chat_id": chat_id, "text": content}
- try:
- # 先尝试代理
- response = requests.post(url, json=payload, proxies=self.proxies, timeout=30)
- logger.info("✅ Telegram响应: %s", response.text)
- response.raise_for_status()
+def push_one(content: str, url: str, apprise_obj: Apprise):
+ attempts = 5
+ result = False
+ for attempt in range(attempts):
+ result = apprise_obj.notify(body=content, title='微信阅读推送...')
+ if result:
+ logger.info(f"✅ 推送成功:{url}")
return True
- except Exception as e:
- logger.error("❌ Telegram代理发送失败: %s", e)
- try:
- # 代理失败后直连
- response = requests.post(url, json=payload, timeout=30)
- response.raise_for_status()
- return True
- except Exception as e:
- logger.error("❌ Telegram发送失败: %s", e)
- return False
-
- def push_wxpusher(self, content, spt):
- """WxPusher消息推送(极简方式)"""
- attempts = 5
- url = self.wxpusher_simple_url.format(spt, content)
-
- for attempt in range(attempts):
- try:
- response = requests.get(url, timeout=10)
- response.raise_for_status()
- logger.info("✅ WxPusher响应: %s", response.text)
- break
- except requests.exceptions.RequestException as e:
- logger.error("❌ WxPusher推送失败: %s", e)
- if attempt < attempts - 1:
- sleep_time = random.randint(180, 360)
- logger.info("将在 %d 秒后重试...", sleep_time)
- time.sleep(sleep_time)
+ else:
+ logger.error(f"❌ 推送失败:{url}")
+ if attempt < attempts - 1:
+ sleep_time = random.randint(180, 360)
+ logger.info(f"将在 {sleep_time} 秒后重试...")
+ time.sleep(sleep_time)
+ return False
"""外部调用"""
-def push(content, method):
- """统一推送接口,支持 PushPlus、Telegram 和 WxPusher"""
- notifier = PushNotification()
-
- if method == "pushplus":
- token = PUSHPLUS_TOKEN
- return notifier.push_pushplus(content, token)
- elif method == "telegram":
- bot_token = TELEGRAM_BOT_TOKEN
- chat_id = TELEGRAM_CHAT_ID
- return notifier.push_telegram(content, bot_token, chat_id)
- elif method == "wxpusher":
- return notifier.push_wxpusher(content, WXPUSHER_SPT)
- else:
- raise ValueError("❌ 无效的通知渠道,请选择 'pushplus'、'telegram' 或 'wxpusher'")
+def push(content: str):
+ """统一推送接口,通过Apprise实现,额外支持 PushPlus 和 WxPusher 极简方式"""
+ urls = APPRISE_URLS
+ apprise_obj = Apprise(asset=AppriseAsset(
+ plugin_paths=os.path.join(__file__, '../apprise_plugins')
+ ))
+ if PUSH_METHOD == 'pushplus':
+ urls = [f'pushplus://{PUSHPLUS_TOKEN}']
+ elif PUSH_METHOD == 'wxpusher':
+ urls = [f'wxpusherspt://{WXPUSHER_SPT}']
+ elif PUSH_METHOD == 'telegram':
+ urls = [f'tgram://{TELEGRAM_BOT_TOKEN}/{TELEGRAM_CHAT_ID}']
+ if urls:
+ logging.info("⏱️ 开始推送...")
+ for url in urls:
+ # 由于Apprise不支持返回各个url成功与否的结果,故单独处理各个url
+ if not apprise_obj.add(url):
+ logger.error(f"❌ 通知渠道无效:{url}")
+ continue
+ if not push_one(content, url, apprise_obj):
+ old_no_proxy = os.getenv('no_proxy')
+ os.environ['no_proxy'] = '*'
+ push_one(content, url, apprise_obj)
+ if old_no_proxy:
+ os.environ['no_proxy'] = old_no_proxy
+ else:
+ os.environ.pop('no_proxy', None)
+ apprise_obj.clear()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..236a3e80
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+requests>=2.32.3
+urllib3>=2.2.3
+apprise
\ No newline at end of file