feat: add pushdeer notification

This commit is contained in:
acgnhik 2022-05-03 11:04:21 +08:00
parent cc44f20c2f
commit d5d9ece36a
34 changed files with 496 additions and 22 deletions

View File

@ -31,6 +31,7 @@ from .setting import (
from .notification import (
EmailNotifier,
ServerchanNotifier,
PushdeerNotifier,
PushplusNotifier,
TelegramNotifier,
)
@ -327,10 +328,12 @@ class Application:
def _setup_notifiers(self) -> None:
self._email_notifier = EmailNotifier()
self._serverchan_notifier = ServerchanNotifier()
self._pushdeer_notifier = PushdeerNotifier()
self._pushplus_notifier = PushplusNotifier()
self._telegram_notifier = TelegramNotifier()
self._settings_manager.apply_email_notification_settings()
self._settings_manager.apply_serverchan_notification_settings()
self._settings_manager.apply_pushdeer_notification_settings()
self._settings_manager.apply_pushplus_notification_settings()
self._settings_manager.apply_telegram_notification_settings()
@ -361,10 +364,12 @@ class Application:
def _destroy_notifiers(self) -> None:
self._email_notifier.disable()
self._serverchan_notifier.disable()
self._pushdeer_notifier.disable()
self._pushplus_notifier.disable()
self._telegram_notifier.disable()
del self._email_notifier
del self._serverchan_notifier
del self._pushdeer_notifier
del self._pushplus_notifier
del self._telegram_notifier

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,6 @@
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
<script src="runtime.c354415fc493c1ff.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.ee42aef57b3529db.js" type="module"></script>
<script src="runtime.5296fd12ffdfadbe.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.b9234f0840c7101a.js" type="module"></script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"configVersion": 1,
"timestamp": 1651319640894,
"timestamp": 1651566774775,
"index": "/index.html",
"assetGroups": [
{
@ -13,16 +13,16 @@
"urls": [
"/103.5b5d2a6e5a8a7479.js",
"/146.92e3b29c4c754544.js",
"/369.6447ab1db6f39c5d.js",
"/45.c90c3cea2bf1a66e.js",
"/66.925f958307346684.js",
"/474.88f730916af2dc81.js",
"/66.17103bf51c59b5c8.js",
"/869.ac675e78fa0ea7cf.js",
"/common.858f777e9296e6f2.js",
"/index.html",
"/main.ee42aef57b3529db.js",
"/main.b9234f0840c7101a.js",
"/manifest.webmanifest",
"/polyfills.4b08448aee19bb22.js",
"/runtime.c354415fc493c1ff.js",
"/runtime.5296fd12ffdfadbe.js",
"/styles.1f581691b230dc4d.css"
],
"patterns": []
@ -1636,9 +1636,9 @@
"hashTable": {
"/103.5b5d2a6e5a8a7479.js": "cc0240f217015b6d4ddcc14f31fcc42e1c1c282a",
"/146.92e3b29c4c754544.js": "3824de681dd1f982ea69a065cdf54d7a1e781f4d",
"/369.6447ab1db6f39c5d.js": "4e3ec6f0b1742d78c52c49ba2ce0099778ae369c",
"/45.c90c3cea2bf1a66e.js": "e5bfb8cf3803593e6b8ea14c90b3d3cb6a066764",
"/66.925f958307346684.js": "cda15ab0b7c746412407ced24061e5913450d955",
"/474.88f730916af2dc81.js": "e7cb3e7bd68c162633d94c8c848dea2daeac8bc3",
"/66.17103bf51c59b5c8.js": "67c9bb3ac7e7c7c25ebe1db69fc87890a2fdc184",
"/869.ac675e78fa0ea7cf.js": "f45052016cb5201d5784b3f261e719d96bd1b153",
"/assets/animal/panda.js": "fec2868bb3053dd2da45f96bbcb86d5116ed72b1",
"/assets/animal/panda.svg": "bebd302cdc601e0ead3a6d2710acf8753f3d83b1",
@ -3234,11 +3234,11 @@
"/assets/twotone/warning.js": "fb2d7ea232f3a99bf8f080dbc94c65699232ac01",
"/assets/twotone/warning.svg": "8c7a2d3e765a2e7dd58ac674870c6655cecb0068",
"/common.858f777e9296e6f2.js": "b68ca68e1e214a2537d96935c23410126cc564dd",
"/index.html": "cf9bd8f6b2acd7238eb2a0a7d807baaa36f2daed",
"/main.ee42aef57b3529db.js": "003c7b5dec11f8297a28878ba376f0ba429fb045",
"/manifest.webmanifest": "0c4534b4c868d756691b1b4372cecb2efce47c6d",
"/index.html": "4957097d609200632fe355aef8f7603a3bb1addc",
"/main.b9234f0840c7101a.js": "c8c7b588c070b957a2659f62d6a77de284aa2233",
"/manifest.webmanifest": "62c1cb8c5ad2af551a956b97013ab55ce77dd586",
"/polyfills.4b08448aee19bb22.js": "8e73f2d42cc13ca353cea5c886d930bd6da08d0d",
"/runtime.c354415fc493c1ff.js": "f6682aed9b5806652e888520f71d062494af864b",
"/runtime.5296fd12ffdfadbe.js": "5b84a91028ab9e0daaaf89d70f2d12c48d5e358e",
"/styles.1f581691b230dc4d.css": "6f5befbbad57c2b2e80aae855139744b8010d150"
},
"navigationUrls": [

View File

@ -1 +1 @@
(()=>{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,o]=e[n],c=!0,l=0;l<t.length;l++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,o<a&&(a=o));if(c){e.splice(n--,1);var d=f();void 0!==d&&(i=d)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"925f958307346684",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",369:"6447ab1db6f39c5d",592:"858f777e9296e6f2",869:"ac675e78fa0ea7cf"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==i+o){a=u;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+o),a.src=r.tu(t)),e[t]=[f];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((u,s)=>n=e[f]=[u,s]);o.push(n[2]=a);var c=r.p+r.u(f),l=new Error;r.l(c,u=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,o)=>{var l,d,[n,a,c]=o,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(f&&f(o);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[n[u]]=0;return r.O(s)},t=self.webpackChunkblrec=self.webpackChunkblrec||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,o,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,o,f]=e[n],c=!0,l=0;l<t.length;l++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f<a&&(a=f));if(c){e.splice(n--,1);var d=o();void 0!==d&&(i=d)}}return i}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,o,f]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"17103bf51c59b5c8",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",474:"88f730916af2dc81",592:"858f777e9296e6f2",869:"ac675e78fa0ea7cf"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,o,f,n)=>{if(e[t])e[t].push(o);else{var a,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==i+f){a=u;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+f),a.src=r.tu(t)),e[t]=[o];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(o,f)=>{var n=r.o(e,o)?e[o]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=o){var a=new Promise((u,s)=>n=e[o]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(o),l=new Error;r.l(c,u=>{if(r.o(e,o)&&(0!==(n=e[o])&&(e[o]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+o+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+o,o)}else e[o]=0},r.O.j=o=>0===e[o];var i=(o,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(o&&o(f);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[n[u]]=0;return r.O(s)},t=self.webpackChunkblrec=self.webpackChunkblrec||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();

View File

@ -3,6 +3,7 @@ from .notifiers import (
MessageNotifier,
EmailNotifier,
ServerchanNotifier,
PushdeerNotifier,
PushplusNotifier,
TelegramNotifier,
)
@ -10,6 +11,7 @@ from .providers import (
MessagingProvider,
EmailService,
Serverchan,
Pushdeer,
Pushplus,
Telegram,
)
@ -19,6 +21,7 @@ __all__ = (
'MessagingProvider',
'EmailService',
'Serverchan',
'Pushdeer',
'Pushplus',
'Telegram',
@ -26,6 +29,7 @@ __all__ = (
'MessageNotifier',
'EmailNotifier',
'ServerchanNotifier',
'PushdeerNotifier',
'PushplusNotifier',
'TelegramNotifier',
)

View File

@ -13,6 +13,7 @@ from .providers import (
EmailService,
MessagingProvider,
Serverchan,
Pushdeer,
Pushplus,
Telegram,
)
@ -37,6 +38,7 @@ __all__ = (
'MessageNotifier',
'EmailNotifier',
'ServerchanNotifier',
'PushdeerNotifier',
'PushplusNotifier',
'TelegramNotifier',
)
@ -190,6 +192,18 @@ class ServerchanNotifier(MessageNotifier):
logger.debug('Disabled Serverchan notifier')
class PushdeerNotifier(MessageNotifier):
provider = Pushdeer.get_instance()
def _do_enable(self) -> None:
super()._do_enable()
logger.debug('Enabled Pushdeer notifier')
def _do_disable(self) -> None:
super()._do_disable()
logger.debug('Disabled Pushdeer notifier')
class PushplusNotifier(MessageNotifier):
provider = Pushplus.get_instance()

View File

@ -1,19 +1,24 @@
import ssl
import logging
import asyncio
import logging
import smtplib
import ssl
from abc import ABC, abstractmethod
from typing import TypedDict, cast, Literal
from email.message import EmailMessage
from http.client import HTTPException
from typing import Final, Literal, TypedDict, cast
from urllib.parse import urljoin
import aiohttp
from ..utils.patterns import Singleton
__all__ = (
'MessagingProvider', 'EmailService', 'Serverchan', 'Pushplus', 'Telegram'
'MessagingProvider',
'EmailService',
'Serverchan',
'Pushdeer',
'Pushplus',
'Telegram',
)
@ -109,6 +114,44 @@ class Serverchan(MessagingProvider):
pass
class PushdeerResponse(TypedDict):
code: int
content: str
error: str
class Pushdeer(MessagingProvider):
_server: Final = 'https://api2.pushdeer.com'
_endpoint: Final = '/message/push'
def __init__(self, server: str = '', pushkey: str = '') -> None:
super().__init__()
self.server = server
self.pushkey = pushkey
async def send_message(self, title: str, content: str) -> None:
self._check_parameters()
await self._post_message(title, content)
def _check_parameters(self) -> None:
if not self.pushkey:
raise ValueError('No pushkey supplied')
async def _post_message(self, title: str, content: str) -> None:
url = urljoin(self.server or self._server, self._endpoint)
payload = {
'pushkey': self.pushkey,
'text': title,
'desp': content,
'type': 'text',
}
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.post(url, json=payload) as res:
response = cast(PushdeerResponse, await res.json())
if response['code'] != 0:
raise HTTPException(response['code'], response['error'])
class PushplusResponse(TypedDict):
code: int
msg: str

View File

@ -22,12 +22,14 @@ from .models import (
SpaceSettings,
EmailSettings,
ServerchanSettings,
PushdeerSettings,
PushplusSettings,
TelegramSettings,
NotifierSettings,
NotificationSettings,
EmailNotificationSettings,
ServerchanNotificationSettings,
PushdeerNotificationSettings,
PushplusNotificationSettings,
TelegramNotificationSettings,
WebHookSettings,
@ -64,12 +66,14 @@ __all__ = (
'SpaceSettings',
'EmailSettings',
'ServerchanSettings',
'PushdeerSettings',
'PushplusSettings',
'TelegramSettings',
'NotifierSettings',
'NotificationSettings',
'EmailNotificationSettings',
'ServerchanNotificationSettings',
'PushdeerNotificationSettings',
'PushplusNotificationSettings',
'TelegramNotificationSettings',
'WebHookSettings',

View File

@ -51,12 +51,14 @@ __all__ = (
'SpaceSettings',
'EmailSettings',
'ServerchanSettings',
'PushdeerSettings',
'PushplusSettings',
'TelegramSettings',
'NotifierSettings',
'NotificationSettings',
'EmailNotificationSettings',
'ServerchanNotificationSettings',
'PushdeerNotificationSettings',
'PushplusNotificationSettings',
'TelegramNotificationSettings',
'WebHookSettings',
@ -345,6 +347,23 @@ class ServerchanSettings(BaseModel):
return value
class PushdeerSettings(BaseModel):
server: str = ''
pushkey: str = ''
@validator('server')
def _validate_server(cls, value: str) -> str:
if value != '' and not re.fullmatch(r'https?://.+', value):
raise ValueError('server is invalid')
return value
@validator('pushkey')
def _validate_pushkey(cls, value: str) -> str:
if value != '' and not re.fullmatch(r'[a-zA-Z\d]{41}', value):
raise ValueError('pushkey is invalid')
return value
class PushplusSettings(BaseModel):
token: str = ''
topic: str = ''
@ -398,6 +417,12 @@ class ServerchanNotificationSettings(
pass
class PushdeerNotificationSettings(
PushdeerSettings, NotifierSettings, NotificationSettings
):
pass
class PushplusNotificationSettings(
PushplusSettings, NotifierSettings, NotificationSettings
):
@ -450,6 +475,8 @@ class Settings(BaseModel):
email_notification: EmailNotificationSettings = EmailNotificationSettings()
serverchan_notification: ServerchanNotificationSettings = \
ServerchanNotificationSettings()
pushdeer_notification: PushdeerNotificationSettings = \
PushdeerNotificationSettings()
pushplus_notification: PushplusNotificationSettings = \
PushplusNotificationSettings()
telegram_notification: TelegramNotificationSettings = \
@ -500,6 +527,7 @@ class SettingsIn(BaseModel):
space: Optional[SpaceSettings] = None
email_notification: Optional[EmailNotificationSettings] = None
serverchan_notification: Optional[ServerchanNotificationSettings] = None
pushdeer_notification: Optional[PushdeerNotificationSettings] = None
pushplus_notification: Optional[PushplusNotificationSettings] = None
telegram_notification: Optional[TelegramNotificationSettings] = None
webhooks: Optional[List[WebHookSettings]] = None

View File

@ -22,7 +22,7 @@ from .models import (
from .typing import KeySetOfSettings
from ..webhook import WebHook
from ..notification import (
Notifier, EmailService, Serverchan, Pushplus, Telegram
Notifier, EmailService, Serverchan, Pushdeer, Pushplus, Telegram
)
from ..logging import configure_logger
from ..exception import NotFoundError
@ -335,6 +335,13 @@ class SettingsManager:
self._apply_notifier_settings(notifier, settings)
self._apply_notification_settings(notifier, settings)
def apply_pushdeer_notification_settings(self) -> None:
notifier = self._app._pushdeer_notifier
settings = self._settings.pushdeer_notification
self._apply_pushdeer_settings(notifier.provider)
self._apply_notifier_settings(notifier, settings)
self._apply_notification_settings(notifier, settings)
def apply_pushplus_notification_settings(self) -> None:
notifier = self._app._pushplus_notifier
settings = self._settings.pushplus_notification
@ -363,6 +370,10 @@ class SettingsManager:
def _apply_serverchan_settings(self, serverchan: Serverchan) -> None:
serverchan.sendkey = self._settings.serverchan_notification.sendkey
def _apply_pushdeer_settings(self, pushdeer: Pushdeer) -> None:
pushdeer.server = self._settings.pushdeer_notification.server
pushdeer.pushkey = self._settings.pushdeer_notification.pushkey
def _apply_pushplus_settings(self, pushplus: Pushplus) -> None:
pushplus.token = self._settings.pushplus_notification.token
pushplus.topic = self._settings.pushplus_notification.topic

View File

@ -13,6 +13,7 @@ KeyOfSettings = Literal[
'space',
'email_notification',
'serverchan_notification',
'pushdeer_notification',
'pushplus_notification',
'telegram_notification',
'webhooks',

View File

@ -51,6 +51,7 @@ AliasKeyOfSettings = Literal[
'space',
'emailNotification',
'serverchanNotification',
'pushdeerNotification',
'pushplusNotification',
'telegramNotification',
'webhooks',

View File

@ -6,6 +6,10 @@
><span class="setting-label">ServerChan 通知</span
><span class="setting-control"><i nz-icon nzType="right"></i></span
></a>
<a class="setting-item" routerLink="pushdeer-notification"
><span class="setting-label">PushDeer 通知</span
><span class="setting-control"><i nz-icon nzType="right"></i></span
></a>
<a class="setting-item" routerLink="pushplus-notification"
><span class="setting-label">pushplus 通知</span
><span class="setting-control"><i nz-icon nzType="right"></i></span

View File

@ -0,0 +1,24 @@
<app-sub-page pageTitle="PushDeer 通知">
<ng-template appSubPageContent>
<app-page-section>
<app-notifier-settings
[settings]="notifierSettings"
keyOfSettings="pushdeerNotification"
></app-notifier-settings>
</app-page-section>
<app-page-section name="PushDeer">
<app-pushdeer-settings
[settings]="pushdeerSettings"
></app-pushdeer-settings>
</app-page-section>
<app-page-section name="事件">
<app-event-settings
[settings]="notificationSettings"
keyOfSettings="pushdeerNotification"
></app-event-settings>
</app-page-section>
</ng-template>
</app-sub-page>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PushdeerNotificationSettingsComponent } from './pushdeer-notification-settings.component';
describe('PushdeerNotificationSettingsComponent', () => {
let component: PushdeerNotificationSettingsComponent;
let fixture: ComponentFixture<PushdeerNotificationSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PushdeerNotificationSettingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PushdeerNotificationSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,46 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import pick from 'lodash-es/pick';
import {
KEYS_OF_NOTIFICATION_SETTINGS,
KEYS_OF_NOTIFIER_SETTINGS,
KEYS_OF_PUSHDEER_SETTINGS,
NotificationSettings,
NotifierSettings,
PushdeerNotificationSettings,
PushdeerSettings,
} from '../../shared/setting.model';
@Component({
selector: 'app-pushdeer-notification-settings',
templateUrl: './pushdeer-notification-settings.component.html',
styleUrls: ['./pushdeer-notification-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PushdeerNotificationSettingsComponent implements OnInit {
pushdeerSettings!: PushdeerSettings;
notifierSettings!: NotifierSettings;
notificationSettings!: NotificationSettings;
constructor(
private changeDetector: ChangeDetectorRef,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.route.data.subscribe((data) => {
const settings = data.settings as PushdeerNotificationSettings;
this.pushdeerSettings = pick(settings, KEYS_OF_PUSHDEER_SETTINGS);
this.notifierSettings = pick(settings, KEYS_OF_NOTIFIER_SETTINGS);
this.notificationSettings = pick(settings, KEYS_OF_NOTIFICATION_SETTINGS);
this.changeDetector.markForCheck();
});
}
}

View File

@ -0,0 +1,59 @@
<form nz-form [formGroup]="settingsForm">
<nz-form-item class="setting-item">
<nz-form-label class="setting-label align-required" nzFor="server" nzNoColon
>server</nz-form-label
>
<nz-form-control
class="setting-control input"
nzHasFeedback
[nzErrorTip]="serverErrorTip"
[nzWarningTip]="syncFailedWarningTip"
[nzValidateStatus]="
serverControl.valid && !syncStatus.server ? 'warning' : serverControl
"
>
<input
id="server"
type="url"
placeholder="默认为官方服务器 https://api2.pushdeer.com"
nz-input
formControlName="server"
/>
<ng-template #serverErrorTip let-control>
<ng-container *ngIf="control.hasError('pattern')">
server 无效
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item class="setting-item">
<nz-form-label class="setting-label" nzFor="pushkey" nzNoColon nzRequired
>pushkey</nz-form-label
>
<nz-form-control
class="setting-control input"
nzHasFeedback
[nzErrorTip]="pushkeyErrorTip"
[nzWarningTip]="syncFailedWarningTip"
[nzValidateStatus]="
pushkeyControl.valid && !syncStatus.pushkey ? 'warning' : pushkeyControl
"
>
<input
id="pushkey"
type="text"
required
nz-input
formControlName="pushkey"
/>
<ng-template #pushkeyErrorTip let-control>
<ng-container *ngIf="control.hasError('required')">
请输入 pushkey
</ng-container>
<ng-container *ngIf="control.hasError('pattern')">
pushkey 无效
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
</form>

View File

@ -0,0 +1,6 @@
@use '../../../shared/styles/setting';
.setting-label {
max-width: 5em !important;
width: 5em !important;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PushdeerSettingsComponent } from './pushdeer-settings.component';
describe('PushdeerSettingsComponent', () => {
let component: PushdeerSettingsComponent;
let fixture: ComponentFixture<PushdeerSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PushdeerSettingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PushdeerSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,81 @@
import {
Component,
OnInit,
ChangeDetectionStrategy,
Input,
OnChanges,
ChangeDetectorRef,
} from '@angular/core';
import {
FormBuilder,
FormControl,
FormGroup,
Validators,
} from '@angular/forms';
import mapValues from 'lodash-es/mapValues';
import { PushdeerSettings } from '../../../shared/setting.model';
import { filterValueChanges } from '../../../shared/rx-operators';
import {
SettingsSyncService,
SyncStatus,
calcSyncStatus,
} from '../../../shared/services/settings-sync.service';
import { SYNC_FAILED_WARNING_TIP } from 'src/app/settings/shared/constants/form';
@Component({
selector: 'app-pushdeer-settings',
templateUrl: './pushdeer-settings.component.html',
styleUrls: ['./pushdeer-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PushdeerSettingsComponent implements OnInit, OnChanges {
@Input() settings!: PushdeerSettings;
syncStatus!: SyncStatus<PushdeerSettings>;
readonly settingsForm: FormGroup;
readonly syncFailedWarningTip = SYNC_FAILED_WARNING_TIP;
constructor(
formBuilder: FormBuilder,
private changeDetector: ChangeDetectorRef,
private settingsSyncService: SettingsSyncService
) {
this.settingsForm = formBuilder.group({
server: ['', [Validators.pattern(/^https?:\/\/.+/)]],
pushkey: [
'',
[Validators.required, Validators.pattern(/^[a-zA-Z\d]{41}$/)],
],
});
}
get serverControl() {
return this.settingsForm.get('server') as FormControl;
}
get pushkeyControl() {
return this.settingsForm.get('pushkey') as FormControl;
}
ngOnChanges(): void {
this.syncStatus = mapValues(this.settings, () => true);
this.settingsForm.setValue(this.settings);
}
ngOnInit(): void {
this.settingsSyncService
.syncSettings(
'pushdeerNotification',
this.settings,
this.settingsForm.valueChanges.pipe(
filterValueChanges<Partial<PushdeerSettings>>(this.settingsForm)
)
)
.subscribe((detail) => {
this.syncStatus = { ...this.syncStatus, ...calcSyncStatus(detail) };
this.changeDetector.markForCheck();
});
}
}

View File

@ -30,6 +30,7 @@ export class EventSettingsComponent implements OnInit, OnChanges {
@Input() keyOfSettings!:
| 'emailNotification'
| 'serverchanNotification'
| 'pushdeerNotification'
| 'pushplusNotification'
| 'telegramNotification';

View File

@ -30,6 +30,7 @@ export class NotifierSettingsComponent implements OnInit, OnChanges {
@Input() keyOfSettings!:
| 'emailNotification'
| 'serverchanNotification'
| 'pushdeerNotification'
| 'pushplusNotification'
| 'telegramNotification';

View File

@ -6,10 +6,12 @@ import { EmailNotificationSettingsResolver } from './shared/services/email-notif
import { PushplusNotificationSettingsResolver } from './shared/services/pushplus-notification-settings.resolver';
import { TelegramNotificationSettingsResolver } from './shared/services/telegram-notification-settings.resolver';
import { ServerchanNotificationSettingsResolver } from './shared/services/serverchan-notification-settings.resolver';
import { PushdeerNotificationSettingsResolver } from './shared/services/pushdeer-notification-settings.resolver';
import { WebhookSettingsResolver } from './shared/services/webhook-settings.resolver';
import { SettingsComponent } from './settings.component';
import { EmailNotificationSettingsComponent } from './notification-settings/email-notification-settings/email-notification-settings.component';
import { ServerchanNotificationSettingsComponent } from './notification-settings/serverchan-notification-settings/serverchan-notification-settings.component';
import { PushdeerNotificationSettingsComponent } from './notification-settings/pushdeer-notification-settings/pushdeer-notification-settings.component';
import { PushplusNotificationSettingsComponent } from './notification-settings/pushplus-notification-settings/pushplus-notification-settings.component';
import { TelegramNotificationSettingsComponent } from './notification-settings/telegram-notification-settings/telegram-notification-settings.component';
import { WebhookManagerComponent } from './webhook-settings/webhook-manager/webhook-manager.component';
@ -29,6 +31,13 @@ const routes: Routes = [
settings: ServerchanNotificationSettingsResolver,
},
},
{
path: 'pushdeer-notification',
component: PushdeerNotificationSettingsComponent,
resolve: {
settings: PushdeerNotificationSettingsResolver,
},
},
{
path: 'pushplus-notification',
component: PushplusNotificationSettingsComponent,

View File

@ -27,6 +27,7 @@ import { SharedModule } from '../shared/shared.module';
import { SettingsResolver } from './shared/services/settings.resolver';
import { EmailNotificationSettingsResolver } from './shared/services/email-notification-settings.resolver';
import { ServerchanNotificationSettingsResolver } from './shared/services/serverchan-notification-settings.resolver';
import { PushdeerNotificationSettingsResolver } from './shared/services/pushdeer-notification-settings.resolver';
import { PushplusNotificationSettingsResolver } from './shared/services/pushplus-notification-settings.resolver';
import { TelegramNotificationSettingsResolver } from './shared/services/telegram-notification-settings.resolver';
import { WebhookSettingsResolver } from './shared/services/webhook-settings.resolver';
@ -49,6 +50,8 @@ import { EmailNotificationSettingsComponent } from './notification-settings/emai
import { EmailSettingsComponent } from './notification-settings/email-notification-settings/email-settings/email-settings.component';
import { ServerchanNotificationSettingsComponent } from './notification-settings/serverchan-notification-settings/serverchan-notification-settings.component';
import { ServerchanSettingsComponent } from './notification-settings/serverchan-notification-settings/serverchan-settings/serverchan-settings.component';
import { PushdeerNotificationSettingsComponent } from './notification-settings/pushdeer-notification-settings/pushdeer-notification-settings.component';
import { PushdeerSettingsComponent } from './notification-settings/pushdeer-notification-settings/pushdeer-settings/pushdeer-settings.component';
import { PushplusNotificationSettingsComponent } from './notification-settings/pushplus-notification-settings/pushplus-notification-settings.component';
import { PushplusSettingsComponent } from './notification-settings/pushplus-notification-settings/pushplus-settings/pushplus-settings.component';
import { TelegramNotificationSettingsComponent } from './notification-settings/telegram-notification-settings/telegram-notification-settings.component';
@ -81,6 +84,8 @@ import { PathTemplateEditDialogComponent } from './output-settings/path-template
EmailSettingsComponent,
ServerchanNotificationSettingsComponent,
ServerchanSettingsComponent,
PushdeerNotificationSettingsComponent,
PushdeerSettingsComponent,
PushplusNotificationSettingsComponent,
PushplusSettingsComponent,
TelegramNotificationSettingsComponent,
@ -125,6 +130,7 @@ import { PathTemplateEditDialogComponent } from './output-settings/path-template
SettingsResolver,
EmailNotificationSettingsResolver,
ServerchanNotificationSettingsResolver,
PushdeerNotificationSettingsResolver,
PushplusNotificationSettingsResolver,
TelegramNotificationSettingsResolver,
WebhookSettingsResolver,

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PushdeerNotificationSettingsResolver } from './pushdeer-notification-settings.resolver';
describe('PushdeerNotificationSettingsResolver', () => {
let service: PushdeerNotificationSettingsResolver;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PushdeerNotificationSettingsResolver);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import {
ActivatedRouteSnapshot,
Resolve,
RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { NGXLogger } from 'ngx-logger';
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { retry } from '../../../shared/rx-operators';
import { PushdeerNotificationSettings } from '../setting.model';
import { SettingService } from './setting.service';
@Injectable()
export class PushdeerNotificationSettingsResolver
implements Resolve<PushdeerNotificationSettings>
{
constructor(
private logger: NGXLogger,
private notification: NzNotificationService,
private settingService: SettingService
) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<PushdeerNotificationSettings> {
return this.settingService.getSettings(['pushdeerNotification']).pipe(
map((settings) => settings.pushdeerNotification),
retry(3, 300),
catchError((error: HttpErrorResponse) => {
this.logger.error(
'Failed to get PushDeer notification settings:',
error
);
this.notification.error('获取 pushdeer 通知设置出错', error.message, {
nzDuration: 0,
});
throw error;
})
);
}
}

View File

@ -127,6 +127,13 @@ export interface ServerchanSettings {
export const KEYS_OF_SERVERCHAN_SETTINGS = ['sendkey'] as const;
export interface PushdeerSettings {
server: string;
pushkey: string;
}
export const KEYS_OF_PUSHDEER_SETTINGS = ['server', 'pushkey'] as const;
export interface PushplusSettings {
token: string;
topic: string;
@ -169,6 +176,10 @@ export type ServerchanNotificationSettings = ServerchanSettings &
NotifierSettings &
NotificationSettings;
export type PushdeerNotificationSettings = PushdeerSettings &
NotifierSettings &
NotificationSettings;
export type PushplusNotificationSettings = PushplusSettings &
NotifierSettings &
NotificationSettings;
@ -211,6 +222,7 @@ export interface Settings {
space: SpaceSettings;
emailNotification: EmailNotificationSettings;
serverchanNotification: ServerchanNotificationSettings;
pushdeerNotification: PushdeerNotificationSettings;
pushplusNotification: PushplusNotificationSettings;
telegramNotification: TelegramNotificationSettings;
webhooks: WebhookSettings[];