Merge branch '202-mastodon-integration' into 'master'

Resolve "Mastodon integration"

Closes #202

See merge request platypush/platypush!8
This commit is contained in:
Fabio Manganiello 2021-11-07 01:00:29 +01:00
commit e86c61a44d
8 changed files with 1764 additions and 2 deletions

View file

@ -5,6 +5,10 @@ Given the high speed of development in the first phase, changes are being report
## [Unreleased]
### Added
- Added Mastodon integration.
### Fixed
- Fixed `switchbot.status` method in case of virtual devices.

View file

@ -0,0 +1,5 @@
``mastodon``
============
.. automodule:: platypush.plugins.mastodon
:members:

View file

@ -72,6 +72,7 @@ Plugins
platypush/plugins/luma.oled.rst
platypush/plugins/mail.imap.rst
platypush/plugins/mail.smtp.rst
platypush/plugins/mastodon.rst
platypush/plugins/media.chromecast.rst
platypush/plugins/media.gstreamer.rst
platypush/plugins/media.kodi.rst

View file

@ -1,7 +1,6 @@
import datetime
import json
import logging
from typing import Dict, Optional
from flask import Blueprint, request, abort, jsonify
@ -18,7 +17,7 @@ __routes__ = [
@auth.route('/auth', methods=['POST'])
def auth_endpoint() -> Dict[str, Optional[str]]:
def auth_endpoint():
"""
Authentication endpoint. It validates the user credentials provided over a JSON payload with the following
structure:

View file

@ -0,0 +1,46 @@
from threading import RLock
from typing import Optional
from platypush.config import Config
from platypush.context import get_backend, get_plugin
_app_tunnel_lock = RLock()
_app_tunnel_url: Optional[str] = None
def _get_http_port() -> int:
http = None
if Config.get('backend.http'):
http = get_backend('http')
assert http, 'The http backend is required in order to subscribe to notifications'
return http.port
def create_ngrok_tunnel() -> str:
"""
This method creates an ngrok tunnel for the local web application,
useful to register public HTTPS callback URLs on the fly from plugins
and backends.
"""
global _app_tunnel_url
with _app_tunnel_lock:
if _app_tunnel_url:
return _app_tunnel_url
local_port = _get_http_port()
ngrok = None
if Config.get('ngrok'):
ngrok = get_plugin('ngrok')
assert ngrok, 'The ngrok plugin is required in order to subscribe to notifications'
tunnel_response = ngrok.create_tunnel(
resource=local_port,
protocol='http',
bind_tls=True,
).output
_app_tunnel_url = tunnel_response.get('url')
assert _app_tunnel_url, 'Unable to create an ngrok tunnel'
return _app_tunnel_url

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
manifest:
events: {}
package: platypush.plugins.mastodon
type: plugin

View file

@ -0,0 +1,201 @@
from random import randint
from marshmallow import fields, missing
from marshmallow.schema import Schema
from marshmallow.validate import OneOf
from platypush.schemas import DateTime, Date, StrippedString
notification_types = ['follow', 'favourite', 'reblog', 'mention', 'poll', 'follow_request']
list_reply_policies = ['none', 'followed', 'list']
class MastodonSchema(Schema):
pass
class MastodonAccountSchema(MastodonSchema):
id = fields.String(
dump_only=True,
metadata=dict(
example=''.join([f'{randint(1, 9)}' for _ in range(18)]),
)
)
username = fields.String(
metadata=dict(
example='admin',
)
)
url = fields.URL()
avatar = fields.URL()
header = fields.URL()
followers_count = fields.Int(dump_only=True)
following_count = fields.Int(dump_only=True)
note = fields.String()
display_name = StrippedString(
metadata=dict(
example='Name Surname',
)
)
locked = fields.Boolean()
bot = fields.Boolean()
discoverable = fields.Boolean()
group = fields.Boolean()
created_at = DateTime(dump_only=True)
last_status_at = DateTime(dump_only=True)
class MastodonFeaturedHashtagSchema(MastodonSchema):
id = fields.Int(dump_only=True)
name = fields.String()
statuses_count = fields.Int(dump_only=True)
last_status = DateTime(dump_only=True)
class MastodonHashtagHistorySchema(MastodonSchema):
day = Date()
uses = fields.Int()
accounts = fields.Int()
class MastodonHashtagSchema(MastodonSchema):
name = fields.String(metadata=dict(example='hashtag'))
url = fields.URL()
history = fields.Nested(
MastodonHashtagHistorySchema, many=True, default=missing
)
class MastodonMediaSchema(MastodonSchema):
id = fields.String(dump_only=True)
description = StrippedString()
type = fields.String(dump_only=True, metadata={'example': 'image'})
url = fields.URL(dump_only=True)
preview_url = fields.URL(dump_only=True)
remote_url = fields.URL(dump_only=True)
preview_remote_url = fields.URL(dump_only=True)
meta = fields.Dict()
class MastodonStatusSchema(MastodonSchema):
id = fields.String(
dump_only=True,
metadata=dict(
example=''.join([f'{randint(1, 9)}' for _ in range(18)]),
)
)
in_reply_to_id = fields.String(
dump_only=True,
allow_none=True,
metadata=dict(
example=''.join([f'{randint(1, 9)}' for _ in range(18)]),
)
)
in_reply_to_account_id = fields.String(
dump_only=True,
allow_none=True,
metadata=dict(
example=''.join([f'{randint(1, 9)}' for _ in range(18)]),
)
)
url = fields.URL(dump_only=True)
content = fields.String(allow_none=False)
account = fields.Nested(MastodonAccountSchema, dump_only=True)
attachments = fields.Nested(
MastodonMediaSchema,
many=True,
dump_only=True,
attribute='media_attachments',
)
hashtags = fields.Nested(
MastodonHashtagSchema, many=True,
attribute='tags', dump_only=True
)
replies_count = fields.Int(dump_only=True)
reblogs_count = fields.Int(dump_only=True)
favourites_count = fields.Int(dump_only=True)
sensitive = fields.Boolean()
favourited = fields.Boolean()
reblogged = fields.Boolean()
muted = fields.Boolean()
bookmarked = fields.Boolean()
pinned = fields.Boolean()
created_at = DateTime(dump_only=True)
class MastodonSearchSchema(MastodonSchema):
accounts = fields.Nested(MastodonAccountSchema, many=True)
statuses = fields.Nested(MastodonStatusSchema, many=True)
hashtags = fields.Nested(MastodonHashtagSchema, many=True)
class MastodonAccountCreationSchema(MastodonSchema):
access_token = fields.String(dump_only=True)
token_type = fields.String(dump_only=True, metadata={'example': 'Bearer'})
scope = fields.String(dump_only=True, metadata={'example': 'read write follow push'})
created_at = DateTime(dump_only=True)
class MastodonAccountListSchema(MastodonSchema):
id = fields.Int(dump_only=True)
title = StrippedString()
class MastodonFilterSchema(MastodonSchema):
id = fields.Int(dump_only=True)
phrase = StrippedString()
whole_word = fields.Boolean()
irreversible = fields.Boolean()
expires_at = DateTime(allow_none=True)
context = fields.List(
fields.String(validate=OneOf(['home', 'notifications', 'public', 'thread'])),
metadata={
'example': 'Which context(s) this filter applies to. '
'Possible values: home, notifications, public, thread',
}
)
class MastodonConversationSchema(MastodonSchema):
id = fields.Int(dump_only=True)
unread = fields.Boolean()
accounts = fields.Nested(MastodonAccountSchema, many=True)
last_status = fields.Nested(MastodonStatusSchema)
class MastodonListSchema(MastodonSchema):
id = fields.Int(dump_only=True)
title = StrippedString()
replies_policy = fields.String(validate=OneOf(list_reply_policies))
class MastodonMentionSchema(MastodonSchema):
id = fields.Int(dump_only=True)
username = StrippedString(metadata=dict(example='user'))
url = fields.URL(metadata=dict(example='https://mastodon.social/@user'))
class MastodonNotificationSchema(MastodonSchema):
id = fields.String(dump_only=True)
type = fields.String(validate=OneOf(notification_types))
account = fields.Nested(MastodonAccountSchema)
status = fields.Nested(MastodonStatusSchema)
mention = fields.Nested(MastodonMentionSchema)
created_at = DateTime(dump_only=True)
class MastodonSubscriptionNotificationTypes(MastodonSchema):
follow = fields.Boolean()
reblog = fields.Boolean()
mention = fields.Boolean()
favourite = fields.Boolean()
poll = fields.Boolean()