diff --git a/app/api/endpoints/message.py b/app/api/endpoints/message.py index 11ad51c4..dbe3a382 100644 --- a/app/api/endpoints/message.py +++ b/app/api/endpoints/message.py @@ -1,13 +1,15 @@ +import json from typing import Union, Any, List -from fastapi import APIRouter, BackgroundTasks, Depends +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException from fastapi import Request +from pywebpush import WebPushException, webpush from sqlalchemy.orm import Session from starlette.responses import PlainTextResponse from app import schemas from app.chain.message import MessageChain -from app.core.config import settings +from app.core.config import settings, global_vars from app.core.security import verify_token from app.db import get_db from app.db.models import User @@ -155,3 +157,33 @@ def set_switchs(switchs: List[NotificationSwitch], SystemConfigOper().set(SystemConfigKey.NotificationChannels, switch_list) return schemas.Response(success=True) + + +@router.post("/subscribe", summary="客户端webpush通知订阅", response_model=schemas.Response) +def subscribe(subscription: schemas.Subscription, _: schemas.TokenPayload = Depends(verify_token)): + """ + 客户端webpush通知订阅 + """ + global_vars.push_subscription(subscription.dict()) + return schemas.Response(success=True) + + +@router.post("/send-webpush", summary="发送webpush通知", response_model=schemas.Response) +def send_notification(payload: schemas.SubscriptionMessage, _: schemas.TokenPayload = Depends(verify_token)): + """ + 发送webpush通知 + """ + try: + for sub in global_vars.get_subscriptions(): + webpush( + subscription_info=sub, + data=json.dumps(payload.dict()), + vapid_private_key=settings.VAPID.get("private_key"), + vapid_claims={ + "sub": settings.VAPID.get("subject") + }, + ) + return schemas.Response(success=True) + except WebPushException as ex: + print("WebPush Error:", repr(ex)) + raise HTTPException(status_code=500, detail=str(ex)) diff --git a/app/core/config.py b/app/core/config.py index 75a989a0..5da4e011 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -2,7 +2,7 @@ import secrets import sys import threading from pathlib import Path -from typing import Optional +from typing import Optional, List from pydantic import BaseSettings, validator @@ -302,7 +302,7 @@ class Settings(BaseSettings): @property def LOG_PATH(self): return self.CONFIG_PATH / "logs" - + @property def COOKIE_PATH(self): return self.CONFIG_PATH / "cookies" @@ -372,6 +372,14 @@ class Settings(BaseSettings): return [] return [d for d in settings.DOWNLOADER.split(",") if d] + @property + def VAPID(self): + return { + "subject": f"mailto: <{self.SUPERUSER}@movie-pilot.org>", + "publicKey": "BH3w49sZA6jXUnE-yt4jO6VKh73lsdsvwoJ6Hx7fmPIDKoqGiUl2GEoZzy-iJfn4SfQQcx7yQdHf9RknwrL_lSM", + "privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894" + } + def __init__(self, **kwargs): super().__init__(**kwargs) with self.CONFIG_PATH as p: @@ -400,6 +408,8 @@ class GlobalVar(object): """ # 系统停止事件 STOP_EVENT: threading.Event = threading.Event() + # webpush订阅 + SUBSCRIPTIONS: List[dict] = [] def stop_system(self): """ @@ -413,6 +423,18 @@ class GlobalVar(object): """ return self.STOP_EVENT.is_set() + def get_subscriptions(self): + """ + 获取webpush订阅 + """ + return self.SUBSCRIPTIONS + + def push_subscription(self, subscription: dict): + """ + 添加webpush订阅 + """ + self.SUBSCRIPTIONS.append(subscription) + # 实例化配置 settings = Settings( diff --git a/app/helper/plugin.py b/app/helper/plugin.py index abf10c9d..e68849e8 100644 --- a/app/helper/plugin.py +++ b/app/helper/plugin.py @@ -35,6 +35,10 @@ class PluginHelper(metaclass=Singleton): if self.install_report(): self.systemconfig.set(SystemConfigKey.PluginInstallReport, "1") + @property + def proxies(self): + return None if settings.GITHUB_PROXY else settings.PROXY + @cached(cache=TTLCache(maxsize=1000, ttl=1800)) def get_plugins(self, repo_url: str) -> Dict[str, dict]: """ @@ -47,7 +51,7 @@ class PluginHelper(metaclass=Singleton): if not user or not repo: return {} raw_url = self._base_url % (user, repo) - res = RequestUtils(proxies=settings.PROXY, headers=settings.GITHUB_HEADERS, + res = RequestUtils(proxies=self.proxies, headers=settings.GITHUB_HEADERS, timeout=10).get_res(f"{raw_url}package.json") if res: try: @@ -159,7 +163,7 @@ class PluginHelper(metaclass=Singleton): if item.get("download_url"): download_url = f"{settings.GITHUB_PROXY}{item.get('download_url')}" # 下载插件文件 - res = RequestUtils(proxies=settings.PROXY, + res = RequestUtils(proxies=self.proxies, headers=settings.GITHUB_HEADERS, timeout=60).get_res(download_url) if not res: return False, f"文件 {item.get('name')} 下载失败!" diff --git a/app/helper/resource.py b/app/helper/resource.py index 8316807b..aa7edb49 100644 --- a/app/helper/resource.py +++ b/app/helper/resource.py @@ -23,6 +23,10 @@ class ResourceHelper(metaclass=Singleton): self.siteshelper = SitesHelper() self.check() + @property + def proxies(self): + return None if settings.GITHUB_PROXY else settings.PROXY + def check(self): """ 检测是否有更新,如有则下载安装 @@ -32,7 +36,7 @@ class ResourceHelper(metaclass=Singleton): if SystemUtils.is_frozen(): return logger.info("开始检测资源包版本...") - res = RequestUtils(proxies=settings.PROXY, headers=settings.GITHUB_HEADERS, timeout=10).get_res(self._repo) + res = RequestUtils(proxies=self.proxies, headers=settings.GITHUB_HEADERS, timeout=10).get_res(self._repo) if res: try: resource_info = json.loads(res.text) @@ -89,7 +93,7 @@ class ResourceHelper(metaclass=Singleton): logger.info(f"开始更新资源文件:{item.get('name')} ...") download_url = f"{settings.GITHUB_PROXY}{item.get('download_url')}" # 下载资源文件 - res = RequestUtils(proxies=settings.PROXY, headers=settings.GITHUB_HEADERS, + res = RequestUtils(proxies=self.proxies, headers=settings.GITHUB_HEADERS, timeout=180).get_res(download_url) if not res: logger.error(f"文件 {item.get('name')} 下载失败!") diff --git a/app/schemas/message.py b/app/schemas/message.py index 55999947..e6ac0173 100644 --- a/app/schemas/message.py +++ b/app/schemas/message.py @@ -84,3 +84,22 @@ class NotificationSwitch(BaseModel): synologychat: Optional[bool] = False # VoceChat开关 vocechat: Optional[bool] = False + + +class Subscription(BaseModel): + """ + 客户端消息订阅 + """ + endpoint: Optional[str] + keys: Optional[dict] = {} + + +class SubscriptionMessage(BaseModel): + """ + 客户端订阅消息体 + """ + title: Optional[str] + body: Optional[str] + icon: Optional[str] + url: Optional[str] + data: Optional[dict] = {} diff --git a/requirements.txt b/requirements.txt index 25acb3b0..71cbdd64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,4 +56,5 @@ cachetools~=5.3.1 fast-bencode~=1.1.3 pystray~=0.19.5 pyotp~=2.9.0 -Pinyin2Hanzi~=0.1.1 \ No newline at end of file +Pinyin2Hanzi~=0.1.1 +pywebpush~=2.0.0 diff --git a/update b/update index a400cf2d..5bc47855 100644 --- a/update +++ b/update @@ -92,6 +92,7 @@ if [[ "${MOVIEPILOT_AUTO_UPDATE}" = "true" ]] || [[ "${MOVIEPILOT_AUTO_UPDATE}" if [ -n "${PROXY_HOST}" ]; then CURL_OPTIONS="-sL -x ${PROXY_HOST}" PIP_OPTIONS="--proxy=${PROXY_HOST}" + GITHUB_PROXY="" echo "使用代理更新程序" else CURL_OPTIONS="-sL"