mirror of https://github.com/caronc/apprise
Added Lark Support (#1361)
parent
1d5a304638
commit
62c762cb6b
|
@ -79,6 +79,7 @@ The table below identifies the services this tool supports and some example serv
|
|||
| [KODI](https://github.com/caronc/apprise/wiki/Notify_kodi) | kodi:// or kodis:// | (TCP) 8080 or 443 | kodi://hostname<br />kodi://user@hostname<br />kodi://user:password@hostname:port
|
||||
| [Kumulos](https://github.com/caronc/apprise/wiki/Notify_kumulos) | kumulos:// | (TCP) 443 | kumulos://apikey/serverkey
|
||||
| [LaMetric Time](https://github.com/caronc/apprise/wiki/Notify_lametric) | lametric:// | (TCP) 443 | lametric://apikey@device_ipaddr<br/>lametric://apikey@hostname:port<br/>lametric://client_id@client_secret
|
||||
| [Lark](https://github.com/caronc/apprise/wiki/Notify_lark) | lark:// | (TCP) 443 | lark://BotToken
|
||||
| [Line](https://github.com/caronc/apprise/wiki/Notify_line) | line:// | (TCP) 443 | line://Token@User<br/>line://Token/User1/User2/UserN
|
||||
| [Mailgun](https://github.com/caronc/apprise/wiki/Notify_mailgun) | mailgun:// | (TCP) 443 | mailgun://user@hostname/apikey<br />mailgun://user@hostname/apikey/email<br />mailgun://user@hostname/apikey/email1/email2/emailN<br />mailgun://user@hostname/apikey/?name="From%20User"
|
||||
| [Mastodon](https://github.com/caronc/apprise/wiki/Notify_mastodon) | mastodon:// or mastodons://| (TCP) 80 or 443 | mastodon://access_key@hostname<br />mastodon://access_key@hostname/@user<br />mastodon://access_key@hostname/@user1/@user2/@userN
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# BSD 2-Clause License
|
||||
#
|
||||
# Apprise - Push Notification Library.
|
||||
# Copyright (c) 2025, 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.
|
||||
|
||||
# Details at:
|
||||
# https://open.larksuite.com/document/client-docs/bot-v3/add-bot
|
||||
|
||||
import re
|
||||
import requests
|
||||
import json
|
||||
|
||||
from ..utils.parse import validate_regex
|
||||
from ..url import PrivacyMode
|
||||
from .base import NotifyBase
|
||||
from ..locale import gettext_lazy as _
|
||||
from ..common import NotifyType
|
||||
|
||||
|
||||
class NotifyLark(NotifyBase):
|
||||
"""
|
||||
A wrapper for Lark (Feishu) Notifications via Webhook
|
||||
"""
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = _('Lark (Feishu)')
|
||||
|
||||
service_url = 'https://open.larksuite.com/'
|
||||
|
||||
# The default protocol
|
||||
secure_protocol = 'lark'
|
||||
|
||||
# A URL that takes you to the setup/help of the specific protocol
|
||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_lark'
|
||||
|
||||
# This is the static part of the webhook URL; only the token varies.
|
||||
notify_url = 'https://open.larksuite.com/open-apis/bot/v2/hook/'
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://{token}',
|
||||
)
|
||||
|
||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||
'token': {
|
||||
'name': _('Bot Token'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
'required': True,
|
||||
'regex': (r'^[a-z0-9-]+$', 'i'),
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, token, **kwargs):
|
||||
"""
|
||||
Initialize Email Object
|
||||
|
||||
The smtp_host and secure_mode can be automatically detected depending
|
||||
on how the URL was built
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# The token associated with the account
|
||||
self.token = validate_regex(
|
||||
token, *self.template_tokens['token']['regex'])
|
||||
if not self.token:
|
||||
msg = 'The Lark Bot Token token specified ({}) is invalid.'\
|
||||
.format(token)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
self.webhook_url = f'{self.notify_url}{self.token}'
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||
return '{schema}://{token}/?{params}'.format(
|
||||
schema=self.secure_protocol,
|
||||
token=self.pprint(self.token, privacy, mode=PrivacyMode.Secret),
|
||||
params=NotifyLark.urlencode(params),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another similar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
self.throttle()
|
||||
|
||||
payload = {
|
||||
'msg_type': 'text',
|
||||
'content': {
|
||||
'text': f'{title}\n{body}' if title else body
|
||||
}
|
||||
}
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
try:
|
||||
r = requests.post(
|
||||
self.webhook_url,
|
||||
headers=headers,
|
||||
data=json.dumps(payload),
|
||||
verify=self.verify_certificate,
|
||||
timeout=self.request_timeout,
|
||||
)
|
||||
if r.status_code != requests.codes.ok:
|
||||
self.logger.warning(
|
||||
'Lark notification failed: %d - %s',
|
||||
r.status_code, r.text)
|
||||
return False
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(f'Lark Exception: {e}')
|
||||
return False
|
||||
|
||||
self.logger.info('Lark notification sent successfully.')
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another similar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
Parses the URL and returns enough arguments that can allow
|
||||
us to re-instantiate this object.
|
||||
|
||||
"""
|
||||
results = NotifyBase.parse_url(url, verify_host=False)
|
||||
if not results:
|
||||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Set our token if found as an argument
|
||||
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||
results['token'] = NotifyLark.unquote(results['qsd']['token'])
|
||||
|
||||
else:
|
||||
# Fall back to hose (if defined here)
|
||||
results['token'] = NotifyLark.unquote(results['host'])
|
||||
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def parse_native_url(url):
|
||||
"""
|
||||
Support https://open.larksuite.com/open-apis/bot/v2/hook//WEBHOOK_TOKEN
|
||||
"""
|
||||
match = re.match(
|
||||
r'^https://open\.larksuite\.com/open-apis/bot/v2/hook/([\w-]+)$',
|
||||
url, re.I)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
return NotifyLark.parse_url('{schema}://{token}'.format(
|
||||
schema=NotifyLark.secure_protocol, token=match.group(1)))
|
|
@ -42,7 +42,7 @@ it easy to access:
|
|||
Africas Talking, Apprise API, APRS, AWS SES, AWS SNS, Bark, BlueSky, Burst SMS,
|
||||
BulkSMS, BulkVS, Chanify, Clickatell, ClickSend, DAPNET, DingTalk, Discord, E-Mail, Emby,
|
||||
FCM, Feishu, Flock, Free Mobile, Google Chat, Gotify, Growl, Guilded, Home
|
||||
Assistant, httpSMS, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, Line,
|
||||
Assistant, httpSMS, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, Lark, Line,
|
||||
MacOSX, Mailgun, Mastodon, Mattermost, Matrix, MessageBird, Microsoft
|
||||
Windows, Microsoft Teams, Misskey, MQTT, MSG91, MyAndroid, Nexmo, Nextcloud,
|
||||
NextcloudTalk, Notica, Notifiarr, Notifico, ntfy, Office365, OneSignal,
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import requests
|
||||
from apprise.plugins.lark import NotifyLark
|
||||
from helpers import AppriseURLTester
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
# Our Testing URLs
|
||||
apprise_url_tests = (
|
||||
('lark://', {
|
||||
# Teams Token missing
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lark://:@/', {
|
||||
# We don't have strict host checking on for lark, so this URL
|
||||
# actually becomes parseable and :@ becomes a hostname.
|
||||
# The below errors because a second token wasn't found
|
||||
'instance': TypeError,
|
||||
}),
|
||||
('lark://{}'.format('abcd-1234'), {
|
||||
# token provided - we're good
|
||||
'instance': NotifyLark,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lark://****/',
|
||||
}),
|
||||
('lark://{}'.format('abcd-1234'), {
|
||||
# token provided - we're good
|
||||
'instance': NotifyLark,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lark://****/',
|
||||
}),
|
||||
('lark://?token={}'.format('abcd-1234'), {
|
||||
# token provided - we're good
|
||||
'instance': NotifyLark,
|
||||
|
||||
# Our expected url(privacy=True) startswith() response:
|
||||
'privacy_url': 'lark://****/',
|
||||
}),
|
||||
# Support Native URLs with arguments
|
||||
('https://open.larksuite.com/open-apis/bot/v2/hook/{}'.format(
|
||||
'abcd-1234'), {
|
||||
# token provided - we're good
|
||||
'instance': NotifyLark,
|
||||
}),
|
||||
('lark://{}'.format('abcd-1234'), {
|
||||
'instance': NotifyLark,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('lark://{}'.format('abcd-1234'), {
|
||||
'instance': NotifyLark,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('lark://{}'.format('a' * 80), {
|
||||
'instance': NotifyLark,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
def test_plugin_lark_urls():
|
||||
"""
|
||||
NotifyLark() Apprise URLs
|
||||
|
||||
"""
|
||||
|
||||
# Run our general tests
|
||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
Loading…
Reference in New Issue