add telegram bot notification

This commit is contained in:
SiHuan 2022-04-30 22:02:26 +08:00
parent 2135e1b5b7
commit 5e32311453
No known key found for this signature in database
GPG Key ID: 6D484EBFDD2ED6BA
38 changed files with 480 additions and 21 deletions

View File

@ -32,6 +32,7 @@ from .notification import (
EmailNotifier,
ServerchanNotifier,
PushplusNotifier,
TelegramNotifier,
)
from .webhook import WebHookEmitter
@ -327,9 +328,11 @@ class Application:
self._email_notifier = EmailNotifier()
self._serverchan_notifier = ServerchanNotifier()
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_pushplus_notification_settings()
self._settings_manager.apply_telegram_notification_settings()
def _setup_webhooks(self) -> None:
self._webhook_emitter = WebHookEmitter()
@ -359,9 +362,11 @@ class Application:
self._email_notifier.disable()
self._serverchan_notifier.disable()
self._pushplus_notifier.disable()
self._telegram_notifier.disable()
del self._email_notifier
del self._serverchan_notifier
del self._pushplus_notifier
del self._telegram_notifier
def _destroy_webhooks(self) -> None:
self._webhook_emitter.disable()

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.459a2fcc68ef4fa7.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.8a8c73fae6ff9291.js" type="module"></script>
<script src="runtime.c354415fc493c1ff.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.ee42aef57b3529db.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

0
src/blrec/data/webapp/ngsw-worker.js Normal file → Executable file
View File

View File

