Misskey support added (#831)

This commit is contained in:
Chris Caron 2023-02-18 21:32:44 -05:00 committed by GitHub
parent ff87f362cc
commit 3bb8aedd87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 429 additions and 6 deletions

View File

@ -36,6 +36,7 @@ Matrix
Mattermost
MessageBird
Microsoft
Misskey
MQTT
MSG91
MSTeams

View File

@ -83,6 +83,7 @@ The table below identifies the services this tool supports and some example serv
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 80 or 443 | matrix://hostname<br />matrix://user@hostname<br />matrixs://user:pass@hostname:port/#room_alias<br />matrixs://user:pass@hostname:port/!room_id<br />matrixs://user:pass@hostname:port/#room_alias/!room_id/#room2<br />matrixs://token@hostname:port/?webhook=matrix<br />matrix://user:token@hostname/?webhook=slack&format=markdown
| [Mattermost](https://github.com/caronc/apprise/wiki/Notify_mattermost) | mmost:// or mmosts:// | (TCP) 8065 | mmost://hostname/authkey<br />mmost://hostname:80/authkey<br />mmost://user@hostname:80/authkey<br />mmost://hostname/authkey?channel=channel<br />mmosts://hostname/authkey<br />mmosts://user@hostname/authkey<br />
| [Microsoft Teams](https://github.com/caronc/apprise/wiki/Notify_msteams) | msteams:// | (TCP) 443 | msteams://TokenA/TokenB/TokenC/
| [Misskey](https://github.com/caronc/apprise/wiki/Notify_misskey) | misskey:// or misskeys://| (TCP) 80 or 443 | misskey://access_token@hostname
| [MQTT](https://github.com/caronc/apprise/wiki/Notify_mqtt) | mqtt:// or mqtts:// | (TCP) 1883 or 8883 | mqtt://hostname/topic<br />mqtt://user@hostname/topic<br />mqtts://user:pass@hostname:9883/topic
| [Nextcloud](https://github.com/caronc/apprise/wiki/Notify_nextcloud) | ncloud:// or nclouds:// | (TCP) 80 or 443 | ncloud://adminuser:pass@host/User<br/>nclouds://adminuser:pass@host/User1/User2/UserN
| [NextcloudTalk](https://github.com/caronc/apprise/wiki/Notify_nextcloudtalk) | nctalk:// or nctalks:// | (TCP) 80 or 443 | nctalk://user:pass@host/RoomId<br/>nctalks://user:pass@host/RoomId1/RoomId2/RoomIdN

View File

@ -0,0 +1,310 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2023, 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.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# 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.
# 1. visit https://misskey-hub.net/ and see what it's all about if you want.
# Choose a service you want to create an account on from here:
# https://misskey-hub.net/en/instances.html
#
# - For this plugin, I tested using https://misskey.sda1.net and created an
# account.
#
# 2. Generate an API Key:
# - Settings > API > Generate Key
# - Name it whatever you want
# - Assign it 'AT LEAST':
# a. Compose or delete chat messages
# b. Compose or delete notes
#
#
# This plugin also supports taking the URL (as identified above) directly
# as well.
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 MisskeyVisibility:
"""
The visibility of any note created
"""
# post will be public
PUBLIC = 'public'
HOME = 'home'
FOLLOWERS = 'followers'
PRIVATE = 'private'
SPECIFIED = 'specified'
# Define the types in a list for validation purposes
MISSKEY_VISIBILITIES = (
MisskeyVisibility.PUBLIC,
MisskeyVisibility.HOME,
MisskeyVisibility.FOLLOWERS,
MisskeyVisibility.PRIVATE,
MisskeyVisibility.SPECIFIED,
)
class NotifyMisskey(NotifyBase):
"""
A wrapper for Misskey Notifications
"""
# The default descriptive name associated with the Notification
service_name = 'Misskey'
# The services URL
service_url = 'https://misskey-hub.net/'
# The default protocol
protocol = 'misskey'
# The default secure protocol
secure_protocol = 'misskeys'
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_misskey'
# The title is not used
title_maxlen = 0
# The maximum allowable characters allowed in the body per message
body_maxlen = 512
# Define object templates
templates = (
'{schema}://{project_id}/{msghook}',
)
# Define object templates
templates = (
'{schema}://{token}@{host}',
'{schema}://{token}@{host}:{port}',
)
# Define our template arguments
# Define our template arguments
template_tokens = dict(NotifyBase.template_tokens, **{
'host': {
'name': _('Hostname'),
'type': 'string',
'required': True,
},
'token': {
'name': _('Access Token'),
'type': 'string',
'required': True,
},
'port': {
'name': _('Port'),
'type': 'int',
'min': 1,
'max': 65535,
},
})
# Define our template arguments
template_args = dict(NotifyBase.template_args, **{
'token': {
'alias_of': 'token',
},
'visibility': {
'name': _('Visibility'),
'type': 'choice:string',
'values': MISSKEY_VISIBILITIES,
'default': MisskeyVisibility.PUBLIC,
},
})
def __init__(self, token=None, visibility=None, **kwargs):
"""
Initialize Misskey Object
"""
super().__init__(**kwargs)
self.token = validate_regex(token)
if not self.token:
msg = 'An invalid Misskey Access Token was specified.'
self.logger.warning(msg)
raise TypeError(msg)
if visibility:
# Input is a string; attempt to get the lookup from our
# sound mapping
vis = 'invalid' if not isinstance(visibility, str) \
else visibility.lower().strip()
# This little bit of black magic allows us to match against
# against multiple versions of the same string ... etc
self.visibility = \
next((v for v in MISSKEY_VISIBILITIES
if v.startswith(vis)), None)
if self.visibility not in MISSKEY_VISIBILITIES:
msg = 'The Misskey visibility specified ({}) is invalid.' \
.format(visibility)
self.logger.warning(msg)
raise TypeError(msg)
else:
self.visibility = self.template_args['visibility']['default']
# Prepare our URL
self.schema = 'https' if self.secure else 'http'
self.api_url = '%s://%s' % (self.schema, self.host)
if isinstance(self.port, int):
self.api_url += ':%d' % self.port
return
def url(self, privacy=False, *args, **kwargs):
"""
Returns the URL built dynamically based on specified arguments.
"""
params = {
'visibility': self.visibility,
}
# Extend our parameters
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
host = self.host
if isinstance(self.port, int):
host += ':%d' % self.port
return '{schema}://{token}@{host}/?{params}'.format(
schema=self.secure_protocol if self.secure else self.protocol,
host=host,
token=self.pprint(self.token, privacy, safe=''),
params=NotifyMisskey.urlencode(params),
)
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
wrapper to _send since we can alert more then one channel
"""
# prepare our headers
headers = {
'User-Agent': self.app_id,
'Content-Type': 'application/json',
}
# Prepare our payload
payload = {
'i': self.token,
'text': body,
'visibility': self.visibility,
}
api_url = f'{self.api_url}/api/notes/create'
self.logger.debug('Misskey GET URL: %s (cert_verify=%r)' % (
api_url, self.verify_certificate))
self.logger.debug('Misskey Payload: %s' % str(payload))
# Always call throttle before any remote server i/o is made
self.throttle()
try:
r = requests.post(
api_url,
headers=headers,
data=dumps(payload),
verify=self.verify_certificate,
timeout=self.request_timeout,
)
if r.status_code != requests.codes.ok:
# We had a problem
status_str = \
NotifyMisskey.http_response_code_lookup(r.status_code)
self.logger.warning(
'Failed to send Misskey 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 Misskey notification.')
except requests.RequestException as e:
self.logger.warning(
'A Connection error occurred sending Misskey '
'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.
"""
results = NotifyBase.parse_url(url)
if not results:
# We're done early as we couldn't load the results
return results
if 'token' in results['qsd'] and len(results['qsd']['token']):
results['token'] = NotifyMisskey.unquote(results['qsd']['token'])
elif not results['password'] and results['user']:
results['token'] = NotifyMisskey.unquote(results['user'])
# Capture visibility if specified
if 'visibility' in results['qsd'] and \
len(results['qsd']['visibility']):
results['visibility'] = \
NotifyMisskey.unquote(results['qsd']['visibility'])
return results

View File

@ -47,14 +47,14 @@ it easy to access:
Apprise API, AWS SES, AWS SNS, Bark, BulkSMS, Boxcar, ClickSend, DAPNET,
DingTalk, Discord, E-Mail, Emby, Faast, FCM, Flock, Gitter, Google Chat,
Gotify, Growl, Guilded, Home Assistant, IFTTT, Join, Kavenegar, KODI, Kumulos,
LaMetric, Line, MacOSX, Mailgun, Mattermost, Matrix, Microsoft Windows,
Mastodon, Microsoft Teams, MessageBird, MQTT, MSG91, MyAndroid, Nexmo,
LaMetric, Line, MacOSX, Mailgun, Mastodon, Mattermost, Matrix, MessageBird,
Microsoft Windows, Microsoft Teams, Misskey, MQTT, MSG91, MyAndroid, Nexmo,
Nextcloud, NextcloudTalk, Notica, Notifico, ntfy, Office365, OneSignal,
Opsgenie, PagerDuty, PagerTree, ParsePlatform, PopcornNotify, Prowl, Pushalot,
PushBullet, Pushjet, Pushover, PushSafer, Reddit, Rocket.Chat, SendGrid, ServerChan,
Signal, SimplePush, Sinch, Slack, SMSEagle, SMTP2Go, Spontit, SparkPost, Super Toasty,
Streamlabs, Stride, Syslog, Techulus Push, Telegram, Twilio, Twitter, Twist,
XBMC, Voipms, Vonage, Webex Teams}
PushBullet, Pushjet, Pushover, PushSafer, Reddit, Rocket.Chat, SendGrid,
ServerChan, Signal, SimplePush, Sinch, Slack, SMSEagle, SMTP2Go, Spontit,
SparkPost, Super Toasty, Streamlabs, Stride, Syslog, Techulus Push, Telegram,
Twilio, Twitter, Twist, XBMC, Voipms, Vonage, Webex Teams}
Name: python-%{pypi_name}
Version: 1.2.1

111
test/test_plugin_misskey.py Normal file
View File

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2023, 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.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# 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.
import os
from apprise.plugins.NotifyMisskey import NotifyMisskey
from helpers import AppriseURLTester
# Disable logging for a cleaner testing output
import logging
logging.disable(logging.CRITICAL)
# Attachment Directory
TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
# Our Testing URLs
apprise_url_tests = (
##################################
# NotifyMisskey
##################################
('misskey://', {
# Missing Everything :)
'instance': None,
}),
('misskey://:@/', {
'instance': None,
}),
('misskey://hostname', {
# Missing Access Token
'instance': TypeError,
}),
('misskey://access_token@hostname', {
# We're good; it's a simple notification
'instance': NotifyMisskey,
}),
('misskeys://access_token@hostname', {
# We're good; it's another simple notification
'instance': NotifyMisskey,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'misskeys://a...n@hostname/'
}),
('misskey://hostname/?token=abcd123', {
# Our access token can be provided as a token= variable
'instance': NotifyMisskey,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'misskey://a...3@hostname'
}),
('misskeys://access_token@hostname:8443', {
# A custom port specified
'instance': NotifyMisskey,
}),
('misskey://access_token@hostname?visibility=invalid', {
# An invalid visibility
'instance': TypeError,
}),
('misskeys://access_token@hostname?visibility=private', {
# Specified a different visiblity
'instance': NotifyMisskey,
}),
('misskeys://access_token@hostname', {
'instance': NotifyMisskey,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('misskeys://access_token@hostname', {
'instance': NotifyMisskey,
# 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_misskey_urls():
"""
NotifyMisskey() Apprise URLs
"""
# Run our general tests
AppriseURLTester(tests=apprise_url_tests).run_all()