2021-01-22 11:29:36 +00:00
|
|
|
__filename__ = "mastoapiv1.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2023-01-21 23:03:30 +00:00
|
|
|
__version__ = "1.4.0"
|
2021-01-22 11:29:36 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2021-01-22 11:29:36 +00:00
|
|
|
__status__ = "Production"
|
2021-06-26 11:16:41 +00:00
|
|
|
__module_group__ = "API"
|
2021-01-22 11:29:36 +00:00
|
|
|
|
|
|
|
import os
|
2021-12-26 15:13:34 +00:00
|
|
|
from utils import load_json
|
2021-12-26 14:08:58 +00:00
|
|
|
from utils import get_config_param
|
2021-12-26 12:02:29 +00:00
|
|
|
from utils import acct_dir
|
2021-12-29 21:55:09 +00:00
|
|
|
from metadata import meta_data_instance
|
2021-01-22 11:29:36 +00:00
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _get_mast_api_v1id(path: str) -> int:
|
2021-01-22 13:32:37 +00:00
|
|
|
"""Extracts the mastodon Id number from the given path
|
|
|
|
"""
|
2022-01-02 22:49:32 +00:00
|
|
|
masto_id = None
|
|
|
|
id_path = '/api/v1/accounts/:'
|
|
|
|
if not path.startswith(id_path):
|
2021-01-22 13:32:37 +00:00
|
|
|
return None
|
2022-01-02 22:49:32 +00:00
|
|
|
masto_id_str = path.replace(id_path, '')
|
|
|
|
if '/' in masto_id_str:
|
|
|
|
masto_id_str = masto_id_str.split('/')[0]
|
|
|
|
if masto_id_str.isdigit():
|
|
|
|
masto_id = int(masto_id_str)
|
|
|
|
return masto_id
|
2021-01-22 13:32:37 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def get_masto_api_v1id_from_nickname(nickname: str) -> int:
|
2021-01-22 13:32:37 +00:00
|
|
|
"""Given an account nickname return the corresponding mastodon id
|
|
|
|
"""
|
|
|
|
return int.from_bytes(nickname.encode('utf-8'), 'little')
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _int_to_bytes(num: int) -> str:
|
2022-01-02 22:49:32 +00:00
|
|
|
"""Integer conversion
|
|
|
|
"""
|
2021-01-22 13:32:37 +00:00
|
|
|
if num == 0:
|
|
|
|
return b""
|
2022-01-02 22:49:32 +00:00
|
|
|
return _int_to_bytes(num // 256) + bytes([num % 256])
|
2021-01-22 13:32:37 +00:00
|
|
|
|
|
|
|
|
2022-01-02 22:49:32 +00:00
|
|
|
def get_nickname_from_masto_api_v1id(masto_id: int) -> str:
|
2021-01-22 13:32:37 +00:00
|
|
|
"""Given the mastodon Id return the nickname
|
|
|
|
"""
|
2022-01-02 22:49:32 +00:00
|
|
|
nickname = _int_to_bytes(masto_id).decode()
|
2021-01-22 13:32:37 +00:00
|
|
|
return nickname[::-1]
|
|
|
|
|
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _get_masto_api_v1account(base_dir: str, nickname: str, domain: str) -> {}:
|
2021-01-22 11:29:36 +00:00
|
|
|
"""See https://github.com/McKael/mastodon-documentation/
|
|
|
|
blob/master/Using-the-API/API.md#account
|
|
|
|
Authorization has already been performed
|
|
|
|
"""
|
2022-01-02 22:49:32 +00:00
|
|
|
account_filename = acct_dir(base_dir, nickname, domain) + '.json'
|
|
|
|
if not os.path.isfile(account_filename):
|
2021-01-22 11:29:36 +00:00
|
|
|
return {}
|
2022-01-02 22:49:32 +00:00
|
|
|
account_json = load_json(account_filename)
|
|
|
|
if not account_json:
|
2021-01-22 11:29:36 +00:00
|
|
|
return {}
|
2022-01-02 22:49:32 +00:00
|
|
|
masto_account_json = {
|
2021-12-29 21:55:09 +00:00
|
|
|
"id": get_masto_api_v1id_from_nickname(nickname),
|
2021-01-22 11:29:36 +00:00
|
|
|
"username": nickname,
|
|
|
|
"acct": nickname,
|
2022-01-02 22:49:32 +00:00
|
|
|
"display_name": account_json['name'],
|
|
|
|
"locked": account_json['manuallyApprovesFollowers'],
|
2021-01-22 14:58:35 +00:00
|
|
|
"created_at": "2016-10-05T10:30:00Z",
|
2021-01-22 11:29:36 +00:00
|
|
|
"followers_count": 0,
|
|
|
|
"following_count": 0,
|
|
|
|
"statuses_count": 0,
|
2022-01-02 22:49:32 +00:00
|
|
|
"note": account_json['summary'],
|
|
|
|
"url": account_json['id'],
|
|
|
|
"avatar": account_json['icon']['url'],
|
|
|
|
"avatar_static": account_json['icon']['url'],
|
|
|
|
"header": account_json['image']['url'],
|
|
|
|
"header_static": account_json['image']['url']
|
2021-01-22 11:29:36 +00:00
|
|
|
}
|
2022-01-02 22:49:32 +00:00
|
|
|
return masto_account_json
|
2021-06-07 19:10:37 +00:00
|
|
|
|
|
|
|
|
2021-12-28 17:20:43 +00:00
|
|
|
def masto_api_v1_response(path: str, calling_domain: str,
|
2022-01-02 22:49:32 +00:00
|
|
|
ua_str: str,
|
2021-12-28 17:20:43 +00:00
|
|
|
authorized: bool,
|
|
|
|
http_prefix: str,
|
|
|
|
base_dir: str, nickname: str, domain: str,
|
|
|
|
domain_full: str,
|
|
|
|
onion_domain: str, i2p_domain: str,
|
|
|
|
translate: {},
|
|
|
|
registration: bool,
|
|
|
|
system_language: str,
|
|
|
|
project_version: str,
|
2022-01-01 20:36:56 +00:00
|
|
|
custom_emoji: [],
|
2021-12-28 17:20:43 +00:00
|
|
|
show_node_info_accounts: bool,
|
|
|
|
broch_mode: bool) -> ({}, str):
|
2021-06-07 19:10:37 +00:00
|
|
|
"""This is a vestigil mastodon API for the purpose
|
|
|
|
of returning an empty result to sites like
|
|
|
|
https://mastopeek.app-dist.eu
|
|
|
|
"""
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = None
|
|
|
|
send_json_str = ''
|
|
|
|
if not ua_str:
|
|
|
|
ua_str = ''
|
2021-06-07 19:10:37 +00:00
|
|
|
|
|
|
|
# parts of the api needing authorization
|
|
|
|
if authorized and nickname:
|
|
|
|
if path == '/api/v1/accounts/verify_credentials':
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = _get_masto_api_v1account(base_dir, nickname, domain)
|
|
|
|
send_json_str = \
|
|
|
|
'masto API account sent for ' + nickname + ' ' + ua_str
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2021-10-24 09:02:28 +00:00
|
|
|
# information about where the request is coming from
|
2022-01-02 22:49:32 +00:00
|
|
|
calling_info = ' ' + ua_str + ', ' + calling_domain
|
2021-10-24 09:02:28 +00:00
|
|
|
|
2021-06-07 19:10:37 +00:00
|
|
|
# Parts of the api which don't need authorization
|
2022-01-02 22:49:32 +00:00
|
|
|
masto_id = _get_mast_api_v1id(path)
|
|
|
|
if masto_id is not None:
|
|
|
|
path_nickname = get_nickname_from_masto_api_v1id(masto_id)
|
|
|
|
if path_nickname:
|
|
|
|
original_path = path
|
2021-06-07 19:10:37 +00:00
|
|
|
if '/followers?' in path or \
|
|
|
|
'/following?' in path or \
|
2022-02-25 10:29:14 +00:00
|
|
|
'/streaming/' in path or \
|
2021-06-07 19:10:37 +00:00
|
|
|
'/search?' in path or \
|
|
|
|
'/relationships?' in path or \
|
|
|
|
'/statuses?' in path:
|
|
|
|
path = path.split('?')[0]
|
2022-02-25 10:29:14 +00:00
|
|
|
if '/streaming/' in path:
|
|
|
|
streaming_msg = \
|
|
|
|
"Error: Streaming API not implemented on this instance"
|
|
|
|
send_json = {
|
|
|
|
"error": streaming_msg
|
|
|
|
}
|
|
|
|
send_json_str = 'masto API streaming response'
|
2021-06-07 19:10:37 +00:00
|
|
|
if path.endswith('/followers'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
2021-10-23 18:51:30 +00:00
|
|
|
'masto API followers sent for ' + nickname + \
|
2022-01-02 22:49:32 +00:00
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.endswith('/following'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
2021-10-23 18:51:30 +00:00
|
|
|
'masto API following sent for ' + nickname + \
|
2022-01-02 22:49:32 +00:00
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.endswith('/statuses'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
2021-10-23 18:51:30 +00:00
|
|
|
'masto API statuses sent for ' + nickname + \
|
2022-01-02 22:49:32 +00:00
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.endswith('/search'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API search sent ' + original_path + \
|
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.endswith('/relationships'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API relationships sent ' + original_path + \
|
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
else:
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = \
|
|
|
|
_get_masto_api_v1account(base_dir, path_nickname, domain)
|
|
|
|
send_json_str = \
|
2021-10-23 18:51:30 +00:00
|
|
|
'masto API account sent for ' + nickname + \
|
2022-01-02 22:49:32 +00:00
|
|
|
calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2021-08-02 11:54:47 +00:00
|
|
|
# NOTE: adding support for '/api/v1/directory seems to create
|
|
|
|
# federation problems, so avoid implementing that
|
|
|
|
|
2021-06-07 19:10:37 +00:00
|
|
|
if path.startswith('/api/v1/blocks'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API instance blocks sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/favorites'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = 'masto API favorites sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/follow_requests'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API follow requests sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/mutes'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API mutes sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/notifications'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = \
|
|
|
|
'masto API notifications sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/reports'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = 'masto API reports sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/statuses'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = 'masto API statuses sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/timelines'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = {
|
2021-10-13 11:27:37 +00:00
|
|
|
'error': 'This method requires an authenticated user'
|
|
|
|
}
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json_str = 'masto API timelines sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/custom_emojis'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = custom_emoji
|
|
|
|
send_json_str = \
|
|
|
|
'masto API custom emojis sent ' + path + calling_info
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2021-12-31 21:18:12 +00:00
|
|
|
admin_nickname = get_config_param(base_dir, 'admin')
|
|
|
|
if admin_nickname and path == '/api/v1/instance':
|
2022-01-02 22:49:32 +00:00
|
|
|
instance_description_short = \
|
2021-12-26 14:08:58 +00:00
|
|
|
get_config_param(base_dir, 'instanceDescriptionShort')
|
2022-01-02 22:49:32 +00:00
|
|
|
if not instance_description_short:
|
|
|
|
instance_description_short = \
|
2021-06-07 19:10:37 +00:00
|
|
|
translate['Yet another Epicyon Instance']
|
2022-01-02 22:49:32 +00:00
|
|
|
instance_description = \
|
2021-12-26 14:08:58 +00:00
|
|
|
get_config_param(base_dir, 'instanceDescription')
|
2022-01-02 22:49:32 +00:00
|
|
|
instance_title = get_config_param(base_dir, 'instanceTitle')
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2021-12-26 18:29:39 +00:00
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
2021-12-26 10:00:46 +00:00
|
|
|
domain_full = onion_domain
|
2021-12-25 17:09:22 +00:00
|
|
|
http_prefix = 'http'
|
2021-12-26 18:29:39 +00:00
|
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
2021-12-26 10:00:46 +00:00
|
|
|
domain_full = i2p_domain
|
2021-12-25 17:09:22 +00:00
|
|
|
http_prefix = 'http'
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2021-12-25 18:38:19 +00:00
|
|
|
if broch_mode:
|
2021-12-25 18:32:17 +00:00
|
|
|
show_node_info_accounts = False
|
2021-06-07 19:10:37 +00:00
|
|
|
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = \
|
2021-12-29 21:55:09 +00:00
|
|
|
meta_data_instance(show_node_info_accounts,
|
2022-01-02 22:49:32 +00:00
|
|
|
instance_title,
|
|
|
|
instance_description_short,
|
|
|
|
instance_description,
|
2021-12-29 21:55:09 +00:00
|
|
|
http_prefix,
|
|
|
|
base_dir,
|
2021-12-31 21:18:12 +00:00
|
|
|
admin_nickname,
|
2021-12-29 21:55:09 +00:00
|
|
|
domain,
|
|
|
|
domain_full,
|
|
|
|
registration,
|
|
|
|
system_language,
|
|
|
|
project_version)
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json_str = 'masto API instance metadata sent ' + ua_str
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/instance/peers'):
|
|
|
|
# This is just a dummy result.
|
|
|
|
# Showing the full list of peers would have privacy implications.
|
|
|
|
# On a large instance you are somewhat lost in the crowd, but on
|
|
|
|
# small instances a full list of peers would convey a lot of
|
|
|
|
# information about the interests of a small number of accounts
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = ['mastodon.social', domain_full]
|
|
|
|
send_json_str = 'masto API peers metadata sent ' + ua_str
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/instance/activity'):
|
2022-01-02 22:49:32 +00:00
|
|
|
send_json = []
|
|
|
|
send_json_str = 'masto API activity metadata sent ' + ua_str
|
|
|
|
return send_json, send_json_str
|