pleroma-relay/relay/misc.py

245 lines
5.4 KiB
Python
Raw Normal View History

from __future__ import annotations
2022-05-06 07:04:51 +00:00
import json
import os
2022-05-06 07:04:51 +00:00
import socket
import typing
2022-05-06 07:04:51 +00:00
from aiohttp.web import Response as AiohttpResponse
from aputils.message import Message as ApMessage
2024-02-21 00:22:18 +00:00
from datetime import datetime
2022-05-06 07:04:51 +00:00
from uuid import uuid4
2024-02-22 22:54:15 +00:00
try:
from importlib.resources import files as pkgfiles
except ImportError:
from importlib_resources import files as pkgfiles
if typing.TYPE_CHECKING:
2024-02-22 22:54:15 +00:00
from pathlib import Path
2024-02-05 18:15:08 +00:00
from typing import Any
from .application import Application
2022-05-06 07:04:51 +00:00
IS_DOCKER = bool(os.environ.get('DOCKER_RUNNING'))
2022-11-09 10:58:35 +00:00
MIMETYPES = {
'activity': 'application/activity+json',
'html': 'text/html',
'json': 'application/json',
'text': 'text/plain'
2022-11-09 10:58:35 +00:00
}
NODEINFO_NS = {
'20': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
}
2022-05-06 07:04:51 +00:00
def boolean(value: Any) -> bool:
2022-11-20 11:14:37 +00:00
if isinstance(value, str):
2024-01-24 02:54:58 +00:00
if value.lower() in {'on', 'y', 'yes', 'true', 'enable', 'enabled', '1'}:
2022-11-20 11:14:37 +00:00
return True
2024-01-24 02:54:58 +00:00
if value.lower() in {'off', 'n', 'no', 'false', 'disable', 'disabled', '0'}:
2022-11-20 11:14:37 +00:00
return False
raise TypeError(f'Cannot parse string "{value}" as a boolean')
2022-11-20 11:14:37 +00:00
if isinstance(value, int):
2022-11-20 11:14:37 +00:00
if value == 1:
return True
if value == 0:
2022-11-20 11:14:37 +00:00
return False
raise ValueError('Integer value must be 1 or 0')
2022-11-20 11:14:37 +00:00
if value is None:
2022-11-20 11:14:37 +00:00
return False
return bool(value)
2022-11-20 11:14:37 +00:00
def check_open_port(host: str, port: int) -> bool:
2022-05-06 07:04:51 +00:00
if host == '0.0.0.0':
host = '127.0.0.1'
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
return s.connect_ex((host, port)) != 0
2022-05-06 07:04:51 +00:00
except socket.error:
2022-05-06 07:04:51 +00:00
return False
def get_app() -> Application:
from .application import Application # pylint: disable=import-outside-toplevel
2022-11-07 10:30:13 +00:00
if not Application.DEFAULT:
raise ValueError('No default application set')
return Application.DEFAULT
2024-02-22 22:54:15 +00:00
def get_resource(path: str) -> Path:
return pkgfiles('relay').joinpath(path)
2024-02-21 00:22:18 +00:00
class JsonEncoder(json.JSONEncoder):
def default(self, obj: Any) -> str:
if isinstance(obj, datetime):
return obj.isoformat()
return JSONEncoder.default(self, obj)
class Message(ApMessage):
2022-11-07 10:30:13 +00:00
@classmethod
2024-01-24 02:54:58 +00:00
def new_actor(cls: type[Message], # pylint: disable=arguments-differ
host: str,
pubkey: str,
2024-01-24 02:54:58 +00:00
description: str | None = None) -> Message:
2022-11-07 10:30:13 +00:00
return cls({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://{host}/actor',
'type': 'Application',
'preferredUsername': 'relay',
'name': 'ActivityRelay',
'summary': description or 'ActivityRelay bot',
'followers': f'https://{host}/followers',
'following': f'https://{host}/following',
'inbox': f'https://{host}/inbox',
'url': f'https://{host}/',
2022-11-07 10:30:13 +00:00
'endpoints': {
'sharedInbox': f'https://{host}/inbox'
},
'publicKey': {
'id': f'https://{host}/actor#main-key',
'owner': f'https://{host}/actor',
'publicKeyPem': pubkey
}
})
@classmethod
2024-01-24 02:54:58 +00:00
def new_announce(cls: type[Message], host: str, obj: str) -> Message:
2022-11-07 10:30:13 +00:00
return cls({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://{host}/activities/{uuid4()}',
2022-11-07 10:30:13 +00:00
'type': 'Announce',
'to': [f'https://{host}/followers'],
'actor': f'https://{host}/actor',
'object': obj
2022-11-07 10:30:13 +00:00
})
@classmethod
2024-01-24 02:54:58 +00:00
def new_follow(cls: type[Message], host: str, actor: str) -> Message:
2022-11-07 10:30:13 +00:00
return cls({
'@context': 'https://www.w3.org/ns/activitystreams',
'type': 'Follow',
'to': [actor],
'object': actor,
'id': f'https://{host}/activities/{uuid4()}',
2022-11-07 10:30:13 +00:00
'actor': f'https://{host}/actor'
})
@classmethod
2024-01-24 02:54:58 +00:00
def new_unfollow(cls: type[Message], host: str, actor: str, follow: str) -> Message:
2022-11-07 10:30:13 +00:00
return cls({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://{host}/activities/{uuid4()}',
2022-11-07 10:30:13 +00:00
'type': 'Undo',
'to': [actor],
'actor': f'https://{host}/actor',
'object': follow
})
@classmethod
2024-01-24 02:54:58 +00:00
def new_response(cls: type[Message],
host: str,
actor: str,
followid: str,
accept: bool) -> Message:
2022-11-07 10:30:13 +00:00
return cls({
'@context': 'https://www.w3.org/ns/activitystreams',
'id': f'https://{host}/activities/{uuid4()}',
2022-11-07 10:30:13 +00:00
'type': 'Accept' if accept else 'Reject',
'to': [actor],
'actor': f'https://{host}/actor',
'object': {
'id': followid,
'type': 'Follow',
'object': f'https://{host}/actor',
'actor': actor
}
})
2024-01-16 05:33:05 +00:00
# todo: remove when fixed in aputils
@property
def object_id(self) -> str:
try:
return self["object"]["id"]
except (KeyError, TypeError):
return self["object"]
2022-11-09 10:58:35 +00:00
class Response(AiohttpResponse):
2024-01-25 00:24:27 +00:00
# AiohttpResponse.__len__ method returns 0, so bool(response) always returns False
def __bool__(self) -> bool:
return True
2022-11-09 10:58:35 +00:00
@classmethod
2024-01-24 02:54:58 +00:00
def new(cls: type[Response],
body: str | bytes | dict = '',
status: int = 200,
headers: dict[str, str] | None = None,
ctype: str = 'text') -> Response:
2022-11-09 10:58:35 +00:00
kwargs = {
'status': status,
'headers': headers,
'content_type': MIMETYPES[ctype]
}
if isinstance(body, bytes):
kwargs['body'] = body
2024-02-21 00:22:18 +00:00
elif isinstance(body, (dict, list, tuple, set)) or ctype in {'json', 'activity'}:
kwargs['text'] = json.dumps(body, cls = JsonEncoder)
2022-11-09 10:58:35 +00:00
else:
kwargs['text'] = body
return cls(**kwargs)
@classmethod
2024-01-24 02:54:58 +00:00
def new_error(cls: type[Response],
status: int,
body: str | bytes | dict,
ctype: str = 'text') -> Response:
2022-11-09 10:58:35 +00:00
if ctype == 'json':
2024-02-21 00:22:18 +00:00
body = {'error': body}
2022-11-09 10:58:35 +00:00
2022-11-13 06:00:53 +00:00
return cls.new(body=body, status=status, ctype=ctype)
2022-11-09 10:58:35 +00:00
@property
def location(self) -> str:
2022-11-09 10:58:35 +00:00
return self.headers.get('Location')
@location.setter
def location(self, value: str) -> None:
2022-11-09 10:58:35 +00:00
self.headers['Location'] = value