mirror of https://github.com/morpheus65535/bazarr
Updated apprise to version 1.7.6
This commit is contained in:
parent
b2d807d9d9
commit
7578b8ef14
|
@ -1,12 +1,12 @@
|
||||||
Metadata-Version: 2.1
|
Metadata-Version: 2.1
|
||||||
Name: apprise
|
Name: apprise
|
||||||
Version: 1.7.4
|
Version: 1.7.6
|
||||||
Summary: Push Notifications that work with just about every platform!
|
Summary: Push Notifications that work with just about every platform!
|
||||||
Home-page: https://github.com/caronc/apprise
|
Home-page: https://github.com/caronc/apprise
|
||||||
Author: Chris Caron
|
Author: Chris Caron
|
||||||
Author-email: lead2gold@gmail.com
|
Author-email: lead2gold@gmail.com
|
||||||
License: BSD
|
License: BSD
|
||||||
Keywords: Alerts Apprise API Automated Packet Reporting System AWS Boxcar BulkSMS BulkVS Burst SMS Chat CLI ClickSend D7Networks Dapnet DBus DingTalk Discord Email Emby Enigma2 Faast FCM Flock Form Gnome Google Chat Gotify Growl Guilded Home Assistant httpSMS IFTTT Join JSON Kavenegar KODI Kumulos LaMetric Line LunaSea MacOSX Mailgun Mastodon Matrix Mattermost MessageBird Microsoft Misskey MQTT MSG91 MSTeams Nextcloud NextcloudTalk Notica Notifiarr Notifico Ntfy Office365 OneSignal Opsgenie PagerDuty PagerTree ParsePlatform PopcornNotify Prowl PushBullet Pushed Pushjet PushMe Push Notifications Pushover PushSafer Pushy PushDeer Reddit Revolt Rocket.Chat RSyslog Ryver SendGrid ServerChan SES Signal SimplePush Sinch Slack SMSEagle SMS Manager SMTP2Go SNS SparkPost Streamlabs Stride Synology Chat Syslog Techulus Telegram Threema Gateway Twilio Twist Twitter Voipms Vonage Webex WeCom Bot WhatsApp Windows XBMC XML Zulip
|
Keywords: Alerts Apprise API Automated Packet Reporting System AWS Boxcar BulkSMS BulkVS Burst SMS Chantify Chat CLI ClickSend D7Networks Dapnet DBus DingTalk Discord Email Emby Enigma2 FCM Feishu Flock Form Free Mobile Gnome Google Chat Gotify Growl Guilded Home Assistant httpSMS IFTTT Join JSON Kavenegar KODI Kumulos LaMetric Line LunaSea MacOSX Mailgun Mastodon Matrix Mattermost MessageBird Microsoft Misskey MQTT MSG91 MSTeams Nextcloud NextcloudTalk Notica Notifiarr Notifico Ntfy Office365 OneSignal Opsgenie PagerDuty PagerTree ParsePlatform PopcornNotify Prowl PushBullet Pushed Pushjet PushMe Push Notifications Pushover PushSafer Pushy PushDeer Reddit Revolt Rocket.Chat RSyslog Ryver SendGrid ServerChan SES Signal SimplePush Sinch Slack SMSEagle SMS Manager SMTP2Go SNS SparkPost Streamlabs Stride Synology Chat Syslog Techulus Telegram Threema Gateway Twilio Twist Twitter Voipms Vonage Webex WeCom Bot WhatsApp Windows XBMC XML Zulip
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: Intended Audience :: Developers
|
Classifier: Intended Audience :: Developers
|
||||||
Classifier: Intended Audience :: System Administrators
|
Classifier: Intended Audience :: System Administrators
|
||||||
|
@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.8
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
Classifier: Programming Language :: Python :: 3.11
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
|
@ -98,11 +99,12 @@ The table below identifies the services this tool supports and some example serv
|
||||||
| [AWS SES](https://github.com/caronc/apprise/wiki/Notify_ses) | ses:// | (TCP) 443 | ses://user@domain/AccessKeyID/AccessSecretKey/RegionName<br/>ses://user@domain/AccessKeyID/AccessSecretKey/RegionName/email1/email2/emailN
|
| [AWS SES](https://github.com/caronc/apprise/wiki/Notify_ses) | ses:// | (TCP) 443 | ses://user@domain/AccessKeyID/AccessSecretKey/RegionName<br/>ses://user@domain/AccessKeyID/AccessSecretKey/RegionName/email1/email2/emailN
|
||||||
| [Bark](https://github.com/caronc/apprise/wiki/Notify_bark) | bark:// | (TCP) 80 or 443 | bark://hostname<br />bark://hostname/device_key<br />bark://hostname/device_key1/device_key2/device_keyN<br/>barks://hostname<br />barks://hostname/device_key<br />barks://hostname/device_key1/device_key2/device_keyN
|
| [Bark](https://github.com/caronc/apprise/wiki/Notify_bark) | bark:// | (TCP) 80 or 443 | bark://hostname<br />bark://hostname/device_key<br />bark://hostname/device_key1/device_key2/device_keyN<br/>barks://hostname<br />barks://hostname/device_key<br />barks://hostname/device_key1/device_key2/device_keyN
|
||||||
| [Boxcar](https://github.com/caronc/apprise/wiki/Notify_boxcar) | boxcar:// | (TCP) 443 | boxcar://hostname<br />boxcar://hostname/@tag<br/>boxcar://hostname/device_token<br />boxcar://hostname/device_token1/device_token2/device_tokenN<br />boxcar://hostname/@tag/@tag2/device_token
|
| [Boxcar](https://github.com/caronc/apprise/wiki/Notify_boxcar) | boxcar:// | (TCP) 443 | boxcar://hostname<br />boxcar://hostname/@tag<br/>boxcar://hostname/device_token<br />boxcar://hostname/device_token1/device_token2/device_tokenN<br />boxcar://hostname/@tag/@tag2/device_token
|
||||||
|
| [Chantify](https://github.com/caronc/apprise/wiki/Notify_chantify) | chantify:// | (TCP) 443 | chantify://token
|
||||||
| [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | discord:// | (TCP) 443 | discord://webhook_id/webhook_token<br />discord://avatar@webhook_id/webhook_token
|
| [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | discord:// | (TCP) 443 | discord://webhook_id/webhook_token<br />discord://avatar@webhook_id/webhook_token
|
||||||
| [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname
|
| [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname
|
||||||
| [Enigma2](https://github.com/caronc/apprise/wiki/Notify_enigma2) | enigma2:// or enigma2s:// | (TCP) 80 or 443 | enigma2://hostname
|
| [Enigma2](https://github.com/caronc/apprise/wiki/Notify_enigma2) | enigma2:// or enigma2s:// | (TCP) 80 or 443 | enigma2://hostname
|
||||||
| [Faast](https://github.com/caronc/apprise/wiki/Notify_faast) | faast:// | (TCP) 443 | faast://authorizationtoken
|
|
||||||
| [FCM](https://github.com/caronc/apprise/wiki/Notify_fcm) | fcm:// | (TCP) 443 | fcm://project@apikey/DEVICE_ID<br />fcm://project@apikey/#TOPIC<br/>fcm://project@apikey/DEVICE_ID1/#topic1/#topic2/DEVICE_ID2/
|
| [FCM](https://github.com/caronc/apprise/wiki/Notify_fcm) | fcm:// | (TCP) 443 | fcm://project@apikey/DEVICE_ID<br />fcm://project@apikey/#TOPIC<br/>fcm://project@apikey/DEVICE_ID1/#topic1/#topic2/DEVICE_ID2/
|
||||||
|
| [Feishu](https://github.com/caronc/apprise/wiki/Notify_feishu) | feishu:// | (TCP) 443 | feishu://token
|
||||||
| [Flock](https://github.com/caronc/apprise/wiki/Notify_flock) | flock:// | (TCP) 443 | flock://token<br/>flock://botname@token<br/>flock://app_token/u:userid<br/>flock://app_token/g:channel_id<br/>flock://app_token/u:userid/g:channel_id
|
| [Flock](https://github.com/caronc/apprise/wiki/Notify_flock) | flock:// | (TCP) 443 | flock://token<br/>flock://botname@token<br/>flock://app_token/u:userid<br/>flock://app_token/g:channel_id<br/>flock://app_token/u:userid/g:channel_id
|
||||||
| [Google Chat](https://github.com/caronc/apprise/wiki/Notify_googlechat) | gchat:// | (TCP) 443 | gchat://workspace/key/token
|
| [Google Chat](https://github.com/caronc/apprise/wiki/Notify_googlechat) | gchat:// | (TCP) 443 | gchat://workspace/key/token
|
||||||
| [Gotify](https://github.com/caronc/apprise/wiki/Notify_gotify) | gotify:// or gotifys:// | (TCP) 80 or 443 | gotify://hostname/token<br />gotifys://hostname/token?priority=high
|
| [Gotify](https://github.com/caronc/apprise/wiki/Notify_gotify) | gotify:// or gotifys:// | (TCP) 80 or 443 | gotify://hostname/token<br />gotifys://hostname/token?priority=high
|
||||||
|
@ -184,6 +186,7 @@ The table below identifies the services this tool supports and some example serv
|
||||||
| [DAPNET](https://github.com/caronc/apprise/wiki/Notify_dapnet) | dapnet:// | (TCP) 80 | dapnet://user:pass@callsign<br/>dapnet://user:pass@callsign1/callsign2/callsignN
|
| [DAPNET](https://github.com/caronc/apprise/wiki/Notify_dapnet) | dapnet:// | (TCP) 80 | dapnet://user:pass@callsign<br/>dapnet://user:pass@callsign1/callsign2/callsignN
|
||||||
| [D7 Networks](https://github.com/caronc/apprise/wiki/Notify_d7networks) | d7sms:// | (TCP) 443 | d7sms://token@PhoneNo<br/>d7sms://token@ToPhoneNo1/ToPhoneNo2/ToPhoneNoN
|
| [D7 Networks](https://github.com/caronc/apprise/wiki/Notify_d7networks) | d7sms:// | (TCP) 443 | d7sms://token@PhoneNo<br/>d7sms://token@ToPhoneNo1/ToPhoneNo2/ToPhoneNoN
|
||||||
| [DingTalk](https://github.com/caronc/apprise/wiki/Notify_dingtalk) | dingtalk:// | (TCP) 443 | dingtalk://token/<br />dingtalk://token/ToPhoneNo<br />dingtalk://token/ToPhoneNo1/ToPhoneNo2/ToPhoneNo1/
|
| [DingTalk](https://github.com/caronc/apprise/wiki/Notify_dingtalk) | dingtalk:// | (TCP) 443 | dingtalk://token/<br />dingtalk://token/ToPhoneNo<br />dingtalk://token/ToPhoneNo1/ToPhoneNo2/ToPhoneNo1/
|
||||||
|
| [Free-Mobile](https://github.com/caronc/apprise/wiki/Notify_freemobile) | freemobile:// | (TCP) 443 | freemobile://user@password/
|
||||||
[httpSMS](https://github.com/caronc/apprise/wiki/Notify_httpsms) | httpsms:// | (TCP) 443 | httpsms://ApiKey@FromPhoneNo<br/>httpsms://ApiKey@FromPhoneNo/ToPhoneNo<br/>httpsms://ApiKey@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
[httpSMS](https://github.com/caronc/apprise/wiki/Notify_httpsms) | httpsms:// | (TCP) 443 | httpsms://ApiKey@FromPhoneNo<br/>httpsms://ApiKey@FromPhoneNo/ToPhoneNo<br/>httpsms://ApiKey@FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
||||||
| [Kavenegar](https://github.com/caronc/apprise/wiki/Notify_kavenegar) | kavenegar:// | (TCP) 443 | kavenegar://ApiKey/ToPhoneNo<br/>kavenegar://FromPhoneNo@ApiKey/ToPhoneNo<br/>kavenegar://ApiKey/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN
|
| [Kavenegar](https://github.com/caronc/apprise/wiki/Notify_kavenegar) | kavenegar:// | (TCP) 443 | kavenegar://ApiKey/ToPhoneNo<br/>kavenegar://FromPhoneNo@ApiKey/ToPhoneNo<br/>kavenegar://ApiKey/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN
|
||||||
| [MessageBird](https://github.com/caronc/apprise/wiki/Notify_messagebird) | msgbird:// | (TCP) 443 | msgbird://ApiKey/FromPhoneNo<br/>msgbird://ApiKey/FromPhoneNo/ToPhoneNo<br/>msgbird://ApiKey/FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
| [MessageBird](https://github.com/caronc/apprise/wiki/Notify_messagebird) | msgbird:// | (TCP) 443 | msgbird://ApiKey/FromPhoneNo<br/>msgbird://ApiKey/FromPhoneNo/ToPhoneNo<br/>msgbird://ApiKey/FromPhoneNo/ToPhoneNo1/ToPhoneNo2/ToPhoneNoN/
|
|
@ -1,12 +1,12 @@
|
||||||
../../bin/apprise,sha256=ZJ-e4qqxNLtdW_DAvpuPPX5iROIiQd8I6nvg7vtAv-g,233
|
../../bin/apprise,sha256=ZJ-e4qqxNLtdW_DAvpuPPX5iROIiQd8I6nvg7vtAv-g,233
|
||||||
apprise-1.7.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
apprise-1.7.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
apprise-1.7.4.dist-info/LICENSE,sha256=gt7qKBxRhVcdmXCYVtrWP6DtYjD0DzONet600dkU994,1343
|
apprise-1.7.6.dist-info/LICENSE,sha256=gt7qKBxRhVcdmXCYVtrWP6DtYjD0DzONet600dkU994,1343
|
||||||
apprise-1.7.4.dist-info/METADATA,sha256=Lc66iPsSCFv0zmoQX8NFuc_V5CqFYN5Yrx_gqeN8OF8,44502
|
apprise-1.7.6.dist-info/METADATA,sha256=z_gaX2IdNJqw4T9q7AYQri9jcIs-OTGCo3t2EgEY-mw,44823
|
||||||
apprise-1.7.4.dist-info/RECORD,,
|
apprise-1.7.6.dist-info/RECORD,,
|
||||||
apprise-1.7.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
apprise-1.7.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
apprise-1.7.4.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
apprise-1.7.6.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
||||||
apprise-1.7.4.dist-info/entry_points.txt,sha256=71YypBuNdjAKiaLsiMG40HEfLHxkU4Mi7o_S0s0d8wI,45
|
apprise-1.7.6.dist-info/entry_points.txt,sha256=71YypBuNdjAKiaLsiMG40HEfLHxkU4Mi7o_S0s0d8wI,45
|
||||||
apprise-1.7.4.dist-info/top_level.txt,sha256=JrCRn-_rXw5LMKXkIgMSE4E0t1Ks9TYrBH54Pflwjkk,8
|
apprise-1.7.6.dist-info/top_level.txt,sha256=JrCRn-_rXw5LMKXkIgMSE4E0t1Ks9TYrBH54Pflwjkk,8
|
||||||
apprise/Apprise.py,sha256=Stm2NhJprWRaMwQfTiIQG_nR1bLpHi_zcdwEcsCpa-A,32865
|
apprise/Apprise.py,sha256=Stm2NhJprWRaMwQfTiIQG_nR1bLpHi_zcdwEcsCpa-A,32865
|
||||||
apprise/Apprise.pyi,sha256=_4TBKvT-QVj3s6PuTh3YX-BbQMeJTdBGdVpubLMY4_k,2203
|
apprise/Apprise.pyi,sha256=_4TBKvT-QVj3s6PuTh3YX-BbQMeJTdBGdVpubLMY4_k,2203
|
||||||
apprise/AppriseAsset.py,sha256=jRW8Y1EcAvjVA9h_mINmsjO4DM3S0aDl6INIFVMcUCs,11647
|
apprise/AppriseAsset.py,sha256=jRW8Y1EcAvjVA9h_mINmsjO4DM3S0aDl6INIFVMcUCs,11647
|
||||||
|
@ -15,13 +15,13 @@ apprise/AppriseAttachment.py,sha256=vhrktSrp8GLr32aK4KqV6BX83IpI1lxZe-pGo1wiSFM,
|
||||||
apprise/AppriseAttachment.pyi,sha256=R9-0dVqWpeaFrVpcREwPhGy3qHWztG5jEjYIOsbE5dM,1145
|
apprise/AppriseAttachment.pyi,sha256=R9-0dVqWpeaFrVpcREwPhGy3qHWztG5jEjYIOsbE5dM,1145
|
||||||
apprise/AppriseConfig.py,sha256=wfuR6Mb3ZLHvjvqWdFp9lVmjjDRWs65unY9qa92RkCg,16909
|
apprise/AppriseConfig.py,sha256=wfuR6Mb3ZLHvjvqWdFp9lVmjjDRWs65unY9qa92RkCg,16909
|
||||||
apprise/AppriseConfig.pyi,sha256=_mUlCnncqAq8sL01WxQTgZjnb2ic9kZXvtqZmVl-fc8,1568
|
apprise/AppriseConfig.pyi,sha256=_mUlCnncqAq8sL01WxQTgZjnb2ic9kZXvtqZmVl-fc8,1568
|
||||||
apprise/AppriseLocale.py,sha256=ISth7xC7M1WhsSNXdGZFouaA4bi07KP35m9RX-ExG48,8852
|
apprise/AppriseLocale.py,sha256=4uSr4Nj_rz6ISMMAfRVRk58wZVLKOofJgk2x0_E8NkQ,8994
|
||||||
apprise/AttachmentManager.py,sha256=EwlnjuKn3fv_pioWcmMCkyDTsO178t6vkEOD8AjAPsw,2053
|
apprise/AttachmentManager.py,sha256=EwlnjuKn3fv_pioWcmMCkyDTsO178t6vkEOD8AjAPsw,2053
|
||||||
apprise/ConfigurationManager.py,sha256=MUmGajxjgnr6FGN7xb3q0nD0VVgdTdvapBBR7CsI-rc,2058
|
apprise/ConfigurationManager.py,sha256=MUmGajxjgnr6FGN7xb3q0nD0VVgdTdvapBBR7CsI-rc,2058
|
||||||
apprise/NotificationManager.py,sha256=ZJgkiCgcJ7Bz_6bwQ47flrcxvLMbA4Vbw0HG_yTsGdE,2041
|
apprise/NotificationManager.py,sha256=ZJgkiCgcJ7Bz_6bwQ47flrcxvLMbA4Vbw0HG_yTsGdE,2041
|
||||||
apprise/URLBase.py,sha256=ZWjHz69790EfVNDIBzWzRZzjw-gwC3db_t3_3an6cWI,28388
|
apprise/URLBase.py,sha256=xRP0-blocp9UudYh04Hb3fIEmTZWJaTv_tzjrqaB9fg,29423
|
||||||
apprise/URLBase.pyi,sha256=WLaRREH7FzZ5x3-qkDkupojWGFC4uFwJ1EDt02lVs8c,520
|
apprise/URLBase.pyi,sha256=WLaRREH7FzZ5x3-qkDkupojWGFC4uFwJ1EDt02lVs8c,520
|
||||||
apprise/__init__.py,sha256=oBHq9Zbcwz9DTkurqnEhbu9Q79a0TdVAZrWFIhlk__8,3368
|
apprise/__init__.py,sha256=ArtvoarAMnBcSfXF7L_hzq5CUJ9TUnHopiC7xafCe3c,3368
|
||||||
apprise/assets/NotifyXML-1.0.xsd,sha256=292qQ_IUl5EWDhPyzm9UTT0C2rVvJkyGar8jiODkJs8,986
|
apprise/assets/NotifyXML-1.0.xsd,sha256=292qQ_IUl5EWDhPyzm9UTT0C2rVvJkyGar8jiODkJs8,986
|
||||||
apprise/assets/NotifyXML-1.1.xsd,sha256=bjR3CGG4AEXoJjYkGCbDttKHSkPP1FlIWO02E7G59g4,1758
|
apprise/assets/NotifyXML-1.1.xsd,sha256=bjR3CGG4AEXoJjYkGCbDttKHSkPP1FlIWO02E7G59g4,1758
|
||||||
apprise/assets/themes/default/apprise-failure-128x128.ico,sha256=Mt0ptfHJaN3Wsv5UCNDn9_3lyEDHxVDv1JdaDEI_xCA,67646
|
apprise/assets/themes/default/apprise-failure-128x128.ico,sha256=Mt0ptfHJaN3Wsv5UCNDn9_3lyEDHxVDv1JdaDEI_xCA,67646
|
||||||
|
@ -45,22 +45,22 @@ apprise/assets/themes/default/apprise-warning-128x128.png,sha256=pf5c4Ph7jWH7gf3
|
||||||
apprise/assets/themes/default/apprise-warning-256x256.png,sha256=SY-xlaiXaj420iEYKC2_fJxU-yj2SuaQg6xfPNi83bw,43708
|
apprise/assets/themes/default/apprise-warning-256x256.png,sha256=SY-xlaiXaj420iEYKC2_fJxU-yj2SuaQg6xfPNi83bw,43708
|
||||||
apprise/assets/themes/default/apprise-warning-32x32.png,sha256=97R2ywNvcwczhBoWEIgajVtWjgT8fLs4FCCz4wu0dwc,2472
|
apprise/assets/themes/default/apprise-warning-32x32.png,sha256=97R2ywNvcwczhBoWEIgajVtWjgT8fLs4FCCz4wu0dwc,2472
|
||||||
apprise/assets/themes/default/apprise-warning-72x72.png,sha256=L8moEInkO_OLxoOcuvN7rmrGZo64iJeH20o-24MQghE,7913
|
apprise/assets/themes/default/apprise-warning-72x72.png,sha256=L8moEInkO_OLxoOcuvN7rmrGZo64iJeH20o-24MQghE,7913
|
||||||
apprise/attachment/AttachBase.py,sha256=ik3hRFnr8Z9bXt69P9Ej1VST4gQbnE0C_9WQvEE-72A,13592
|
apprise/attachment/AttachBase.py,sha256=T3WreGrTsqqGplXJO36jm-N14X7ymSc9xt7XdTYuXVE,13656
|
||||||
apprise/attachment/AttachBase.pyi,sha256=w0XG_QKauiMLJ7eQ4S57IiLIURZHm_Snw7l6-ih9GP8,961
|
apprise/attachment/AttachBase.pyi,sha256=w0XG_QKauiMLJ7eQ4S57IiLIURZHm_Snw7l6-ih9GP8,961
|
||||||
apprise/attachment/AttachFile.py,sha256=MbHY_av0GeM_AIBKV02Hq7SHiZ9eCr1yTfvDMUgi2I4,4765
|
apprise/attachment/AttachFile.py,sha256=MbHY_av0GeM_AIBKV02Hq7SHiZ9eCr1yTfvDMUgi2I4,4765
|
||||||
apprise/attachment/AttachHTTP.py,sha256=dyDy3U47cI28ENhaw1r5nQlGh8FWHZlHI8n9__k8wcY,11995
|
apprise/attachment/AttachHTTP.py,sha256=_CMPp4QGLATfGO2-Nw57sxsQyed9z3ywgoB0vpK3KZk,13779
|
||||||
apprise/attachment/__init__.py,sha256=xabgXpvV05X-YRuqIt3uGYMXwYNXjHyF6Dwd8HfZCFE,1658
|
apprise/attachment/__init__.py,sha256=xabgXpvV05X-YRuqIt3uGYMXwYNXjHyF6Dwd8HfZCFE,1658
|
||||||
apprise/cli.py,sha256=h-pWSQPqQficH6J-OEp3MTGydWyt6vMYnDZvHCeAt4Y,20697
|
apprise/cli.py,sha256=h-pWSQPqQficH6J-OEp3MTGydWyt6vMYnDZvHCeAt4Y,20697
|
||||||
apprise/common.py,sha256=I6wfrndggCL7l7KAl7Cm4uwAX9n0l3SN4-BVvTE0L0M,5593
|
apprise/common.py,sha256=I6wfrndggCL7l7KAl7Cm4uwAX9n0l3SN4-BVvTE0L0M,5593
|
||||||
apprise/common.pyi,sha256=luF3QRiClDCk8Z23rI6FCGYsVmodOt_JYfYyzGogdNM,447
|
apprise/common.pyi,sha256=luF3QRiClDCk8Z23rI6FCGYsVmodOt_JYfYyzGogdNM,447
|
||||||
apprise/config/ConfigBase.py,sha256=A4p_N9vSxOK37x9kuYeZFzHhAeEt-TCe2oweNi2KGg4,53062
|
apprise/config/ConfigBase.py,sha256=d1efIuQFCJr66WgpudV2DWtxY3-tuZAyMAhHXBzJ8p0,53194
|
||||||
apprise/config/ConfigBase.pyi,sha256=cngfobwH6v2vxYbQrObDi5Z-t5wcquWF-wR0kBCr3Eg,54
|
apprise/config/ConfigBase.pyi,sha256=cngfobwH6v2vxYbQrObDi5Z-t5wcquWF-wR0kBCr3Eg,54
|
||||||
apprise/config/ConfigFile.py,sha256=u_SDaN3OHMyaAq2X7k_T4_PRKkVsDwleqBz9YIN5lbA,6138
|
apprise/config/ConfigFile.py,sha256=u_SDaN3OHMyaAq2X7k_T4_PRKkVsDwleqBz9YIN5lbA,6138
|
||||||
apprise/config/ConfigHTTP.py,sha256=Iy6Ji8_nX3xDjFgJGLrz4ftrMlMiyKiFGzYGJ7rMSMQ,9457
|
apprise/config/ConfigHTTP.py,sha256=Iy6Ji8_nX3xDjFgJGLrz4ftrMlMiyKiFGzYGJ7rMSMQ,9457
|
||||||
apprise/config/ConfigMemory.py,sha256=epEAgNy-eJVWoQaUOvjivMWxXTofy6wAQ-NbCqYmuyE,2829
|
apprise/config/ConfigMemory.py,sha256=epEAgNy-eJVWoQaUOvjivMWxXTofy6wAQ-NbCqYmuyE,2829
|
||||||
apprise/config/__init__.py,sha256=lbsxrUpB1IYM2q7kjYhsXQGgPF-yZXJrKFE361tdIPY,1663
|
apprise/config/__init__.py,sha256=lbsxrUpB1IYM2q7kjYhsXQGgPF-yZXJrKFE361tdIPY,1663
|
||||||
apprise/conversion.py,sha256=bvTu-3TU2CPEhdroLRtd_XpDzzXqe_wyUql089IpYxs,6197
|
apprise/conversion.py,sha256=0VZ0eCZfksN-97Vl0TjVjwnCTgus3XTRioceSFnP-gc,6277
|
||||||
apprise/decorators/CustomNotifyPlugin.py,sha256=F49vOM2EVy43Pn3j8z7tgTacweMUxGhw0UX-1n2Y3c8,7836
|
apprise/decorators/CustomNotifyPlugin.py,sha256=i4D-sgOsBWsxO5auWCN2bgXLLPuADaaLlJ1gUKLj2bU,7972
|
||||||
apprise/decorators/__init__.py,sha256=e_PDAm0kQNzwDPx-NJZLPfLMd2VAABvNZtxx_iDviRM,1487
|
apprise/decorators/__init__.py,sha256=e_PDAm0kQNzwDPx-NJZLPfLMd2VAABvNZtxx_iDviRM,1487
|
||||||
apprise/decorators/notify.py,sha256=a2WupErNw1_SMAld7jPC273bskiChMpYy95BOog5A9w,5111
|
apprise/decorators/notify.py,sha256=a2WupErNw1_SMAld7jPC273bskiChMpYy95BOog5A9w,5111
|
||||||
apprise/emojis.py,sha256=ONF0t8dY9f2XlEkLUG79-ybKVAj2GqbPj2-Be97vAoI,87738
|
apprise/emojis.py,sha256=ONF0t8dY9f2XlEkLUG79-ybKVAj2GqbPj2-Be97vAoI,87738
|
||||||
|
@ -69,21 +69,22 @@ apprise/i18n/en/LC_MESSAGES/apprise.mo,sha256=oUTuHREmLEYN07oqYqRMJ_kU71-o5o37Ns
|
||||||
apprise/logger.py,sha256=131hqhed8cUj9x_mfXDEvwA2YbcYDFAYiWVK1HgxRVY,6921
|
apprise/logger.py,sha256=131hqhed8cUj9x_mfXDEvwA2YbcYDFAYiWVK1HgxRVY,6921
|
||||||
apprise/manager.py,sha256=R9w8jxQRNy6Z_XDcobkt4JYbrC4jtj2OwRw9Zrib3CA,26857
|
apprise/manager.py,sha256=R9w8jxQRNy6Z_XDcobkt4JYbrC4jtj2OwRw9Zrib3CA,26857
|
||||||
apprise/plugins/NotifyAppriseAPI.py,sha256=ISBE0brD3eQdyw3XrGXd4Uc4kSYvIuI3SSUVCt-bkdo,16654
|
apprise/plugins/NotifyAppriseAPI.py,sha256=ISBE0brD3eQdyw3XrGXd4Uc4kSYvIuI3SSUVCt-bkdo,16654
|
||||||
apprise/plugins/NotifyAprs.py,sha256=IS1uxIl391L3i2LOK6x8xmlOG1W58k4o793Oq2W5Wao,24220
|
apprise/plugins/NotifyAprs.py,sha256=xdL_aIVgb4ggxRFeCdkZAbgHYZ8DWLw9pRpLZQ0rHoE,25523
|
||||||
apprise/plugins/NotifyBark.py,sha256=bsDvKooRy4k1Gg7tvBjv3DIx7-WZiV_mbTrkTwMtd9Q,15698
|
apprise/plugins/NotifyBark.py,sha256=bsDvKooRy4k1Gg7tvBjv3DIx7-WZiV_mbTrkTwMtd9Q,15698
|
||||||
apprise/plugins/NotifyBase.py,sha256=9MB2uv4Rv8BnoXjU52k5Mv4YQppkNPv4Y_iPwauKxKQ,29716
|
apprise/plugins/NotifyBase.py,sha256=G3xkF_a2BWqNSxsrnOW7NUgHjOqBCYC5zihCifWemo8,30360
|
||||||
apprise/plugins/NotifyBase.pyi,sha256=aKlZXRYUgG8lz_ZgGkYYJ_GKhuf18youTmMU-FlG7z8,21
|
apprise/plugins/NotifyBase.pyi,sha256=aKlZXRYUgG8lz_ZgGkYYJ_GKhuf18youTmMU-FlG7z8,21
|
||||||
apprise/plugins/NotifyBoxcar.py,sha256=vR00-WggHa1nHYWyb-f5P2V-G4f683fU_-GBlIeJvD0,12867
|
apprise/plugins/NotifyBoxcar.py,sha256=vR00-WggHa1nHYWyb-f5P2V-G4f683fU_-GBlIeJvD0,12867
|
||||||
apprise/plugins/NotifyBulkSMS.py,sha256=stPWAFCfhBP617zYK9Dgk6pNJBN_WcyJtODzo0jR1QQ,16005
|
apprise/plugins/NotifyBulkSMS.py,sha256=stPWAFCfhBP617zYK9Dgk6pNJBN_WcyJtODzo0jR1QQ,16005
|
||||||
apprise/plugins/NotifyBulkVS.py,sha256=viLGeyUDiirRRM7CgRqqElHSLYFnMugDtWE6Ytjqfaw,13290
|
apprise/plugins/NotifyBulkVS.py,sha256=viLGeyUDiirRRM7CgRqqElHSLYFnMugDtWE6Ytjqfaw,13290
|
||||||
apprise/plugins/NotifyBurstSMS.py,sha256=cN2kRETKIK5LhwpQEA8C68LKv8KEUPmXYe-nTSegGls,15550
|
apprise/plugins/NotifyBurstSMS.py,sha256=cN2kRETKIK5LhwpQEA8C68LKv8KEUPmXYe-nTSegGls,15550
|
||||||
|
apprise/plugins/NotifyChantify.py,sha256=GJJOAtSnVoIfKbJF_W1DTu7WsvS_zHdjO4T1XTKT87g,6673
|
||||||
apprise/plugins/NotifyClickSend.py,sha256=UfOJqsas6WLjQskojuJE7I_-lrb5QrkMiBZv-po_Q9c,11229
|
apprise/plugins/NotifyClickSend.py,sha256=UfOJqsas6WLjQskojuJE7I_-lrb5QrkMiBZv-po_Q9c,11229
|
||||||
apprise/plugins/NotifyD7Networks.py,sha256=4E6Fh0kQoDlMMwgZJDOXky7c7KrdMMvqprcfm29scWU,15043
|
apprise/plugins/NotifyD7Networks.py,sha256=4E6Fh0kQoDlMMwgZJDOXky7c7KrdMMvqprcfm29scWU,15043
|
||||||
apprise/plugins/NotifyDBus.py,sha256=1eVJHIL3XkFjDePMqfcll35Ie1vxggJ1iBsVFAIaF00,14379
|
apprise/plugins/NotifyDBus.py,sha256=1eVJHIL3XkFjDePMqfcll35Ie1vxggJ1iBsVFAIaF00,14379
|
||||||
apprise/plugins/NotifyDapnet.py,sha256=KuXjBU0ZrIYtoDei85NeLZ-IP810T4w5oFXH9sWiSh0,13624
|
apprise/plugins/NotifyDapnet.py,sha256=KuXjBU0ZrIYtoDei85NeLZ-IP810T4w5oFXH9sWiSh0,13624
|
||||||
apprise/plugins/NotifyDingTalk.py,sha256=NJyETgN6QjtRqtxQjfBLFVuFpURyWykRftm6WpQJVbY,12009
|
apprise/plugins/NotifyDingTalk.py,sha256=NJyETgN6QjtRqtxQjfBLFVuFpURyWykRftm6WpQJVbY,12009
|
||||||
apprise/plugins/NotifyDiscord.py,sha256=M_qmTzB7NNL5_agjYDX38KBN1jRzDBp2EMSNwEF_9Tw,26072
|
apprise/plugins/NotifyDiscord.py,sha256=M_qmTzB7NNL5_agjYDX38KBN1jRzDBp2EMSNwEF_9Tw,26072
|
||||||
apprise/plugins/NotifyEmail.py,sha256=DhAzLFX4pzzuS07QQFcv0VUOYu2PzQE7TTjlPokJcPY,38883
|
apprise/plugins/NotifyEmail.py,sha256=Y_ZOrdK6hTUKHLvogKpV5VqD8byzDyDSvwIVmfdsC2g,39789
|
||||||
apprise/plugins/NotifyEmby.py,sha256=OMVO8XsVl_XCBYNNNQi8ni2lS4voLfU8Puk1xJOAvHs,24039
|
apprise/plugins/NotifyEmby.py,sha256=OMVO8XsVl_XCBYNNNQi8ni2lS4voLfU8Puk1xJOAvHs,24039
|
||||||
apprise/plugins/NotifyEnigma2.py,sha256=Hj0Q9YOeljSwbfiuMKLqXTVX_1g_mjNUGEts7wfrwno,11498
|
apprise/plugins/NotifyEnigma2.py,sha256=Hj0Q9YOeljSwbfiuMKLqXTVX_1g_mjNUGEts7wfrwno,11498
|
||||||
apprise/plugins/NotifyFCM/__init__.py,sha256=mBFtIgIJuLIFnMB5ndx5Makjs9orVMc2oLoD7LaVT48,21669
|
apprise/plugins/NotifyFCM/__init__.py,sha256=mBFtIgIJuLIFnMB5ndx5Makjs9orVMc2oLoD7LaVT48,21669
|
||||||
|
@ -91,9 +92,10 @@ apprise/plugins/NotifyFCM/color.py,sha256=8iqDtadloQh2TMxkFmIFwenHqKp1pHHn1bwyWO
|
||||||
apprise/plugins/NotifyFCM/common.py,sha256=978uBUoNdtopCtylipGiKQdsQ8FTONxkFBp7uJMZHc8,1718
|
apprise/plugins/NotifyFCM/common.py,sha256=978uBUoNdtopCtylipGiKQdsQ8FTONxkFBp7uJMZHc8,1718
|
||||||
apprise/plugins/NotifyFCM/oauth.py,sha256=Vvbd0-rd5BPIjAneG3rILU153JIzfSZ0kaDov6hm96M,11197
|
apprise/plugins/NotifyFCM/oauth.py,sha256=Vvbd0-rd5BPIjAneG3rILU153JIzfSZ0kaDov6hm96M,11197
|
||||||
apprise/plugins/NotifyFCM/priority.py,sha256=0WuRW1y1HVnybgjlTeCZPHzt7j8SwWnC7faNcjioAOc,8163
|
apprise/plugins/NotifyFCM/priority.py,sha256=0WuRW1y1HVnybgjlTeCZPHzt7j8SwWnC7faNcjioAOc,8163
|
||||||
apprise/plugins/NotifyFaast.py,sha256=_F1633tQhk8gCfaNpZZm808f2G0S6fP0OOEetSiv0h8,6972
|
apprise/plugins/NotifyFeishu.py,sha256=IpcABdLZJ1vcQdZHlmASVbNOiOCIrmgKFhz1hbdskY4,7266
|
||||||
apprise/plugins/NotifyFlock.py,sha256=0rUIa9nToGsO8BTUgixh8Z_qdVixJeH479UNYjcE4EM,12748
|
apprise/plugins/NotifyFlock.py,sha256=0rUIa9nToGsO8BTUgixh8Z_qdVixJeH479UNYjcE4EM,12748
|
||||||
apprise/plugins/NotifyForm.py,sha256=38nL-2m1cf4gEQFQ4NpvA4j9i5_nNUgelReWFSjyV5U,17905
|
apprise/plugins/NotifyForm.py,sha256=38nL-2m1cf4gEQFQ4NpvA4j9i5_nNUgelReWFSjyV5U,17905
|
||||||
|
apprise/plugins/NotifyFreeMobile.py,sha256=XCkgZLc3KKGlx_9UdeoMJVcHpeQrOml9T93S-DGf4bs,6644
|
||||||
apprise/plugins/NotifyGnome.py,sha256=8MXTa8gZg1wTgNJfLlmq7_fl3WaYK-SX6VR91u308C4,9059
|
apprise/plugins/NotifyGnome.py,sha256=8MXTa8gZg1wTgNJfLlmq7_fl3WaYK-SX6VR91u308C4,9059
|
||||||
apprise/plugins/NotifyGoogleChat.py,sha256=lnoN17m6lZANaXcElDTP8lcuVWjIZEK8C6_iqJNAnw4,12622
|
apprise/plugins/NotifyGoogleChat.py,sha256=lnoN17m6lZANaXcElDTP8lcuVWjIZEK8C6_iqJNAnw4,12622
|
||||||
apprise/plugins/NotifyGotify.py,sha256=DNlOIHyuYitO5use9oa_REPm2Fant7y9QSaatrZFNI0,10551
|
apprise/plugins/NotifyGotify.py,sha256=DNlOIHyuYitO5use9oa_REPm2Fant7y9QSaatrZFNI0,10551
|
||||||
|
@ -109,7 +111,7 @@ apprise/plugins/NotifyKumulos.py,sha256=eCEW2ZverZqETOLHVWMC4E8Ll6rEhhEWOSD73RD8
|
||||||
apprise/plugins/NotifyLametric.py,sha256=h8vZoX-Ll5NBZRprBlxTO2H9w0lOiMxglGvUgJtK4_8,37534
|
apprise/plugins/NotifyLametric.py,sha256=h8vZoX-Ll5NBZRprBlxTO2H9w0lOiMxglGvUgJtK4_8,37534
|
||||||
apprise/plugins/NotifyLine.py,sha256=OVI0ozMJcq_-dI8dodVX52dzUzgENlAbOik-Kw4l-rI,10676
|
apprise/plugins/NotifyLine.py,sha256=OVI0ozMJcq_-dI8dodVX52dzUzgENlAbOik-Kw4l-rI,10676
|
||||||
apprise/plugins/NotifyLunaSea.py,sha256=woN8XdkwAjhgxAXp7Zj4XsWLybNL80l4W3Dx5BvobZg,14459
|
apprise/plugins/NotifyLunaSea.py,sha256=woN8XdkwAjhgxAXp7Zj4XsWLybNL80l4W3Dx5BvobZg,14459
|
||||||
apprise/plugins/NotifyMQTT.py,sha256=PFLwESgR8dMZvVFHxmOZ8xfy-YqyX5b2kl_e8Z1lo-0,19537
|
apprise/plugins/NotifyMQTT.py,sha256=cnuG4f3bYYNPhEj9qDX8SLmnxLVT9G1b8J5w6-mQGKY,19545
|
||||||
apprise/plugins/NotifyMSG91.py,sha256=P7JPyT1xmucnaEeCZPf_6aJfe1gS_STYYwEM7hJ7QBw,12677
|
apprise/plugins/NotifyMSG91.py,sha256=P7JPyT1xmucnaEeCZPf_6aJfe1gS_STYYwEM7hJ7QBw,12677
|
||||||
apprise/plugins/NotifyMSTeams.py,sha256=dFH575hoLL3zRddbBKfozlYjxvPJGbj3BKvfJSIkvD0,22976
|
apprise/plugins/NotifyMSTeams.py,sha256=dFH575hoLL3zRddbBKfozlYjxvPJGbj3BKvfJSIkvD0,22976
|
||||||
apprise/plugins/NotifyMacOSX.py,sha256=y2fGpSZXomFiNwKbWImrXQUMVM4JR4uPCnsWpnxQrFA,8271
|
apprise/plugins/NotifyMacOSX.py,sha256=y2fGpSZXomFiNwKbWImrXQUMVM4JR4uPCnsWpnxQrFA,8271
|
||||||
|
@ -124,7 +126,7 @@ apprise/plugins/NotifyNextcloudTalk.py,sha256=dLl_g7Knq5PVcadbzDuQsxbGHTZlC4r-pQ
|
||||||
apprise/plugins/NotifyNotica.py,sha256=yHmk8HiNFjzoI4Gewo_nBRrx9liEmhT95k1d10wqhYg,12990
|
apprise/plugins/NotifyNotica.py,sha256=yHmk8HiNFjzoI4Gewo_nBRrx9liEmhT95k1d10wqhYg,12990
|
||||||
apprise/plugins/NotifyNotifiarr.py,sha256=ADwLJO9eenfLkNa09tXMGSBTM4c3zTY0SEePvyB8WYA,15857
|
apprise/plugins/NotifyNotifiarr.py,sha256=ADwLJO9eenfLkNa09tXMGSBTM4c3zTY0SEePvyB8WYA,15857
|
||||||
apprise/plugins/NotifyNotifico.py,sha256=Qe9jMN_M3GL4XlYIWkAf-w_Hf65g9Hde4bVuytGhUW4,12035
|
apprise/plugins/NotifyNotifico.py,sha256=Qe9jMN_M3GL4XlYIWkAf-w_Hf65g9Hde4bVuytGhUW4,12035
|
||||||
apprise/plugins/NotifyNtfy.py,sha256=TkDs6jOc30XQn2O2BJ14-nE_cohPdJiSS8DpYXc9hoE,27953
|
apprise/plugins/NotifyNtfy.py,sha256=AtJt2zH35mMQTwRDxKia93NPy6-4rtixplP53zIYV2M,27979
|
||||||
apprise/plugins/NotifyOffice365.py,sha256=8TxsVsdbUghmNj0kceMlmoZzTOKQTgn3priI8JuRuHE,25190
|
apprise/plugins/NotifyOffice365.py,sha256=8TxsVsdbUghmNj0kceMlmoZzTOKQTgn3priI8JuRuHE,25190
|
||||||
apprise/plugins/NotifyOneSignal.py,sha256=gsw7ckW7xLiJDRUb7eJHNe_4bvdBXmt6_YsB1u_ghjw,18153
|
apprise/plugins/NotifyOneSignal.py,sha256=gsw7ckW7xLiJDRUb7eJHNe_4bvdBXmt6_YsB1u_ghjw,18153
|
||||||
apprise/plugins/NotifyOpsgenie.py,sha256=zJWpknjoHq35Iv9w88ucR62odaeIN3nrGFPtYnhDdjA,20515
|
apprise/plugins/NotifyOpsgenie.py,sha256=zJWpknjoHq35Iv9w88ucR62odaeIN3nrGFPtYnhDdjA,20515
|
||||||
|
@ -144,7 +146,7 @@ apprise/plugins/NotifyPushy.py,sha256=mmWcnu905Fvc8ihYXvZ7lVYErGZH5Q-GbBNS20v5r4
|
||||||
apprise/plugins/NotifyRSyslog.py,sha256=W42LT90X65-pNoU7KdhdX1PBcmsz9RyV376CDa_H3CI,11982
|
apprise/plugins/NotifyRSyslog.py,sha256=W42LT90X65-pNoU7KdhdX1PBcmsz9RyV376CDa_H3CI,11982
|
||||||
apprise/plugins/NotifyReddit.py,sha256=E78OSyDQfUalBEcg71sdMsNBOwdj7cVBnELrhrZEAXY,25785
|
apprise/plugins/NotifyReddit.py,sha256=E78OSyDQfUalBEcg71sdMsNBOwdj7cVBnELrhrZEAXY,25785
|
||||||
apprise/plugins/NotifyRevolt.py,sha256=DRA9Xylwl6leVjVFuJcP4L1cG49CIBtnQdxh4BKnAZ4,14500
|
apprise/plugins/NotifyRevolt.py,sha256=DRA9Xylwl6leVjVFuJcP4L1cG49CIBtnQdxh4BKnAZ4,14500
|
||||||
apprise/plugins/NotifyRocketChat.py,sha256=GTEfT-upQ56tJgE0kuc59l4uQGySj_d15wjdcARR9Ko,24624
|
apprise/plugins/NotifyRocketChat.py,sha256=Cb_nasX0-G3FoPMYvNk55RJ-tHuXUCTLUn2wTSi4IcI,25738
|
||||||
apprise/plugins/NotifyRyver.py,sha256=yhHPMLGeJtcHwBKSPPk0OBfp59DgTvXio1R59JhrJu4,11823
|
apprise/plugins/NotifyRyver.py,sha256=yhHPMLGeJtcHwBKSPPk0OBfp59DgTvXio1R59JhrJu4,11823
|
||||||
apprise/plugins/NotifySES.py,sha256=wtRmpAZkS5mQma6sdiaPT6U1xcgoj77CB9mNFvSEAw8,33545
|
apprise/plugins/NotifySES.py,sha256=wtRmpAZkS5mQma6sdiaPT6U1xcgoj77CB9mNFvSEAw8,33545
|
||||||
apprise/plugins/NotifySMSEagle.py,sha256=voFNqOewD9OC1eRctD0YdUB_ZSWsb06rjUwBfCcxPYA,24161
|
apprise/plugins/NotifySMSEagle.py,sha256=voFNqOewD9OC1eRctD0YdUB_ZSWsb06rjUwBfCcxPYA,24161
|
||||||
|
@ -162,7 +164,7 @@ apprise/plugins/NotifyStreamlabs.py,sha256=lx3N8T2ufUWFYIZ-kU_rOv50YyGWBqLSCKk7x
|
||||||
apprise/plugins/NotifySynology.py,sha256=_jTqfgWeOuSi_I8geMOraHBVFtDkvm9mempzymrmeAo,11105
|
apprise/plugins/NotifySynology.py,sha256=_jTqfgWeOuSi_I8geMOraHBVFtDkvm9mempzymrmeAo,11105
|
||||||
apprise/plugins/NotifySyslog.py,sha256=J9Kain2bb-PDNiG5Ydb0q678cYjNE_NjZFqMG9oEXM0,10617
|
apprise/plugins/NotifySyslog.py,sha256=J9Kain2bb-PDNiG5Ydb0q678cYjNE_NjZFqMG9oEXM0,10617
|
||||||
apprise/plugins/NotifyTechulusPush.py,sha256=m43_Qj1scPcgCRX5Dr2Ul7nxMbaiVxNzm_HRuNmfgoA,7253
|
apprise/plugins/NotifyTechulusPush.py,sha256=m43_Qj1scPcgCRX5Dr2Ul7nxMbaiVxNzm_HRuNmfgoA,7253
|
||||||
apprise/plugins/NotifyTelegram.py,sha256=Bim4mmPcefHNpvbNSy3pmLuCXRw5IVVWUNUB1SkIhDM,35624
|
apprise/plugins/NotifyTelegram.py,sha256=XE7PC9LRzcrfE2bpLKyor5lO_7B9LS4Xw1UlUmA4a2A,37187
|
||||||
apprise/plugins/NotifyThreema.py,sha256=C_C3j0fJWgeF2uB7ceJFXOdC6Lt0TFBInFMs5Xlg04M,11885
|
apprise/plugins/NotifyThreema.py,sha256=C_C3j0fJWgeF2uB7ceJFXOdC6Lt0TFBInFMs5Xlg04M,11885
|
||||||
apprise/plugins/NotifyTwilio.py,sha256=WCo8eTI9OF1rtg3ueHHRDXt4Lp45eZ6h3IdTZVf5HM8,15976
|
apprise/plugins/NotifyTwilio.py,sha256=WCo8eTI9OF1rtg3ueHHRDXt4Lp45eZ6h3IdTZVf5HM8,15976
|
||||||
apprise/plugins/NotifyTwist.py,sha256=nZA73CYVe-p0tkVMy5q3vFRyflLM4yjUo9LECvkUwgc,28841
|
apprise/plugins/NotifyTwist.py,sha256=nZA73CYVe-p0tkVMy5q3vFRyflLM4yjUo9LECvkUwgc,28841
|
||||||
|
@ -175,7 +177,7 @@ apprise/plugins/NotifyWhatsApp.py,sha256=PtzW0ue3d2wZ8Pva_LG29jUcpRRP03TFxO5SME_
|
||||||
apprise/plugins/NotifyWindows.py,sha256=QgWJfJF8AE6RWr-L81YYVZNWrnImK9Qr3B991HWanqU,8563
|
apprise/plugins/NotifyWindows.py,sha256=QgWJfJF8AE6RWr-L81YYVZNWrnImK9Qr3B991HWanqU,8563
|
||||||
apprise/plugins/NotifyXBMC.py,sha256=5hDuOTP3Kwtp4NEMaokNjWyEKEkQcN_fSx-cUPJvhaU,12096
|
apprise/plugins/NotifyXBMC.py,sha256=5hDuOTP3Kwtp4NEMaokNjWyEKEkQcN_fSx-cUPJvhaU,12096
|
||||||
apprise/plugins/NotifyXML.py,sha256=WJnmdvXseuTRgioVMRqpR8a09cDfTpPTfuFlTnT_TfI,16973
|
apprise/plugins/NotifyXML.py,sha256=WJnmdvXseuTRgioVMRqpR8a09cDfTpPTfuFlTnT_TfI,16973
|
||||||
apprise/plugins/NotifyZulip.py,sha256=mbZoPiQXFbcaJ5UYDbkX4HJPAvRzPEAB-rsOlF9SD4o,13755
|
apprise/plugins/NotifyZulip.py,sha256=M8cSL7nZvtBYyTX6045g34tyn2vyybltgD1CoI4Xa7A,13968
|
||||||
apprise/plugins/__init__.py,sha256=jTfLmW47kZC_Wf5eFFta2NoD2J-7_E7JaPrrVMIECkU,18725
|
apprise/plugins/__init__.py,sha256=jTfLmW47kZC_Wf5eFFta2NoD2J-7_E7JaPrrVMIECkU,18725
|
||||||
apprise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
apprise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
apprise/utils.py,sha256=SjRU2tb1UsVnTCTXPUyXVz3WpRbDWwAHH-d3ll38EHY,53185
|
apprise/utils.py,sha256=SjRU2tb1UsVnTCTXPUyXVz3WpRbDWwAHH-d3ll38EHY,53185
|
|
@ -219,6 +219,9 @@ class AppriseLocale:
|
||||||
try:
|
try:
|
||||||
# Acquire our locale
|
# Acquire our locale
|
||||||
lang = locale.getlocale()[0]
|
lang = locale.getlocale()[0]
|
||||||
|
# Compatibility for Python >= 3.12
|
||||||
|
if lang == 'C':
|
||||||
|
lang = AppriseLocale._default_language
|
||||||
|
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
# This occurs when an invalid locale was parsed from the
|
# This occurs when an invalid locale was parsed from the
|
||||||
|
|
|
@ -669,6 +669,79 @@ class URLBase:
|
||||||
'verify': 'yes' if self.verify_certificate else 'no',
|
'verify': 'yes' if self.verify_certificate else 'no',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post_process_parse_url_results(results):
|
||||||
|
"""
|
||||||
|
After parsing the URL, this function applies a bit of extra logic to
|
||||||
|
support extra entries like `pass` becoming `password`, etc
|
||||||
|
|
||||||
|
This function assumes that parse_url() was called previously setting
|
||||||
|
up the basics to be checked
|
||||||
|
"""
|
||||||
|
|
||||||
|
# if our URL ends with an 's', then assume our secure flag is set.
|
||||||
|
results['secure'] = (results['schema'][-1] == 's')
|
||||||
|
|
||||||
|
# QSD Checking (over-rides all)
|
||||||
|
qsd_exists = True if isinstance(results.get('qsd'), dict) else False
|
||||||
|
|
||||||
|
if qsd_exists and 'verify' in results['qsd']:
|
||||||
|
# Pulled from URL String
|
||||||
|
results['verify'] = parse_bool(
|
||||||
|
results['qsd'].get('verify', True))
|
||||||
|
|
||||||
|
elif 'verify' in results:
|
||||||
|
# Pulled from YAML Configuratoin
|
||||||
|
results['verify'] = parse_bool(results.get('verify', True))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Support SSL Certificate 'verify' keyword. Default to being
|
||||||
|
# enabled
|
||||||
|
results['verify'] = True
|
||||||
|
|
||||||
|
# Password overrides
|
||||||
|
if 'pass' in results:
|
||||||
|
results['password'] = results['pass']
|
||||||
|
del results['pass']
|
||||||
|
|
||||||
|
if qsd_exists:
|
||||||
|
if 'password' in results['qsd']:
|
||||||
|
results['password'] = results['qsd']['password']
|
||||||
|
if 'pass' in results['qsd']:
|
||||||
|
results['password'] = results['qsd']['pass']
|
||||||
|
|
||||||
|
# User overrides
|
||||||
|
if 'user' in results['qsd']:
|
||||||
|
results['user'] = results['qsd']['user']
|
||||||
|
|
||||||
|
# parse_url() always creates a 'password' and 'user' entry in the
|
||||||
|
# results returned. Entries are set to None if they weren't
|
||||||
|
# specified
|
||||||
|
if results['password'] is None and 'user' in results['qsd']:
|
||||||
|
# Handle cases where the user= provided in 2 locations, we want
|
||||||
|
# the original to fall back as a being a password (if one
|
||||||
|
# wasn't otherwise defined) e.g.
|
||||||
|
# mailtos://PASSWORD@hostname?user=admin@mail-domain.com
|
||||||
|
# - in the above, the PASSWORD gets lost in the parse url()
|
||||||
|
# since a user= over-ride is specified.
|
||||||
|
presults = parse_url(results['url'])
|
||||||
|
if presults:
|
||||||
|
# Store our Password
|
||||||
|
results['password'] = presults['user']
|
||||||
|
|
||||||
|
# Store our socket read timeout if specified
|
||||||
|
if 'rto' in results['qsd']:
|
||||||
|
results['rto'] = results['qsd']['rto']
|
||||||
|
|
||||||
|
# Store our socket connect timeout if specified
|
||||||
|
if 'cto' in results['qsd']:
|
||||||
|
results['cto'] = results['qsd']['cto']
|
||||||
|
|
||||||
|
if 'port' in results['qsd']:
|
||||||
|
results['port'] = results['qsd']['port']
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url, verify_host=True, plus_to_space=False,
|
def parse_url(url, verify_host=True, plus_to_space=False,
|
||||||
strict_port=False):
|
strict_port=False):
|
||||||
|
@ -698,53 +771,7 @@ class URLBase:
|
||||||
# We're done; we failed to parse our url
|
# We're done; we failed to parse our url
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# if our URL ends with an 's', then assume our secure flag is set.
|
return URLBase.post_process_parse_url_results(results)
|
||||||
results['secure'] = (results['schema'][-1] == 's')
|
|
||||||
|
|
||||||
# Support SSL Certificate 'verify' keyword. Default to being enabled
|
|
||||||
results['verify'] = True
|
|
||||||
|
|
||||||
if 'verify' in results['qsd']:
|
|
||||||
results['verify'] = parse_bool(
|
|
||||||
results['qsd'].get('verify', True))
|
|
||||||
|
|
||||||
# Password overrides
|
|
||||||
if 'password' in results['qsd']:
|
|
||||||
results['password'] = results['qsd']['password']
|
|
||||||
if 'pass' in results['qsd']:
|
|
||||||
results['password'] = results['qsd']['pass']
|
|
||||||
|
|
||||||
# User overrides
|
|
||||||
if 'user' in results['qsd']:
|
|
||||||
results['user'] = results['qsd']['user']
|
|
||||||
|
|
||||||
# parse_url() always creates a 'password' and 'user' entry in the
|
|
||||||
# results returned. Entries are set to None if they weren't specified
|
|
||||||
if results['password'] is None and 'user' in results['qsd']:
|
|
||||||
# Handle cases where the user= provided in 2 locations, we want
|
|
||||||
# the original to fall back as a being a password (if one wasn't
|
|
||||||
# otherwise defined)
|
|
||||||
# e.g.
|
|
||||||
# mailtos://PASSWORD@hostname?user=admin@mail-domain.com
|
|
||||||
# - the PASSWORD gets lost in the parse url() since a user=
|
|
||||||
# over-ride is specified.
|
|
||||||
presults = parse_url(results['url'])
|
|
||||||
if presults:
|
|
||||||
# Store our Password
|
|
||||||
results['password'] = presults['user']
|
|
||||||
|
|
||||||
# Store our socket read timeout if specified
|
|
||||||
if 'rto' in results['qsd']:
|
|
||||||
results['rto'] = results['qsd']['rto']
|
|
||||||
|
|
||||||
# Store our socket connect timeout if specified
|
|
||||||
if 'cto' in results['qsd']:
|
|
||||||
results['cto'] = results['qsd']['cto']
|
|
||||||
|
|
||||||
if 'port' in results['qsd']:
|
|
||||||
results['port'] = results['qsd']['port']
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def http_response_code_lookup(code, response_mask=None):
|
def http_response_code_lookup(code, response_mask=None):
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
__title__ = 'Apprise'
|
__title__ = 'Apprise'
|
||||||
__version__ = '1.7.4'
|
__version__ = '1.7.6'
|
||||||
__author__ = 'Chris Caron'
|
__author__ = 'Chris Caron'
|
||||||
__license__ = 'BSD'
|
__license__ = 'BSD'
|
||||||
__copywrite__ = 'Copyright (C) 2024 Chris Caron <lead2gold@gmail.com>'
|
__copywrite__ = 'Copyright (C) 2024 Chris Caron <lead2gold@gmail.com>'
|
||||||
|
|
|
@ -253,7 +253,7 @@ class AttachBase(URLBase):
|
||||||
return self.detected_mimetype \
|
return self.detected_mimetype \
|
||||||
if self.detected_mimetype else self.unknown_mimetype
|
if self.detected_mimetype else self.unknown_mimetype
|
||||||
|
|
||||||
def exists(self):
|
def exists(self, retrieve_if_missing=True):
|
||||||
"""
|
"""
|
||||||
Simply returns true if the object has downloaded and stored the
|
Simply returns true if the object has downloaded and stored the
|
||||||
attachment AND the attachment has not expired.
|
attachment AND the attachment has not expired.
|
||||||
|
@ -282,7 +282,7 @@ class AttachBase(URLBase):
|
||||||
# The file is not present
|
# The file is not present
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.download()
|
return False if not retrieve_if_missing else self.download()
|
||||||
|
|
||||||
def invalidate(self):
|
def invalidate(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import threading
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from .AttachBase import AttachBase
|
from .AttachBase import AttachBase
|
||||||
from ..common import ContentLocation
|
from ..common import ContentLocation
|
||||||
|
@ -56,6 +57,9 @@ class AttachHTTP(AttachBase):
|
||||||
# Web based requests are remote/external to our current location
|
# Web based requests are remote/external to our current location
|
||||||
location = ContentLocation.HOSTED
|
location = ContentLocation.HOSTED
|
||||||
|
|
||||||
|
# thread safe loading
|
||||||
|
_lock = threading.Lock()
|
||||||
|
|
||||||
def __init__(self, headers=None, **kwargs):
|
def __init__(self, headers=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize HTTP Object
|
Initialize HTTP Object
|
||||||
|
@ -96,9 +100,6 @@ class AttachHTTP(AttachBase):
|
||||||
# our content is inaccessible
|
# our content is inaccessible
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ensure any existing content set has been invalidated
|
|
||||||
self.invalidate()
|
|
||||||
|
|
||||||
# prepare header
|
# prepare header
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
|
@ -117,16 +118,28 @@ class AttachHTTP(AttachBase):
|
||||||
|
|
||||||
url += self.fullpath
|
url += self.fullpath
|
||||||
|
|
||||||
self.logger.debug('HTTP POST URL: %s (cert_verify=%r)' % (
|
|
||||||
url, self.verify_certificate,
|
|
||||||
))
|
|
||||||
|
|
||||||
# Where our request object will temporarily live.
|
# Where our request object will temporarily live.
|
||||||
r = None
|
r = None
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
# Always call throttle before any remote server i/o is made
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
if self.exists(retrieve_if_missing=False):
|
||||||
|
# Due to locking; it's possible a concurrent thread already
|
||||||
|
# handled the retrieval in which case we can safely move on
|
||||||
|
self.logger.trace(
|
||||||
|
'HTTP Attachment %s already retrieved',
|
||||||
|
self._temp_file.name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Ensure any existing content set has been invalidated
|
||||||
|
self.invalidate()
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'HTTP Attachment Fetch URL: %s (cert_verify=%r)' % (
|
||||||
|
url, self.verify_certificate))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Make our request
|
# Make our request
|
||||||
with requests.get(
|
with requests.get(
|
||||||
|
@ -149,12 +162,13 @@ class AttachHTTP(AttachBase):
|
||||||
file_size = 0
|
file_size = 0
|
||||||
|
|
||||||
# Perform a little Q/A on file limitations and restrictions
|
# Perform a little Q/A on file limitations and restrictions
|
||||||
if self.max_file_size > 0 and file_size > self.max_file_size:
|
if self.max_file_size > 0 and \
|
||||||
|
file_size > self.max_file_size:
|
||||||
|
|
||||||
# The content retrieved is to large
|
# The content retrieved is to large
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
'HTTP response exceeds allowable maximum file length '
|
'HTTP response exceeds allowable maximum file '
|
||||||
'({}KB): {}'.format(
|
'length ({}KB): {}'.format(
|
||||||
int(self.max_file_size / 1024),
|
int(self.max_file_size / 1024),
|
||||||
self.url(privacy=True)))
|
self.url(privacy=True)))
|
||||||
|
|
||||||
|
@ -171,8 +185,11 @@ class AttachHTTP(AttachBase):
|
||||||
if result:
|
if result:
|
||||||
self.detected_name = result.group('name').strip()
|
self.detected_name = result.group('name').strip()
|
||||||
|
|
||||||
# Create a temporary file to work with
|
# Create a temporary file to work with; delete must be set
|
||||||
self._temp_file = NamedTemporaryFile()
|
# to False or it isn't compatible with Microsoft Windows
|
||||||
|
# instances. In lieu of this, __del__ will clean up the
|
||||||
|
# file for us.
|
||||||
|
self._temp_file = NamedTemporaryFile(delete=False)
|
||||||
|
|
||||||
# Get our chunk size
|
# Get our chunk size
|
||||||
chunk_size = self.chunk_size
|
chunk_size = self.chunk_size
|
||||||
|
@ -180,21 +197,24 @@ class AttachHTTP(AttachBase):
|
||||||
# Track all bytes written to disk
|
# Track all bytes written to disk
|
||||||
bytes_written = 0
|
bytes_written = 0
|
||||||
|
|
||||||
# If we get here, we can now safely write our content to disk
|
# If we get here, we can now safely write our content to
|
||||||
|
# disk
|
||||||
for chunk in r.iter_content(chunk_size=chunk_size):
|
for chunk in r.iter_content(chunk_size=chunk_size):
|
||||||
# filter out keep-alive chunks
|
# filter out keep-alive chunks
|
||||||
if chunk:
|
if chunk:
|
||||||
self._temp_file.write(chunk)
|
self._temp_file.write(chunk)
|
||||||
bytes_written = self._temp_file.tell()
|
bytes_written = self._temp_file.tell()
|
||||||
|
|
||||||
# Prevent a case where Content-Length isn't provided
|
# Prevent a case where Content-Length isn't
|
||||||
# we don't want to fetch beyond our limits
|
# provided. In this case we don't want to fetch
|
||||||
|
# beyond our limits
|
||||||
if self.max_file_size > 0:
|
if self.max_file_size > 0:
|
||||||
if bytes_written > self.max_file_size:
|
if bytes_written > self.max_file_size:
|
||||||
# The content retrieved is to large
|
# The content retrieved is to large
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
'HTTP response exceeds allowable maximum '
|
'HTTP response exceeds allowable '
|
||||||
'file length ({}KB): {}'.format(
|
'maximum file length '
|
||||||
|
'({}KB): {}'.format(
|
||||||
int(self.max_file_size / 1024),
|
int(self.max_file_size / 1024),
|
||||||
self.url(privacy=True)))
|
self.url(privacy=True)))
|
||||||
|
|
||||||
|
@ -206,15 +226,16 @@ class AttachHTTP(AttachBase):
|
||||||
|
|
||||||
elif bytes_written + chunk_size \
|
elif bytes_written + chunk_size \
|
||||||
> self.max_file_size:
|
> self.max_file_size:
|
||||||
# Adjust out next read to accomodate up to our
|
# Adjust out next read to accomodate up to
|
||||||
# limit +1. This will prevent us from readig
|
# our limit +1. This will prevent us from
|
||||||
# to much into our memory buffer
|
# reading to much into our memory buffer
|
||||||
self.max_file_size - bytes_written + 1
|
self.max_file_size - bytes_written + 1
|
||||||
|
|
||||||
# Ensure our content is flushed to disk for post-processing
|
# Ensure our content is flushed to disk for post-processing
|
||||||
self._temp_file.flush()
|
self._temp_file.flush()
|
||||||
|
|
||||||
# Set our minimum requirements for a successful download() call
|
# Set our minimum requirements for a successful download()
|
||||||
|
# call
|
||||||
self.download_path = self._temp_file.name
|
self.download_path = self._temp_file.name
|
||||||
if not self.detected_name:
|
if not self.detected_name:
|
||||||
self.detected_name = os.path.basename(self.fullpath)
|
self.detected_name = os.path.basename(self.fullpath)
|
||||||
|
@ -254,11 +275,30 @@ class AttachHTTP(AttachBase):
|
||||||
Close our temporary file
|
Close our temporary file
|
||||||
"""
|
"""
|
||||||
if self._temp_file:
|
if self._temp_file:
|
||||||
|
self.logger.trace(
|
||||||
|
'Attachment cleanup of %s', self._temp_file.name)
|
||||||
self._temp_file.close()
|
self._temp_file.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Ensure our file is removed (if it exists)
|
||||||
|
os.unlink(self._temp_file.name)
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Reset our temporary file to prevent from entering
|
||||||
|
# this block again
|
||||||
self._temp_file = None
|
self._temp_file = None
|
||||||
|
|
||||||
super().invalidate()
|
super().invalidate()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""
|
||||||
|
Tidy memory if open
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
self.invalidate()
|
||||||
|
|
||||||
def url(self, privacy=False, *args, **kwargs):
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
|
|
@ -1184,6 +1184,9 @@ class ConfigBase(URLBase):
|
||||||
# Prepare our Asset Object
|
# Prepare our Asset Object
|
||||||
_results['asset'] = asset
|
_results['asset'] = asset
|
||||||
|
|
||||||
|
# Handle post processing of result set
|
||||||
|
_results = URLBase.post_process_parse_url_results(_results)
|
||||||
|
|
||||||
# Store our preloaded entries
|
# Store our preloaded entries
|
||||||
preloaded.append({
|
preloaded.append({
|
||||||
'results': _results,
|
'results': _results,
|
||||||
|
|
|
@ -58,8 +58,8 @@ def markdown_to_html(content):
|
||||||
"""
|
"""
|
||||||
Converts specified content from markdown to HTML.
|
Converts specified content from markdown to HTML.
|
||||||
"""
|
"""
|
||||||
|
return markdown(content, extensions=[
|
||||||
return markdown(content)
|
'markdown.extensions.nl2br', 'markdown.extensions.tables'])
|
||||||
|
|
||||||
|
|
||||||
def text_to_html(content):
|
def text_to_html(content):
|
||||||
|
|
|
@ -147,6 +147,10 @@ class CustomNotifyPlugin(NotifyBase):
|
||||||
|
|
||||||
self._default_args = {}
|
self._default_args = {}
|
||||||
|
|
||||||
|
# Some variables do not need to be set
|
||||||
|
if 'secure' in kwargs:
|
||||||
|
del kwargs['secure']
|
||||||
|
|
||||||
# Apply our updates based on what was parsed
|
# Apply our updates based on what was parsed
|
||||||
dict_full_update(self._default_args, self._base_args)
|
dict_full_update(self._default_args, self._base_args)
|
||||||
dict_full_update(self._default_args, kwargs)
|
dict_full_update(self._default_args, kwargs)
|
||||||
|
|
|
@ -203,6 +203,13 @@ class NotifyAprs(NotifyBase):
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"map_to": "targets",
|
"map_to": "targets",
|
||||||
},
|
},
|
||||||
|
"delay": {
|
||||||
|
"name": _("Resend Delay"),
|
||||||
|
"type": "float",
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 5.0,
|
||||||
|
"default": 0.0,
|
||||||
|
},
|
||||||
"locale": {
|
"locale": {
|
||||||
"name": _("Locale"),
|
"name": _("Locale"),
|
||||||
"type": "choice:string",
|
"type": "choice:string",
|
||||||
|
@ -212,7 +219,7 @@ class NotifyAprs(NotifyBase):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, targets=None, locale=None, **kwargs):
|
def __init__(self, targets=None, locale=None, delay=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize APRS Object
|
Initialize APRS Object
|
||||||
"""
|
"""
|
||||||
|
@ -272,6 +279,28 @@ class NotifyAprs(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Update our delay
|
||||||
|
if delay is None:
|
||||||
|
self.delay = NotifyAprs.template_args["delay"]["default"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.delay = float(delay)
|
||||||
|
if self.delay < NotifyAprs.template_args["delay"]["min"]:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
elif self.delay >= NotifyAprs.template_args["delay"]["max"]:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
msg = "Unsupported APRS-IS delay ({}) specified. ".format(
|
||||||
|
delay)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Bump up our request_rate
|
||||||
|
self.request_rate_per_sec += self.delay
|
||||||
|
|
||||||
# Set the transmitter group
|
# Set the transmitter group
|
||||||
self.locale = \
|
self.locale = \
|
||||||
NotifyAprs.template_args["locale"]["default"] \
|
NotifyAprs.template_args["locale"]["default"] \
|
||||||
|
@ -674,6 +703,10 @@ class NotifyAprs(NotifyBase):
|
||||||
# Store our locale if not default
|
# Store our locale if not default
|
||||||
params['locale'] = self.locale
|
params['locale'] = self.locale
|
||||||
|
|
||||||
|
if self.delay != NotifyAprs.template_args["delay"]["default"]:
|
||||||
|
# Store our locale if not default
|
||||||
|
params['delay'] = "{:.2f}".format(self.delay)
|
||||||
|
|
||||||
# Extend our parameters
|
# Extend our parameters
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
@ -727,6 +760,10 @@ class NotifyAprs(NotifyBase):
|
||||||
# All entries after the hostname are additional targets
|
# All entries after the hostname are additional targets
|
||||||
results["targets"].extend(NotifyAprs.split_path(results["fullpath"]))
|
results["targets"].extend(NotifyAprs.split_path(results["fullpath"]))
|
||||||
|
|
||||||
|
# Get Delay (if set)
|
||||||
|
if 'delay' in results['qsd'] and len(results['qsd']['delay']):
|
||||||
|
results['delay'] = NotifyAprs.unquote(results['qsd']['delay'])
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support rooms this way too
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if "to" in results["qsd"] and len(results["qsd"]["to"]):
|
if "to" in results["qsd"] and len(results["qsd"]["to"]):
|
||||||
|
|
|
@ -457,6 +457,19 @@ class NotifyBase(URLBase):
|
||||||
# Handle situations where the title is None
|
# Handle situations where the title is None
|
||||||
title = '' if not title else title
|
title = '' if not title else title
|
||||||
|
|
||||||
|
# Truncate flag set with attachments ensures that only 1
|
||||||
|
# attachment passes through. In the event there could be many
|
||||||
|
# services specified, we only want to do this logic once.
|
||||||
|
# The logic is only applicable if ther was more then 1 attachment
|
||||||
|
# specified
|
||||||
|
overflow = self.overflow_mode if overflow is None else overflow
|
||||||
|
if attach and len(attach) > 1 and overflow == OverflowMode.TRUNCATE:
|
||||||
|
# Save first attachment
|
||||||
|
_attach = AppriseAttachment(attach[0], asset=self.asset)
|
||||||
|
else:
|
||||||
|
# reference same attachment
|
||||||
|
_attach = attach
|
||||||
|
|
||||||
# Apply our overflow (if defined)
|
# Apply our overflow (if defined)
|
||||||
for chunk in self._apply_overflow(
|
for chunk in self._apply_overflow(
|
||||||
body=body, title=title, overflow=overflow,
|
body=body, title=title, overflow=overflow,
|
||||||
|
@ -465,7 +478,7 @@ class NotifyBase(URLBase):
|
||||||
# Send notification
|
# Send notification
|
||||||
yield dict(
|
yield dict(
|
||||||
body=chunk['body'], title=chunk['title'],
|
body=chunk['body'], title=chunk['title'],
|
||||||
notify_type=notify_type, attach=attach,
|
notify_type=notify_type, attach=_attach,
|
||||||
body_format=body_format
|
body_format=body_format
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -485,7 +498,7 @@ class NotifyBase(URLBase):
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'the title goes here',
|
title: 'the title goes here',
|
||||||
body: 'the message body goes here',
|
body: 'the continued message body goes here',
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,118 +26,111 @@
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
# Chantify
|
||||||
|
# 1. Visit https://chanify.net/
|
||||||
|
|
||||||
|
# The API URL will look something like this:
|
||||||
|
# https://api.chanify.net/v1/sender/token
|
||||||
|
#
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..utils import parse_bool
|
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class NotifyFaast(NotifyBase):
|
class NotifyChantify(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for Faast Notifications
|
A wrapper for Chantify Notifications
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The default descriptive name associated with the Notification
|
# The default descriptive name associated with the Notification
|
||||||
service_name = 'Faast'
|
service_name = _('Chantify')
|
||||||
|
|
||||||
# The services URL
|
# The services URL
|
||||||
service_url = 'http://www.faast.io/'
|
service_url = 'https://chanify.net/'
|
||||||
|
|
||||||
# The default protocol (this is secure for faast)
|
# The default secure protocol
|
||||||
protocol = 'faast'
|
secure_protocol = 'chantify'
|
||||||
|
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_faast'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_chantify'
|
||||||
|
|
||||||
# Faast uses the http protocol with JSON requests
|
# Notification URL
|
||||||
notify_url = 'https://www.appnotifications.com/account/notifications.json'
|
notify_url = 'https://api.chanify.net/v1/sender/{token}/'
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
|
||||||
image_size = NotifyImageSize.XY_72
|
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{authtoken}',
|
'{schema}://{token}',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# The title is not used
|
||||||
|
title_maxlen = 0
|
||||||
|
|
||||||
|
# Define our tokens; these are the minimum tokens required required to
|
||||||
|
# be passed into this function (as arguments). The syntax appends any
|
||||||
|
# previously defined in the base package and builds onto them
|
||||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
'authtoken': {
|
'token': {
|
||||||
'name': _('Authorization Token'),
|
'name': _('Token'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
|
'regex': (r'^[A-Z0-9_-]+$', 'i'),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Define our template arguments
|
# Define our template arguments
|
||||||
template_args = dict(NotifyBase.template_args, **{
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
'image': {
|
'token': {
|
||||||
'name': _('Include Image'),
|
'alias_of': 'token',
|
||||||
'type': 'bool',
|
|
||||||
'default': True,
|
|
||||||
'map_to': 'include_image',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, authtoken, include_image=True, **kwargs):
|
def __init__(self, token, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Faast Object
|
Initialize Chantify Object
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Store the Authentication Token
|
self.token = validate_regex(
|
||||||
self.authtoken = validate_regex(authtoken)
|
token, *self.template_tokens['token']['regex'])
|
||||||
if not self.authtoken:
|
if not self.token:
|
||||||
msg = 'An invalid Faast Authentication Token ' \
|
msg = 'The Chantify token specified ({}) is invalid.'\
|
||||||
'({}) was specified.'.format(authtoken)
|
.format(token)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Associate an image with our post
|
|
||||||
self.include_image = include_image
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Faast Notification
|
Send our notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# prepare our headers
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
}
|
}
|
||||||
|
|
||||||
# prepare JSON Object
|
# Our Message
|
||||||
payload = {
|
payload = {
|
||||||
'user_credentials': self.authtoken,
|
'text': body
|
||||||
'title': title,
|
|
||||||
'message': body,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Acquire our image if we're configured to do so
|
self.logger.debug('Chantify GET URL: %s (cert_verify=%r)' % (
|
||||||
image_url = None if not self.include_image \
|
self.notify_url, self.verify_certificate))
|
||||||
else self.image_url(notify_type)
|
self.logger.debug('Chantify Payload: %s' % str(payload))
|
||||||
|
|
||||||
if image_url:
|
|
||||||
payload['icon_url'] = image_url
|
|
||||||
|
|
||||||
self.logger.debug('Faast POST URL: %s (cert_verify=%r)' % (
|
|
||||||
self.notify_url, self.verify_certificate,
|
|
||||||
))
|
|
||||||
self.logger.debug('Faast Payload: %s' % str(payload))
|
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
# Always call throttle before any remote server i/o is made
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
self.notify_url,
|
self.notify_url.format(token=self.token),
|
||||||
data=payload,
|
data=payload,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
|
@ -146,10 +139,10 @@ class NotifyFaast(NotifyBase):
|
||||||
if r.status_code != requests.codes.ok:
|
if r.status_code != requests.codes.ok:
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyFaast.http_response_code_lookup(r.status_code)
|
NotifyChantify.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Faast notification:'
|
'Failed to send Chantify notification: '
|
||||||
'{}{}error={}.'.format(
|
'{}{}error={}.'.format(
|
||||||
status_str,
|
status_str,
|
||||||
', ' if status_str else '',
|
', ' if status_str else '',
|
||||||
|
@ -161,12 +154,12 @@ class NotifyFaast(NotifyBase):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info('Sent Faast notification.')
|
self.logger.info('Sent Chantify notification.')
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A Connection error occurred sending Faast notification.',
|
'A Connection error occurred sending Chantify '
|
||||||
)
|
'notification.')
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
|
@ -179,18 +172,13 @@ class NotifyFaast(NotifyBase):
|
||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Define any URL parameters
|
# Prepare our parameters
|
||||||
params = {
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
'image': 'yes' if self.include_image else 'no',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extend our parameters
|
return '{schema}://{token}/?{params}'.format(
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
schema=self.secure_protocol,
|
||||||
|
token=self.pprint(self.token, privacy, safe=''),
|
||||||
return '{schema}://{authtoken}/?{params}'.format(
|
params=NotifyChantify.urlencode(params),
|
||||||
schema=self.protocol,
|
|
||||||
authtoken=self.pprint(self.authtoken, privacy, safe=''),
|
|
||||||
params=NotifyFaast.urlencode(params),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -200,16 +188,19 @@ class NotifyFaast(NotifyBase):
|
||||||
us to re-instantiate this object.
|
us to re-instantiate this object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# parse_url already handles getting the `user` and `password` fields
|
||||||
|
# populated.
|
||||||
results = NotifyBase.parse_url(url, verify_host=False)
|
results = NotifyBase.parse_url(url, verify_host=False)
|
||||||
if not results:
|
if not results:
|
||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# Store our authtoken using the host
|
# Allow over-ride
|
||||||
results['authtoken'] = NotifyFaast.unquote(results['host'])
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
|
results['token'] = NotifyChantify.unquote(results['qsd']['token'])
|
||||||
|
|
||||||
# Include image with our post
|
else:
|
||||||
results['include_image'] = \
|
results['token'] = NotifyChantify.unquote(results['host'])
|
||||||
parse_bool(results['qsd'].get('image', True))
|
|
||||||
|
|
||||||
return results
|
return results
|
|
@ -45,7 +45,7 @@ from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
from ..common import NotifyFormat, NotifyType
|
from ..common import NotifyFormat, NotifyType
|
||||||
from ..conversion import convert_between
|
from ..conversion import convert_between
|
||||||
from ..utils import is_email, parse_emails
|
from ..utils import is_email, parse_emails, is_hostname
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
from ..logger import logger
|
from ..logger import logger
|
||||||
|
|
||||||
|
@ -566,13 +566,21 @@ class NotifyEmail(NotifyBase):
|
||||||
# Apply any defaults based on certain known configurations
|
# Apply any defaults based on certain known configurations
|
||||||
self.NotifyEmailDefaults(secure_mode=secure_mode, **kwargs)
|
self.NotifyEmailDefaults(secure_mode=secure_mode, **kwargs)
|
||||||
|
|
||||||
if self.user and self.host:
|
if self.user:
|
||||||
|
if self.host:
|
||||||
# Prepare the bases of our email
|
# Prepare the bases of our email
|
||||||
self.from_addr = [self.app_id, '{}@{}'.format(
|
self.from_addr = [self.app_id, '{}@{}'.format(
|
||||||
re.split(r'[\s@]+', self.user)[0],
|
re.split(r'[\s@]+', self.user)[0],
|
||||||
self.host,
|
self.host,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = is_email(self.user)
|
||||||
|
if result:
|
||||||
|
# Prepare the bases of our email and include domain
|
||||||
|
self.host = result['domain']
|
||||||
|
self.from_addr = [self.app_id, self.user]
|
||||||
|
|
||||||
if from_addr:
|
if from_addr:
|
||||||
result = is_email(from_addr)
|
result = is_email(from_addr)
|
||||||
if result:
|
if result:
|
||||||
|
@ -1037,11 +1045,25 @@ class NotifyEmail(NotifyBase):
|
||||||
us to re-instantiate this object.
|
us to re-instantiate this object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
results = NotifyBase.parse_url(url)
|
results = NotifyBase.parse_url(url, verify_host=False)
|
||||||
if not results:
|
if not results:
|
||||||
# We're done early as we couldn't load the results
|
# We're done early as we couldn't load the results
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
# Prepare our target lists
|
||||||
|
results['targets'] = []
|
||||||
|
|
||||||
|
if not is_hostname(results['host'], ipv4=False, ipv6=False,
|
||||||
|
underscore=False):
|
||||||
|
|
||||||
|
if is_email(NotifyEmail.unquote(results['host'])):
|
||||||
|
# Don't lose defined email addresses
|
||||||
|
results['targets'].append(NotifyEmail.unquote(results['host']))
|
||||||
|
|
||||||
|
# Detect if we have a valid hostname or not; be sure to reset it's
|
||||||
|
# value if invalid; we'll attempt to figure this out later on
|
||||||
|
results['host'] = ''
|
||||||
|
|
||||||
# The From address is a must; either through the use of templates
|
# The From address is a must; either through the use of templates
|
||||||
# from= entry and/or merging the user and hostname together, this
|
# from= entry and/or merging the user and hostname together, this
|
||||||
# must be calculated or parse_url will fail.
|
# must be calculated or parse_url will fail.
|
||||||
|
@ -1052,7 +1074,7 @@ class NotifyEmail(NotifyBase):
|
||||||
|
|
||||||
# Get our potential email targets; if none our found we'll just
|
# Get our potential email targets; if none our found we'll just
|
||||||
# add one to ourselves
|
# add one to ourselves
|
||||||
results['targets'] = NotifyEmail.split_path(results['fullpath'])
|
results['targets'] += NotifyEmail.split_path(results['fullpath'])
|
||||||
|
|
||||||
# Attempt to detect 'to' email address
|
# Attempt to detect 'to' email address
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2024, Chris Caron <lead2gold@gmail.com>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
# Feishu
|
||||||
|
# 1. Visit https://open.feishu.cn
|
||||||
|
|
||||||
|
# Custom Bot Setup
|
||||||
|
# https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
|
||||||
|
#
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..utils import validate_regex
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyFeishu(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Feishu Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = _('Feishu')
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://open.feishu.cn/'
|
||||||
|
|
||||||
|
# The default secure protocol
|
||||||
|
secure_protocol = 'feishu'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_feishu'
|
||||||
|
|
||||||
|
# Notification URL
|
||||||
|
notify_url = 'https://open.feishu.cn/open-apis/bot/v2/hook/{token}/'
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{token}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# The title is not used
|
||||||
|
title_maxlen = 0
|
||||||
|
|
||||||
|
# Limit is documented to be 20K message sizes. This number safely
|
||||||
|
# allows padding around that size.
|
||||||
|
body_maxlen = 19985
|
||||||
|
|
||||||
|
# Define our tokens; these are the minimum tokens required required to
|
||||||
|
# be passed into this function (as arguments). The syntax appends any
|
||||||
|
# previously defined in the base package and builds onto them
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'token': {
|
||||||
|
'name': _('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
'regex': (r'^[A-Z0-9_-]+$', 'i'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'token': {
|
||||||
|
'alias_of': 'token',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, token, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Feishu Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.token = validate_regex(
|
||||||
|
token, *self.template_tokens['token']['regex'])
|
||||||
|
if not self.token:
|
||||||
|
msg = 'The Feishu token specified ({}) is invalid.'\
|
||||||
|
.format(token)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Send our notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
# prepare our headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Our Message
|
||||||
|
payload = {
|
||||||
|
'msg_type': 'text',
|
||||||
|
"content": {
|
||||||
|
"text": body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug('Feishu GET URL: %s (cert_verify=%r)' % (
|
||||||
|
self.notify_url, self.verify_certificate))
|
||||||
|
self.logger.debug('Feishu Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url.format(token=self.token),
|
||||||
|
data=dumps(payload).encode('utf-8'),
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sample Responses
|
||||||
|
#
|
||||||
|
# Valid:
|
||||||
|
# {
|
||||||
|
# "code": 0,
|
||||||
|
# "data": {},
|
||||||
|
# "msg": "success"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Invalid (non 200 response):
|
||||||
|
# {
|
||||||
|
# "code": 9499,
|
||||||
|
# "msg": "Bad Request",
|
||||||
|
# "data": {}
|
||||||
|
# }
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyFeishu.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Feishu notification: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Feishu notification.')
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Feishu '
|
||||||
|
'notification.')
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Prepare our parameters
|
||||||
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
|
|
||||||
|
return '{schema}://{token}/?{params}'.format(
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
token=self.pprint(self.token, privacy, safe=''),
|
||||||
|
params=NotifyFeishu.urlencode(params),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_url(url):
|
||||||
|
"""
|
||||||
|
Parses the URL and returns enough arguments that can allow
|
||||||
|
us to re-instantiate this object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# parse_url already handles getting the `user` and `password` fields
|
||||||
|
# populated.
|
||||||
|
results = NotifyBase.parse_url(url, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# Allow over-ride
|
||||||
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
|
results['token'] = NotifyFeishu.unquote(results['qsd']['token'])
|
||||||
|
|
||||||
|
else:
|
||||||
|
results['token'] = NotifyFeishu.unquote(results['host'])
|
||||||
|
|
||||||
|
return results
|
|
@ -0,0 +1,204 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2024, Chris Caron <lead2gold@gmail.com>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
# Free Mobile
|
||||||
|
# 1. Visit https://mobile.free.fr/
|
||||||
|
|
||||||
|
# the URL will look something like this:
|
||||||
|
# https://smsapi.free-mobile.fr/sendmsg
|
||||||
|
#
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyFreeMobile(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Free-Mobile Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = _('Free-Mobile')
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://mobile.free.fr/'
|
||||||
|
|
||||||
|
# The default secure protocol
|
||||||
|
secure_protocol = 'freemobile'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_freemobile'
|
||||||
|
|
||||||
|
# Plain Text Notification URL
|
||||||
|
notify_url = 'https://smsapi.free-mobile.fr/sendmsg'
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{user}@{password}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# The title is not used
|
||||||
|
title_maxlen = 0
|
||||||
|
|
||||||
|
# SMS Messages are restricted in size
|
||||||
|
body_maxlen = 160
|
||||||
|
|
||||||
|
# Define our tokens; these are the minimum tokens required required to
|
||||||
|
# be passed into this function (as arguments). The syntax appends any
|
||||||
|
# previously defined in the base package and builds onto them
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'user': {
|
||||||
|
'name': _('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
'password': {
|
||||||
|
'name': _('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Free Mobile Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
if not (self.user and self.password):
|
||||||
|
msg = 'A FreeMobile user and password ' \
|
||||||
|
'combination was not provided.'
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Prepare our parameters
|
||||||
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
|
|
||||||
|
return '{schema}://{user}@{password}/?{params}'.format(
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
user=self.user,
|
||||||
|
password=self.pprint(self.password, privacy, safe=''),
|
||||||
|
params=NotifyFreeMobile.urlencode(params),
|
||||||
|
)
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Send our notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
# prepare our headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare our payload
|
||||||
|
payload = {
|
||||||
|
'user': self.user,
|
||||||
|
'pass': self.password,
|
||||||
|
'msg': body
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug('Free Mobile GET URL: %s (cert_verify=%r)' % (
|
||||||
|
self.notify_url, self.verify_certificate))
|
||||||
|
self.logger.debug('Free Mobile Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url,
|
||||||
|
data=dumps(payload).encode('utf-8'),
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyFreeMobile.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Free Mobile notification: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Free Mobile notification.')
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Free Mobile '
|
||||||
|
'notification.')
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_url(url):
|
||||||
|
"""
|
||||||
|
Parses the URL and returns enough arguments that can allow
|
||||||
|
us to re-instantiate this object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# parse_url already handles getting the `user` and `password` fields
|
||||||
|
# populated.
|
||||||
|
results = NotifyBase.parse_url(url, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# The hostname can act as the password if specified and a password
|
||||||
|
# was otherwise not (specified):
|
||||||
|
if not results.get('password'):
|
||||||
|
results['password'] = NotifyFreeMobile.unquote(results['host'])
|
||||||
|
|
||||||
|
return results
|
|
@ -89,7 +89,7 @@ class NotifyMQTT(NotifyBase):
|
||||||
|
|
||||||
requirements = {
|
requirements = {
|
||||||
# Define our required packaging in order to work
|
# Define our required packaging in order to work
|
||||||
'packages_required': 'paho-mqtt'
|
'packages_required': 'paho-mqtt < 2.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
# The default descriptive name associated with the Notification
|
# The default descriptive name associated with the Notification
|
||||||
|
|
|
@ -698,7 +698,7 @@ class NotifyNtfy(NotifyBase):
|
||||||
"""
|
"""
|
||||||
Returns the number of targets associated with this notification
|
Returns the number of targets associated with this notification
|
||||||
"""
|
"""
|
||||||
return len(self.topics)
|
return 1 if not self.topics else len(self.topics)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
|
|
|
@ -59,6 +59,9 @@ class RocketChatAuthMode:
|
||||||
# providing a webhook
|
# providing a webhook
|
||||||
WEBHOOK = "webhook"
|
WEBHOOK = "webhook"
|
||||||
|
|
||||||
|
# Support token submission
|
||||||
|
TOKEN = "token"
|
||||||
|
|
||||||
# Providing a username and password (default)
|
# Providing a username and password (default)
|
||||||
BASIC = "basic"
|
BASIC = "basic"
|
||||||
|
|
||||||
|
@ -66,6 +69,7 @@ class RocketChatAuthMode:
|
||||||
# Define our authentication modes
|
# Define our authentication modes
|
||||||
ROCKETCHAT_AUTH_MODES = (
|
ROCKETCHAT_AUTH_MODES = (
|
||||||
RocketChatAuthMode.WEBHOOK,
|
RocketChatAuthMode.WEBHOOK,
|
||||||
|
RocketChatAuthMode.TOKEN,
|
||||||
RocketChatAuthMode.BASIC,
|
RocketChatAuthMode.BASIC,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,6 +111,8 @@ class NotifyRocketChat(NotifyBase):
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}/{targets}',
|
'{schema}://{user}:{password}@{host}/{targets}',
|
||||||
|
'{schema}://{user}:{token}@{host}:{port}/{targets}',
|
||||||
|
'{schema}://{user}:{token}@{host}/{targets}',
|
||||||
'{schema}://{webhook}@{host}',
|
'{schema}://{webhook}@{host}',
|
||||||
'{schema}://{webhook}@{host}:{port}',
|
'{schema}://{webhook}@{host}:{port}',
|
||||||
'{schema}://{webhook}@{host}/{targets}',
|
'{schema}://{webhook}@{host}/{targets}',
|
||||||
|
@ -135,6 +141,11 @@ class NotifyRocketChat(NotifyBase):
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'private': True,
|
'private': True,
|
||||||
},
|
},
|
||||||
|
'token': {
|
||||||
|
'name': _('API Token'),
|
||||||
|
'map_to': 'password',
|
||||||
|
'private': True,
|
||||||
|
},
|
||||||
'webhook': {
|
'webhook': {
|
||||||
'name': _('Webhook'),
|
'name': _('Webhook'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
@ -230,13 +241,20 @@ class NotifyRocketChat(NotifyBase):
|
||||||
if self.webhook is not None:
|
if self.webhook is not None:
|
||||||
# Just a username was specified, we treat this as a webhook
|
# Just a username was specified, we treat this as a webhook
|
||||||
self.mode = RocketChatAuthMode.WEBHOOK
|
self.mode = RocketChatAuthMode.WEBHOOK
|
||||||
|
elif self.password and len(self.password) > 32:
|
||||||
|
self.mode = RocketChatAuthMode.TOKEN
|
||||||
else:
|
else:
|
||||||
self.mode = RocketChatAuthMode.BASIC
|
self.mode = RocketChatAuthMode.BASIC
|
||||||
|
|
||||||
if self.mode == RocketChatAuthMode.BASIC \
|
self.logger.debug(
|
||||||
|
"Auto-Detected Rocketchat Auth Mode: %s", self.mode)
|
||||||
|
|
||||||
|
if self.mode in (RocketChatAuthMode.BASIC, RocketChatAuthMode.TOKEN) \
|
||||||
and not (self.user and self.password):
|
and not (self.user and self.password):
|
||||||
# Username & Password is required for Rocket Chat to work
|
# Username & Password is required for Rocket Chat to work
|
||||||
msg = 'No Rocket.Chat user/pass combo was specified.'
|
msg = 'No Rocket.Chat {} was specified.'.format(
|
||||||
|
'user/pass combo' if self.mode == RocketChatAuthMode.BASIC else
|
||||||
|
'user/apikey')
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
@ -245,6 +263,13 @@ class NotifyRocketChat(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if self.mode == RocketChatAuthMode.TOKEN:
|
||||||
|
# Set our headers for further communication
|
||||||
|
self.headers.update({
|
||||||
|
'X-User-Id': self.user,
|
||||||
|
'X-Auth-Token': self.password,
|
||||||
|
})
|
||||||
|
|
||||||
# Validate recipients and drop bad ones:
|
# Validate recipients and drop bad ones:
|
||||||
for recipient in parse_list(targets):
|
for recipient in parse_list(targets):
|
||||||
result = IS_CHANNEL.match(recipient)
|
result = IS_CHANNEL.match(recipient)
|
||||||
|
@ -309,12 +334,13 @@ class NotifyRocketChat(NotifyBase):
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
# Determine Authentication
|
# Determine Authentication
|
||||||
if self.mode == RocketChatAuthMode.BASIC:
|
if self.mode in (RocketChatAuthMode.BASIC, RocketChatAuthMode.TOKEN):
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=NotifyRocketChat.quote(self.user, safe=''),
|
user=NotifyRocketChat.quote(self.user, safe=''),
|
||||||
password=self.pprint(
|
password=self.pprint(
|
||||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
auth = '{user}{webhook}@'.format(
|
auth = '{user}{webhook}@'.format(
|
||||||
user='{}:'.format(NotifyRocketChat.quote(self.user, safe=''))
|
user='{}:'.format(NotifyRocketChat.quote(self.user, safe=''))
|
||||||
|
@ -359,7 +385,10 @@ class NotifyRocketChat(NotifyBase):
|
||||||
# Call the _send_ function applicable to whatever mode we're in
|
# Call the _send_ function applicable to whatever mode we're in
|
||||||
# - calls _send_webhook_notification if the mode variable is set
|
# - calls _send_webhook_notification if the mode variable is set
|
||||||
# - calls _send_basic_notification if the mode variable is not set
|
# - calls _send_basic_notification if the mode variable is not set
|
||||||
return getattr(self, '_send_{}_notification'.format(self.mode))(
|
return getattr(self, '_send_{}_notification'.format(
|
||||||
|
RocketChatAuthMode.WEBHOOK
|
||||||
|
if self.mode == RocketChatAuthMode.WEBHOOK
|
||||||
|
else RocketChatAuthMode.BASIC))(
|
||||||
body=body, title=title, notify_type=notify_type, **kwargs)
|
body=body, title=title, notify_type=notify_type, **kwargs)
|
||||||
|
|
||||||
def _send_webhook_notification(self, body, title='',
|
def _send_webhook_notification(self, body, title='',
|
||||||
|
@ -412,7 +441,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
"""
|
"""
|
||||||
# Track whether we authenticated okay
|
# Track whether we authenticated okay
|
||||||
|
|
||||||
if not self.login():
|
if self.mode == RocketChatAuthMode.BASIC and not self.login():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# prepare JSON Object
|
# prepare JSON Object
|
||||||
|
@ -432,9 +461,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
channel = channels.pop(0)
|
channel = channels.pop(0)
|
||||||
payload['channel'] = channel
|
payload['channel'] = channel
|
||||||
|
|
||||||
if not self._send(
|
if not self._send(payload, notify_type=notify_type, **kwargs):
|
||||||
payload, notify_type=notify_type, headers=self.headers,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
# toggle flag
|
# toggle flag
|
||||||
has_error = True
|
has_error = True
|
||||||
|
@ -447,13 +474,12 @@ class NotifyRocketChat(NotifyBase):
|
||||||
room = rooms.pop(0)
|
room = rooms.pop(0)
|
||||||
payload['roomId'] = room
|
payload['roomId'] = room
|
||||||
|
|
||||||
if not self._send(
|
if not self._send(payload, notify_type=notify_type, **kwargs):
|
||||||
payload, notify_type=notify_type, headers=self.headers,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
# toggle flag
|
# toggle flag
|
||||||
has_error = True
|
has_error = True
|
||||||
|
|
||||||
|
if self.mode == RocketChatAuthMode.BASIC:
|
||||||
# logout
|
# logout
|
||||||
self.logout()
|
self.logout()
|
||||||
|
|
||||||
|
@ -476,7 +502,7 @@ class NotifyRocketChat(NotifyBase):
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
def _send(self, payload, notify_type, path='api/v1/chat.postMessage',
|
def _send(self, payload, notify_type, path='api/v1/chat.postMessage',
|
||||||
headers={}, **kwargs):
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Notify Rocket.Chat Notification
|
Perform Notify Rocket.Chat Notification
|
||||||
"""
|
"""
|
||||||
|
@ -487,6 +513,9 @@ class NotifyRocketChat(NotifyBase):
|
||||||
api_url, self.verify_certificate))
|
api_url, self.verify_certificate))
|
||||||
self.logger.debug('Rocket.Chat Payload: %s' % str(payload))
|
self.logger.debug('Rocket.Chat Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Copy our existing headers
|
||||||
|
headers = self.headers.copy()
|
||||||
|
|
||||||
# Apply minimum headers
|
# Apply minimum headers
|
||||||
headers.update({
|
headers.update({
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
|
|
|
@ -82,6 +82,34 @@ IS_CHAT_ID_RE = re.compile(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramMarkdownVersion:
|
||||||
|
"""
|
||||||
|
Telegram Markdown Version
|
||||||
|
"""
|
||||||
|
# Classic (Original Telegram Markdown)
|
||||||
|
ONE = 'MARKDOWN'
|
||||||
|
|
||||||
|
# Supports strikethrough and many other items
|
||||||
|
TWO = 'MarkdownV2'
|
||||||
|
|
||||||
|
|
||||||
|
TELEGRAM_MARKDOWN_VERSION_MAP = {
|
||||||
|
# v1
|
||||||
|
"v1": TelegramMarkdownVersion.ONE,
|
||||||
|
"1": TelegramMarkdownVersion.ONE,
|
||||||
|
# v2
|
||||||
|
"v2": TelegramMarkdownVersion.TWO,
|
||||||
|
"2": TelegramMarkdownVersion.TWO,
|
||||||
|
"default": TelegramMarkdownVersion.TWO,
|
||||||
|
}
|
||||||
|
|
||||||
|
TELEGRAM_MARKDOWN_VERSIONS = {
|
||||||
|
# Note: This also acts as a reverse lookup mapping
|
||||||
|
TelegramMarkdownVersion.ONE: 'v1',
|
||||||
|
TelegramMarkdownVersion.TWO: 'v2',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TelegramContentPlacement:
|
class TelegramContentPlacement:
|
||||||
"""
|
"""
|
||||||
The Telegram Content Placement
|
The Telegram Content Placement
|
||||||
|
@ -333,6 +361,12 @@ class NotifyTelegram(NotifyBase):
|
||||||
'name': _('Topic Thread ID'),
|
'name': _('Topic Thread ID'),
|
||||||
'type': 'int',
|
'type': 'int',
|
||||||
},
|
},
|
||||||
|
'mdv': {
|
||||||
|
'name': _('Markdown Version'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': ('v1', 'v2'),
|
||||||
|
'default': 'v2',
|
||||||
|
},
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
|
@ -346,7 +380,7 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
def __init__(self, bot_token, targets, detect_owner=True,
|
def __init__(self, bot_token, targets, detect_owner=True,
|
||||||
include_image=False, silent=None, preview=None, topic=None,
|
include_image=False, silent=None, preview=None, topic=None,
|
||||||
content=None, **kwargs):
|
content=None, mdv=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Telegram Object
|
Initialize Telegram Object
|
||||||
"""
|
"""
|
||||||
|
@ -361,6 +395,17 @@ class NotifyTelegram(NotifyBase):
|
||||||
self.logger.warning(err)
|
self.logger.warning(err)
|
||||||
raise TypeError(err)
|
raise TypeError(err)
|
||||||
|
|
||||||
|
# Get our Markdown Version
|
||||||
|
self.markdown_ver = \
|
||||||
|
TELEGRAM_MARKDOWN_VERSION_MAP[NotifyTelegram.
|
||||||
|
template_args['mdv']['default']] \
|
||||||
|
if mdv is None else \
|
||||||
|
next((
|
||||||
|
v for k, v in TELEGRAM_MARKDOWN_VERSION_MAP.items()
|
||||||
|
if str(mdv).lower().startswith(k)),
|
||||||
|
TELEGRAM_MARKDOWN_VERSION_MAP[NotifyTelegram.
|
||||||
|
template_args['mdv']['default']])
|
||||||
|
|
||||||
# Define whether or not we should make audible alarms
|
# Define whether or not we should make audible alarms
|
||||||
self.silent = self.template_args['silent']['default'] \
|
self.silent = self.template_args['silent']['default'] \
|
||||||
if silent is None else bool(silent)
|
if silent is None else bool(silent)
|
||||||
|
@ -717,8 +762,7 @@ class NotifyTelegram(NotifyBase):
|
||||||
|
|
||||||
# Prepare Message Body
|
# Prepare Message Body
|
||||||
if self.notify_format == NotifyFormat.MARKDOWN:
|
if self.notify_format == NotifyFormat.MARKDOWN:
|
||||||
_payload['parse_mode'] = 'MARKDOWN'
|
_payload['parse_mode'] = self.markdown_ver
|
||||||
|
|
||||||
_payload['text'] = body
|
_payload['text'] = body
|
||||||
|
|
||||||
else: # HTML
|
else: # HTML
|
||||||
|
@ -886,6 +930,7 @@ class NotifyTelegram(NotifyBase):
|
||||||
'silent': 'yes' if self.silent else 'no',
|
'silent': 'yes' if self.silent else 'no',
|
||||||
'preview': 'yes' if self.preview else 'no',
|
'preview': 'yes' if self.preview else 'no',
|
||||||
'content': self.content,
|
'content': self.content,
|
||||||
|
'mdv': TELEGRAM_MARKDOWN_VERSIONS[self.markdown_ver],
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.topic:
|
if self.topic:
|
||||||
|
@ -990,6 +1035,10 @@ class NotifyTelegram(NotifyBase):
|
||||||
# Store our bot token
|
# Store our bot token
|
||||||
results['bot_token'] = bot_token
|
results['bot_token'] = bot_token
|
||||||
|
|
||||||
|
# Support Markdown Version
|
||||||
|
if 'mdv' in results['qsd'] and len(results['qsd']['mdv']):
|
||||||
|
results['mdv'] = results['qsd']['mdv']
|
||||||
|
|
||||||
# Support Thread Topic
|
# Support Thread Topic
|
||||||
if 'topic' in results['qsd'] and len(results['qsd']['topic']):
|
if 'topic' in results['qsd'] and len(results['qsd']['topic']):
|
||||||
results['topic'] = results['qsd']['topic']
|
results['topic'] = results['qsd']['topic']
|
||||||
|
|
|
@ -163,6 +163,9 @@ class NotifyZulip(NotifyBase):
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
|
'token': {
|
||||||
|
'alias_of': 'token',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# The default hostname to append to a defined organization
|
# The default hostname to append to a defined organization
|
||||||
|
@ -377,21 +380,24 @@ class NotifyZulip(NotifyBase):
|
||||||
# The botname
|
# The botname
|
||||||
results['botname'] = NotifyZulip.unquote(results['user'])
|
results['botname'] = NotifyZulip.unquote(results['user'])
|
||||||
|
|
||||||
# The first token is stored in the hostname
|
# The organization is stored in the hostname
|
||||||
results['organization'] = NotifyZulip.unquote(results['host'])
|
results['organization'] = NotifyZulip.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Store our targets
|
||||||
try:
|
results['targets'] = NotifyZulip.split_path(results['fullpath'])
|
||||||
results['token'] = \
|
|
||||||
NotifyZulip.split_path(results['fullpath'])[0]
|
|
||||||
|
|
||||||
except IndexError:
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
|
# Store our token if specified
|
||||||
|
results['token'] = NotifyZulip.unquote(results['qsd']['token'])
|
||||||
|
|
||||||
|
elif results['targets']:
|
||||||
|
# First item is the token
|
||||||
|
results['token'] = results['targets'].pop(0)
|
||||||
|
|
||||||
|
else:
|
||||||
# no token
|
# no token
|
||||||
results['token'] = None
|
results['token'] = None
|
||||||
|
|
||||||
# Get unquoted entries
|
|
||||||
results['targets'] = NotifyZulip.split_path(results['fullpath'])[1:]
|
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support rooms this way too
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
alembic==1.13.1
|
alembic==1.13.1
|
||||||
aniso8601==9.0.1
|
aniso8601==9.0.1
|
||||||
argparse==1.4.0
|
argparse==1.4.0
|
||||||
apprise==1.7.4
|
apprise==1.7.6
|
||||||
apscheduler<=3.10.4
|
apscheduler<=3.10.4
|
||||||
attrs==23.2.0
|
attrs==23.2.0
|
||||||
blinker==1.7.0
|
blinker==1.7.0
|
||||||
|
|
Loading…
Reference in New Issue