2021-01-22 11:29:36 +00:00
|
|
|
__filename__ = "mastoapiv1.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2021-01-26 10:07:42 +00:00
|
|
|
__version__ = "1.2.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
|
|
|
|
from utils import loadJson
|
2021-06-07 19:10:37 +00:00
|
|
|
from utils import getConfigParam
|
2021-07-13 21:59:53 +00:00
|
|
|
from utils import acctDir
|
2021-06-07 19:10:37 +00:00
|
|
|
from metadata import metaDataInstance
|
2021-01-22 11:29:36 +00:00
|
|
|
|
|
|
|
|
2021-06-07 19:10:37 +00:00
|
|
|
def _getMastApiV1Id(path: str) -> int:
|
2021-01-22 13:32:37 +00:00
|
|
|
"""Extracts the mastodon Id number from the given path
|
|
|
|
"""
|
|
|
|
mastoId = None
|
|
|
|
idPath = '/api/v1/accounts/:'
|
|
|
|
if not path.startswith(idPath):
|
|
|
|
return None
|
|
|
|
mastoIdStr = path.replace(idPath, '')
|
|
|
|
if '/' in mastoIdStr:
|
|
|
|
mastoIdStr = mastoIdStr.split('/')[0]
|
|
|
|
if mastoIdStr.isdigit():
|
|
|
|
mastoId = int(mastoIdStr)
|
|
|
|
return mastoId
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def getMastoApiV1IdFromNickname(nickname: str) -> int:
|
|
|
|
"""Given an account nickname return the corresponding mastodon id
|
|
|
|
"""
|
|
|
|
return int.from_bytes(nickname.encode('utf-8'), 'little')
|
|
|
|
|
|
|
|
|
|
|
|
def _intToBytes(num: int) -> str:
|
|
|
|
if num == 0:
|
|
|
|
return b""
|
|
|
|
else:
|
|
|
|
return _intToBytes(num // 256) + bytes([num % 256])
|
|
|
|
|
|
|
|
|
|
|
|
def getNicknameFromMastoApiV1Id(mastoId: int) -> str:
|
|
|
|
"""Given the mastodon Id return the nickname
|
|
|
|
"""
|
|
|
|
nickname = _intToBytes(mastoId).decode()
|
|
|
|
return nickname[::-1]
|
|
|
|
|
|
|
|
|
2021-06-07 19:10:37 +00:00
|
|
|
def _getMastoApiV1Account(baseDir: 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
|
|
|
|
"""
|
2021-07-13 21:59:53 +00:00
|
|
|
accountFilename = acctDir(baseDir, nickname, domain) + '.json'
|
2021-01-22 11:29:36 +00:00
|
|
|
if not os.path.isfile(accountFilename):
|
|
|
|
return {}
|
|
|
|
accountJson = loadJson(accountFilename)
|
|
|
|
if not accountJson:
|
|
|
|
return {}
|
|
|
|
mastoAccountJson = {
|
2021-01-22 13:32:37 +00:00
|
|
|
"id": getMastoApiV1IdFromNickname(nickname),
|
2021-01-22 11:29:36 +00:00
|
|
|
"username": nickname,
|
|
|
|
"acct": nickname,
|
2021-01-22 13:42:22 +00:00
|
|
|
"display_name": accountJson['name'],
|
2021-01-22 11:29:36 +00:00
|
|
|
"locked": accountJson['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,
|
|
|
|
"note": accountJson['summary'],
|
|
|
|
"url": accountJson['id'],
|
|
|
|
"avatar": accountJson['icon']['url'],
|
|
|
|
"avatar_static": accountJson['icon']['url'],
|
|
|
|
"header": accountJson['image']['url'],
|
|
|
|
"header_static": accountJson['image']['url']
|
|
|
|
}
|
|
|
|
return mastoAccountJson
|
2021-06-07 19:10:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
def mastoApiV1Response(path: str, callingDomain: str,
|
|
|
|
authorized: bool,
|
|
|
|
httpPrefix: str,
|
|
|
|
baseDir: str, nickname: str, domain: str,
|
|
|
|
domainFull: str,
|
|
|
|
onionDomain: str, i2pDomain: str,
|
|
|
|
translate: {},
|
|
|
|
registration: bool,
|
|
|
|
systemLanguage: str,
|
|
|
|
projectVersion: str,
|
|
|
|
customEmoji: [],
|
|
|
|
showNodeInfoAccounts: bool,
|
|
|
|
brochMode: bool) -> ({}, str):
|
|
|
|
"""This is a vestigil mastodon API for the purpose
|
|
|
|
of returning an empty result to sites like
|
|
|
|
https://mastopeek.app-dist.eu
|
|
|
|
"""
|
|
|
|
sendJson = None
|
|
|
|
sendJsonStr = ''
|
|
|
|
|
|
|
|
# parts of the api needing authorization
|
|
|
|
if authorized and nickname:
|
|
|
|
if path == '/api/v1/accounts/verify_credentials':
|
|
|
|
sendJson = _getMastoApiV1Account(baseDir, nickname, domain)
|
|
|
|
sendJsonStr = 'masto API account sent for ' + nickname
|
|
|
|
|
|
|
|
# Parts of the api which don't need authorization
|
|
|
|
mastoId = _getMastApiV1Id(path)
|
|
|
|
if mastoId is not None:
|
|
|
|
pathNickname = getNicknameFromMastoApiV1Id(mastoId)
|
|
|
|
if pathNickname:
|
|
|
|
originalPath = path
|
|
|
|
if '/followers?' in path or \
|
|
|
|
'/following?' in path or \
|
|
|
|
'/search?' in path or \
|
|
|
|
'/relationships?' in path or \
|
|
|
|
'/statuses?' in path:
|
|
|
|
path = path.split('?')[0]
|
|
|
|
if path.endswith('/followers'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = 'masto API followers sent for ' + nickname
|
|
|
|
elif path.endswith('/following'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = 'masto API following sent for ' + nickname
|
|
|
|
elif path.endswith('/statuses'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = 'masto API statuses sent for ' + nickname
|
|
|
|
elif path.endswith('/search'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = 'masto API search sent ' + originalPath
|
|
|
|
elif path.endswith('/relationships'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = \
|
|
|
|
'masto API relationships sent ' + originalPath
|
|
|
|
else:
|
|
|
|
sendJson = \
|
|
|
|
_getMastoApiV1Account(baseDir, pathNickname, domain)
|
|
|
|
sendJsonStr = 'masto API account sent for ' + nickname
|
|
|
|
|
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'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API instance blocks sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/favorites'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API favorites sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/follow_requests'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API follow requests sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/mutes'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API mutes sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/notifications'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API notifications sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/reports'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API reports sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/statuses'):
|
|
|
|
sendJson = []
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API statuses sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/timelines'):
|
2021-10-13 11:27:37 +00:00
|
|
|
sendJson = {
|
|
|
|
'error': 'This method requires an authenticated user'
|
|
|
|
}
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API timelines sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
elif path.startswith('/api/v1/custom_emojis'):
|
|
|
|
sendJson = customEmoji
|
2021-10-12 11:56:49 +00:00
|
|
|
sendJsonStr = 'masto API custom emojis sent ' + path
|
2021-06-07 19:10:37 +00:00
|
|
|
|
|
|
|
adminNickname = getConfigParam(baseDir, 'admin')
|
|
|
|
if adminNickname and path == '/api/v1/instance':
|
|
|
|
instanceDescriptionShort = \
|
|
|
|
getConfigParam(baseDir,
|
|
|
|
'instanceDescriptionShort')
|
|
|
|
if not instanceDescriptionShort:
|
|
|
|
instanceDescriptionShort = \
|
|
|
|
translate['Yet another Epicyon Instance']
|
|
|
|
instanceDescription = getConfigParam(baseDir,
|
|
|
|
'instanceDescription')
|
|
|
|
instanceTitle = getConfigParam(baseDir, 'instanceTitle')
|
|
|
|
|
|
|
|
if callingDomain.endswith('.onion') and onionDomain:
|
|
|
|
domainFull = onionDomain
|
|
|
|
httpPrefix = 'http'
|
|
|
|
elif (callingDomain.endswith('.i2p') and i2pDomain):
|
|
|
|
domainFull = i2pDomain
|
|
|
|
httpPrefix = 'http'
|
|
|
|
|
|
|
|
if brochMode:
|
|
|
|
showNodeInfoAccounts = False
|
|
|
|
|
|
|
|
sendJson = \
|
|
|
|
metaDataInstance(showNodeInfoAccounts,
|
|
|
|
instanceTitle,
|
|
|
|
instanceDescriptionShort,
|
|
|
|
instanceDescription,
|
|
|
|
httpPrefix,
|
|
|
|
baseDir,
|
|
|
|
adminNickname,
|
|
|
|
domain,
|
|
|
|
domainFull,
|
|
|
|
registration,
|
|
|
|
systemLanguage,
|
|
|
|
projectVersion)
|
|
|
|
sendJsonStr = 'masto API instance metadata sent'
|
|
|
|
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
|
|
|
|
sendJson = ['mastodon.social', domainFull]
|
|
|
|
sendJsonStr = 'masto API peers metadata sent'
|
|
|
|
elif path.startswith('/api/v1/instance/activity'):
|
|
|
|
sendJson = []
|
|
|
|
sendJsonStr = 'masto API activity metadata sent'
|
|
|
|
return sendJson, sendJsonStr
|