@ -1,6 +1,6 @@
{
"configVersion": 1,
"timestamp": 1650082907617,
"timestamp": 1651319640894,
"index": "/index.html",
"assetGroups": [
{
@ -13,16 +13,16 @@
"urls": [
"/103.5b5d2a6e5a8a7479.js",
"/146.92e3b29c4c754544.js",
"/369.6447ab1db6f39c5d.js",
"/45.c90c3cea2bf1a66e.js",
"/66.8d16a032cbce41ed.js",
"/694.dbcbd5e1953ea55d.js",
"/66.925f958307346684.js",
"/869.ac675e78fa0ea7cf.js",
"/common.858f777e9296e6f2.js",
"/index.html",
"/main.8a8c73fae6ff9291.js",
"/main.ee42aef57b3529db.js",
"/manifest.webmanifest",
"/polyfills.4b08448aee19bb22.js",
"/runtime.459a2fcc68ef4fa7.js",
"/runtime.c354415fc493c1ff.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.8d16a032cbce41ed.js": "a473089370c2fe27f96a778acf1e709dc5770b31",
"/694.dbcbd5e1953ea55d.js": "41973de76799b085188903f46cc6974f20395741",
"/66.925f958307346684.js": "cda15ab0b7c746412407ced24061e5913450d955",
"/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": "be9d178866ccd58dfdc2a4c1b375ab030302163b",
"/main.8a8c73fae6ff9291.js": "41a5a5a8fb5cda4cfa0e28532812594816257122",
"/manifest.webmanifest": "62c1cb8c5ad2af551a956b97013ab55ce77dd586",
"/index.html": "cf9bd8f6b2acd7238eb2a0a7d807baaa36f2daed",
"/main.ee42aef57b3529db.js": "003c7b5dec11f8297a28878ba376f0ba429fb045",
"/manifest.webmanifest": "0c4534b4c868d756691b1b4372cecb2efce47c6d",
"/polyfills.4b08448aee19bb22.js": "8e73f2d42cc13ca353cea5c886d930bd6da08d0d",
"/runtime.459a2fcc68ef4fa7.js": "a68b948e588e75f8cb2fb5315ac41623e7c6ed1e",
"/runtime.c354415fc493c1ff.js": "f6682aed9b5806652e888520f71d062494af864b",
"/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,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:"8d16a032cbce41ed",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",592:"858f777e9296e6f2",694:"dbcbd5e1953ea55d",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))})()})();
(()=>{"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))})()})();

0
src/blrec/data/webapp/safety-worker.js Normal file → Executable file
View File

0
src/blrec/data/webapp/worker-basic.min.js vendored Normal file → Executable file
View File

View File

@ -4,12 +4,14 @@ from .notifiers import (
EmailNotifier,
ServerchanNotifier,
PushplusNotifier,
TelegramNotifier,
)
from .providers import (
MessagingProvider,
EmailService,
Serverchan,
Pushplus,
Telegram,
)
@ -18,10 +20,12 @@ __all__ = (
'EmailService',
'Serverchan',
'Pushplus',
'Telegram',
'Notifier',
'MessageNotifier',
'EmailNotifier',
'ServerchanNotifier',
'PushplusNotifier',
'TelegramNotifier',
)

View File

@ -14,6 +14,7 @@ from .providers import (
MessagingProvider,
Serverchan,
Pushplus,
Telegram,
)
from .message import (
make_live_info_content,
@ -36,7 +37,8 @@ __all__ = (
'MessageNotifier',
'EmailNotifier',
'ServerchanNotifier',
'PushplusNotifier'
'PushplusNotifier',
'TelegramNotifier',
)
@ -198,3 +200,14 @@ class PushplusNotifier(MessageNotifier):
def _do_disable(self) -> None:
super()._do_disable()
logger.debug('Disabled Pushplus notifier')
class TelegramNotifier(MessageNotifier):
provider = Telegram.get_instance()
def _do_enable(self) -> None:
super()._do_enable()
logger.debug('Enabled Telegram notifier')
def _do_disable(self) -> None:
super()._do_disable()
logger.debug('Disabled Telegram notifier')

View File

@ -12,7 +12,7 @@ import aiohttp
from ..utils.patterns import Singleton
__all__ = 'MessagingProvider', 'EmailService', 'Serverchan', 'Pushplus'
__all__ = 'MessagingProvider', 'EmailService', 'Serverchan', 'Pushplus', 'Telegram'
logger = logging.getLogger(__name__)
@ -143,3 +143,38 @@ class Pushplus(MessagingProvider):
response = cast(PushplusResponse, await res.json())
if response['code'] != 200:
raise HTTPException(response['code'], response['msg'])
class TelegramResponse(TypedDict):
ok: bool
result: dict
class Telegram(MessagingProvider):
def __init__(self, token: str = '', chatid: str = '') -> None:
super().__init__()
self.token = token
self.chatid = chatid
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.token:
raise ValueError('No token supplied')
if not self.chatid:
raise ValueError('No chatid supplied')
async def _post_message(self, title: str, content: str) -> None:
url = f'https://api.telegram.org/bot{self.token}/sendMessage'
payload = {
'chatid': self.chatid,
'text': title + '\n' + content,
'parse_mode': 'HTML',
}
async with aiohttp.ClientSession(raise_for_status=True) as session:
async with session.post(url, json=payload) as res:
response = cast(TelegramResponse, await res.json())
print(response)
if not response['ok']:
raise HTTPException(response['result']['error_code'], response['result']['description'])

View File

@ -23,11 +23,13 @@ from .models import (
EmailSettings,
ServerchanSettings,
PushplusSettings,
TelegramSettings,
NotifierSettings,
NotificationSettings,
EmailNotificationSettings,
ServerchanNotificationSettings,
PushplusNotificationSettings,
TelegramNotificationSettings,
WebHookSettings,
)
from .typing import KeyOfSettings, KeySetOfSettings
@ -63,11 +65,13 @@ __all__ = (
'EmailSettings',
'ServerchanSettings',
'PushplusSettings',
'TelegramSettings',
'NotifierSettings',
'NotificationSettings',
'EmailNotificationSettings',
'ServerchanNotificationSettings',
'PushplusNotificationSettings',
'TelegramNotificationSettings',
'WebHookSettings',
'update_settings',

View File

@ -14,6 +14,7 @@ from typing import (
import toml
from blrec.notification.providers import Telegram
from pydantic import BaseModel as PydanticBaseModel
from pydantic import Field, BaseSettings, validator, PrivateAttr
from pydantic.networks import HttpUrl, EmailStr
@ -52,11 +53,13 @@ __all__ = (
'EmailSettings',
'ServerchanSettings',
'PushplusSettings',
'TelegramSettings',
'NotifierSettings',
'NotificationSettings',
'EmailNotificationSettings',
'ServerchanNotificationSettings',
'PushplusNotificationSettings',
'TelegramNotificationSettings',
'WebHookSettings',
)
@ -353,6 +356,20 @@ class PushplusSettings(BaseModel):
raise ValueError('token is invalid')
return value
class TelegramSettings(BaseModel):
token: str = ''
chatid: str = ''
@validator('token')
def _validate_token(cls, value: str) -> str:
if value != '' and not re.fullmatch(r'[0-9]{8,10}:[a-zA-Z0-9_-]{35}', value):
raise ValueError('token is invalid')
return value
@validator('chatid')
def _validate_chatid(cls, value: str) -> str:
if value != '' and not re.fullmatch(r'(-|[0-9]){0,}', value):
raise ValueError('chatid is invalid')
return value
class NotifierSettings(BaseModel):
enabled: bool = False
@ -382,6 +399,10 @@ class PushplusNotificationSettings(
):
pass
class TelegramNotificationSettings(
TelegramSettings,NotifierSettings, NotificationSettings
):
pass
class WebHookEventSettings(BaseModel):
live_began: bool = True
@ -425,6 +446,8 @@ class Settings(BaseModel):
ServerchanNotificationSettings()
pushplus_notification: PushplusNotificationSettings = \
PushplusNotificationSettings()
telegram_notification: TelegramNotificationSettings = \
TelegramNotificationSettings()
webhooks: Annotated[List[WebHookSettings], Field(max_items=50)] = []
@classmethod
@ -472,6 +495,7 @@ class SettingsIn(BaseModel):
email_notification: Optional[EmailNotificationSettings] = None
serverchan_notification: Optional[ServerchanNotificationSettings] = None
pushplus_notification: Optional[PushplusNotificationSettings] = None
telegram_notification: Optional[TelegramNotificationSettings] = None
webhooks: Optional[List[WebHookSettings]] = None

View File

@ -21,7 +21,7 @@ from .models import (
)
from .typing import KeySetOfSettings
from ..webhook import WebHook
from ..notification import Notifier, EmailService, Serverchan, Pushplus
from ..notification import Notifier, EmailService, Serverchan, Pushplus, Telegram
from ..logging import configure_logger
from ..exception import NotFoundError
if TYPE_CHECKING:
@ -340,6 +340,13 @@ class SettingsManager:
self._apply_notifier_settings(notifier, settings)
self._apply_notification_settings(notifier, settings)
def apply_telegram_notification_settings(self) -> None:
notifier = self._app._telegram_notifier
settings = self._settings.telegram_notification
self._apply_telegram_settings(notifier.provider)
self._apply_notifier_settings(notifier, settings)
self._apply_notification_settings(notifier, settings)
def apply_webhooks_settings(self) -> None:
webhooks = [WebHook.from_settings(s) for s in self._settings.webhooks]
self._app._webhook_emitter.webhooks = webhooks
@ -358,6 +365,10 @@ class SettingsManager:
pushplus.token = self._settings.pushplus_notification.token
pushplus.topic = self._settings.pushplus_notification.topic
def _apply_telegram_settings(self, telegram: Telegram) -> None:
telegram.token = self._settings.telegram_notification.token
telegram.chatid = self._settings.telegram_notification.chatid
def _apply_notifier_settings(
self, notifier: Notifier, settings: NotifierSettings
) -> None:

View File

@ -14,6 +14,7 @@ KeyOfSettings = Literal[
'email_notification',
'serverchan_notification',
'pushplus_notification',
'telegram_notification',
'webhooks',
]

View File

@ -52,5 +52,6 @@ AliasKeyOfSettings = Literal[
'emailNotification',
'serverchanNotification',
'pushplusNotification',
'telegramNotification',
'webhooks',
]

View File

@ -8835,6 +8835,7 @@
"resolved": "https://registry.npmmirror.com/nice-napi/download/nice-napi-1.0.2.tgz",
"integrity": "sha1-3Aq1oerCDOVIgC/FaG6qa8ZUkns=",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"!win32"

View File

@ -10,3 +10,7 @@
><span class="setting-label">pushplus 通知</span
><span class="setting-control"><i nz-icon nzType="right"></i></span
></a>
<a class="setting-item" routerLink="telegram-notification"
><span class="setting-label">telegram 通知</span
><span class="setting-control"><i nz-icon nzType="right"></i></span
></a>

View File

@ -30,7 +30,8 @@ export class EventSettingsComponent implements OnInit, OnChanges {
@Input() keyOfSettings!:
| 'emailNotification'
| 'serverchanNotification'
| 'pushplusNotification';
| 'pushplusNotification'
| 'telegramNotification';
syncStatus!: SyncStatus<NotificationSettings>;

View File

@ -30,7 +30,8 @@ export class NotifierSettingsComponent implements OnInit, OnChanges {
@Input() keyOfSettings!:
| 'emailNotification'
| 'serverchanNotification'
| 'pushplusNotification';
| 'pushplusNotification'
| 'telegramNotification';
syncStatus!: SyncStatus<NotifierSettings>;

View File

@ -0,0 +1,23 @@
<app-sub-page pageTitle="telegram 通知">
<ng-template appSubPageContent>
<app-page-section>
<app-notifier-settings
[settings]="notifierSettings"
keyOfSettings="telegramNotification"
></app-notifier-settings>
</app-page-section>
<app-page-section name="telegram">
<app-telegram-settings
[settings]="telegramSettings"
></app-telegram-settings>
</app-page-section>
<app-page-section name="事件">
<app-event-settings
[settings]="notificationSettings"
keyOfSettings="telegramNotification"
></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 { TelegramNotificationSettingsComponent } from './telegram-notification-settings.component';
describe('TelegramNotificationSettingsComponent', () => {
let component: TelegramNotificationSettingsComponent;
let fixture: ComponentFixture<TelegramNotificationSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TelegramNotificationSettingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TelegramNotificationSettingsComponent);
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 {
NotificationSettings,
TelegramSettings,
NotifierSettings,
TelegramNotificationSettings,
KEYS_OF_TELEGRAM_SETTINGS,
KEYS_OF_NOTIFIER_SETTINGS,
KEYS_OF_NOTIFICATION_SETTINGS,
} from '../../shared/setting.model';
@Component({
selector: 'app-telegram-notification-settings',
templateUrl: './telegram-notification-settings.component.html',
styleUrls: ['./telegram-notification-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TelegramNotificationSettingsComponent implements OnInit {
telegramSettings!: TelegramSettings;
notifierSettings!: NotifierSettings;
notificationSettings!: NotificationSettings;
constructor(
private changeDetector: ChangeDetectorRef,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.route.data.subscribe((data) => {
const settings = data.settings as TelegramNotificationSettings;
this.changeDetector.markForCheck();
this.telegramSettings = pick(settings, KEYS_OF_TELEGRAM_SETTINGS);
this.notifierSettings = pick(settings, KEYS_OF_NOTIFIER_SETTINGS);
this.notificationSettings = pick(settings, KEYS_OF_NOTIFICATION_SETTINGS);
});
}
}

View File

@ -0,0 +1,60 @@
<form nz-form [formGroup]="settingsForm">
<nz-form-item class="setting-item">
<nz-form-label
class="setting-label required"
nzFor="token"
nzNoColon
nzRequired
>token</nz-form-label
>
<nz-form-control
class="setting-control input"
nzHasFeedback
[nzErrorTip]="tokenErrorTip"
[nzWarningTip]="syncFailedWarningTip"
[nzValidateStatus]="
tokenControl.valid && !syncStatus.token ? 'warning' : tokenControl
"
>
<input id="token" type="text" required nz-input formControlName="token" />
<ng-template #tokenErrorTip let-control>
<ng-container *ngIf="control.hasError('required')">
请输入 token
</ng-container>
<ng-container *ngIf="control.hasError('pattern')">
token 无效
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item class="setting-item">
<nz-form-label class="setting-label" nzFor="chatid" nzNoColon nzRequired
>chatid</nz-form-label
>
<nz-form-control
class="setting-control input"
nzHasFeedback
[nzErrorTip]="chatidErrorTip"
[nzWarningTip]="syncFailedWarningTip"
[nzValidateStatus]="
chatidControl.valid && !syncStatus.chatid ? 'warning' : chatidControl
"
>
<input
id="chatid"
type="text"
required
nz-input
formControlName="chatid"
/>
<ng-template #chatidErrorTip let-control>
<ng-container *ngIf="control.hasError('required')">
请输入 chatid
</ng-container>
<ng-container *ngIf="control.hasError('pattern')">
chatid 无效
</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: 4em !important;
width: 4em !important;
}

View File

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

View File

@ -0,0 +1,78 @@
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 { TelegramSettings } 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-telegram-settings',
templateUrl: './telegram-settings.component.html',
styleUrls: ['./telegram-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TelegramSettingsComponent implements OnInit, OnChanges {
@Input() settings!: TelegramSettings;
syncStatus!: SyncStatus<TelegramSettings>;
readonly settingsForm: FormGroup;
readonly syncFailedWarningTip = SYNC_FAILED_WARNING_TIP;
constructor(
formBuilder: FormBuilder,
private changeDetector: ChangeDetectorRef,
private settingsSyncService: SettingsSyncService
) {
this.settingsForm = formBuilder.group({
token: ['', [Validators.required, Validators.pattern(/^[0-9]{8,10}:[a-zA-Z0-9_-]{35}$/)]],
chatid: ['', [Validators.required, Validators.pattern(/^(-|[0-9]){0,}$/)]],
});
}
get tokenControl() {
return this.settingsForm.get('token') as FormControl;
}
get chatidControl() {
return this.settingsForm.get('chatid') as FormControl;
}
ngOnChanges(): void {
this.syncStatus = mapValues(this.settings, () => true);
this.settingsForm.setValue(this.settings);
}
ngOnInit(): void {
this.settingsSyncService
.syncSettings(
'telegramNotification',
this.settings,
this.settingsForm.valueChanges.pipe(
filterValueChanges<TelegramSettings>(this.settingsForm)
)
)
.subscribe((detail) => {
this.syncStatus = { ...this.syncStatus, ...calcSyncStatus(detail) };
this.changeDetector.markForCheck();
});
}
}

View File

@ -4,12 +4,14 @@ import { RouterModule, Routes } from '@angular/router';
import { SettingsResolver } from './shared/services/settings.resolver';
import { EmailNotificationSettingsResolver } from './shared/services/email-notification-settings.resolver';
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 { 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 { 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';
const routes: Routes = [
@ -34,6 +36,13 @@ const routes: Routes = [
settings: PushplusNotificationSettingsResolver,
},
},
{
path: 'telegram-notification',
component: TelegramNotificationSettingsComponent,
resolve: {
settings: TelegramNotificationSettingsResolver,
},
},
{
path: 'webhooks',
component: WebhookManagerComponent,

View File

@ -28,6 +28,7 @@ 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 { 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';
import { SettingsRoutingModule } from './settings-routing.module';
import { SettingsComponent } from './settings.component';
@ -50,6 +51,8 @@ import { ServerchanNotificationSettingsComponent } from './notification-settings
import { ServerchanSettingsComponent } from './notification-settings/serverchan-notification-settings/serverchan-settings/serverchan-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';
import { TelegramSettingsComponent } from './notification-settings/telegram-notification-settings/telegram-settings/telegram-settings.component';
import { NotifierSettingsComponent } from './notification-settings/shared/components/notifier-settings/notifier-settings.component';
import { WebhookManagerComponent } from './webhook-settings/webhook-manager/webhook-manager.component';
import { WebhookEditDialogComponent } from './webhook-settings/webhook-edit-dialog/webhook-edit-dialog.component';
@ -80,6 +83,8 @@ import { PathTemplateEditDialogComponent } from './output-settings/path-template
ServerchanSettingsComponent,
PushplusNotificationSettingsComponent,
PushplusSettingsComponent,
TelegramNotificationSettingsComponent,
TelegramSettingsComponent,
NotifierSettingsComponent,
WebhookManagerComponent,
WebhookEditDialogComponent,
@ -121,6 +126,7 @@ import { PathTemplateEditDialogComponent } from './output-settings/path-template
EmailNotificationSettingsResolver,
ServerchanNotificationSettingsResolver,
PushplusNotificationSettingsResolver,
TelegramNotificationSettingsResolver,
WebhookSettingsResolver,
],
})

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TelegramNotificationSettingsResolver } from './telegram-notification-settings.resolver';
describe('TelegramNotificationSettingsResolverService', () => {
let service: TelegramNotificationSettingsResolver;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TelegramNotificationSettingsResolver);
});
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 { TelegramNotificationSettings } from '../setting.model';
import { SettingService } from './setting.service';
@Injectable()
export class TelegramNotificationSettingsResolver
implements Resolve<TelegramNotificationSettings>
{
constructor(
private logger: NGXLogger,
private notification: NzNotificationService,
private settingService: SettingService
) {}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<TelegramNotificationSettings> {
return this.settingService.getSettings(['telegramNotification']).pipe(
map((settings) => settings.telegramNotification),
retry(3, 300),
catchError((error: HttpErrorResponse) => {
this.logger.error(
'Failed to get telegram notification settings:',
error
);
this.notification.error('获取 telegram 通知设置出错', error.message, {
nzDuration: 0,
});
throw error;
})
);
}
}

View File

@ -134,6 +134,13 @@ export interface PushplusSettings {
export const KEYS_OF_PUSHPLUS_SETTINGS = ['token', 'topic'] as const;
export interface TelegramSettings {
token: string;
chatid: string;
}
export const KEYS_OF_TELEGRAM_SETTINGS = ['token', 'chatid'] as const;
export interface NotifierSettings {
enabled: boolean;
}
@ -166,6 +173,10 @@ export type PushplusNotificationSettings = PushplusSettings &
NotifierSettings &
NotificationSettings;
export type TelegramNotificationSettings = TelegramSettings &
NotifierSettings &
NotificationSettings;
export interface WebhookEventSettings {
liveBegan: boolean;
liveEnded: boolean;
@ -201,6 +212,7 @@ export interface Settings {
emailNotification: EmailNotificationSettings;
serverchanNotification: ServerchanNotificationSettings;
pushplusNotification: PushplusNotificationSettings;
telegramNotification: TelegramNotificationSettings;
webhooks: WebhookSettings[];
}