forked from platypush/platypush
Merge branch '202-mastodon-integration' into 'master'
Resolve "Mastodon integration" Closes #202 See merge request platypush/platypush!8
This commit is contained in:
commit
e86c61a44d
8 changed files with 1764 additions and 2 deletions
|
@ -5,6 +5,10 @@ Given the high speed of development in the first phase, changes are being report
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added Mastodon integration.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `switchbot.status` method in case of virtual devices.
|
- Fixed `switchbot.status` method in case of virtual devices.
|
||||||
|
|
5
docs/source/platypush/plugins/mastodon.rst
Normal file
5
docs/source/platypush/plugins/mastodon.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``mastodon``
|
||||||
|
============
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.mastodon
|
||||||
|
:members:
|
|
@ -72,6 +72,7 @@ Plugins
|
||||||
platypush/plugins/luma.oled.rst
|
platypush/plugins/luma.oled.rst
|
||||||
platypush/plugins/mail.imap.rst
|
platypush/plugins/mail.imap.rst
|
||||||
platypush/plugins/mail.smtp.rst
|
platypush/plugins/mail.smtp.rst
|
||||||
|
platypush/plugins/mastodon.rst
|
||||||
platypush/plugins/media.chromecast.rst
|
platypush/plugins/media.chromecast.rst
|
||||||
platypush/plugins/media.gstreamer.rst
|
platypush/plugins/media.gstreamer.rst
|
||||||
platypush/plugins/media.kodi.rst
|
platypush/plugins/media.kodi.rst
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
from flask import Blueprint, request, abort, jsonify
|
from flask import Blueprint, request, abort, jsonify
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ __routes__ = [
|
||||||
|
|
||||||
|
|
||||||
@auth.route('/auth', methods=['POST'])
|
@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
|
Authentication endpoint. It validates the user credentials provided over a JSON payload with the following
|
||||||
structure:
|
structure:
|
||||||
|
|
46
platypush/common/ngrok/__init__.py
Normal file
46
platypush/common/ngrok/__init__.py
Normal 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
|
1502
platypush/plugins/mastodon/__init__.py
Normal file
1502
platypush/plugins/mastodon/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
4
platypush/plugins/mastodon/manifest.yaml
Normal file
4
platypush/plugins/mastodon/manifest.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
manifest:
|
||||||
|
events: {}
|
||||||
|
package: platypush.plugins.mastodon
|
||||||
|
type: plugin
|
201
platypush/schemas/mastodon.py
Normal file
201
platypush/schemas/mastodon.py
Normal 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()
|
Loading…
Reference in a new issue