epicyon/mastoapiv1.py

276 lines
10 KiB
Python

__filename__ = "mastoapiv1.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
__module_group__ = "API"
import os
from utils import loadJson
from utils import getConfigParam
from utils import acctDir
from utils import isAccountDir
from metadata import metaDataInstance
def _getMastApiV1Directory(baseDir: str, domain: str) -> []:
mastoDirectory = []
ctr = 1
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
for acct in dirs:
if not isAccountDir(acct):
continue
actorFilename = \
os.path.join(baseDir + '/accounts', acct + '.json')
if not os.path.isfile(actorFilename):
continue
actorJson = loadJson(actorFilename, 0, 1)
if not actorJson:
continue
if not actorJson.get('discoverable'):
continue
isGroup = False
if actorJson['type'] != 'Person' and actorJson['type'] != 'Group':
continue
if actorJson['type'] == 'Group':
isGroup = True
isLocked = False
if actorJson.get('manuallyApprovesFollowers'):
isLocked = True
isBot = False
acct = {
"id": ctr,
"username": actorJson['preferredUsername'],
"acct": actorJson['preferredUsername'] + "@" + domain,
"display_name": actorJson['name'],
"locked": isLocked,
"bot": isBot,
"discoverable": True,
"group": isGroup,
"created_at": actorJson['published'],
"note": actorJson['summary'],
"url": actorJson['url'],
"avatar": actorJson['icon']['url'],
"avatar_static": actorJson['icon']['url'],
"header": actorJson['image']['url'],
"header_static": actorJson['image']['url'],
"followers_count": 999,
"following_count": 999,
"statuses_count": 999,
"last_status_at": "2021-07-31",
"emojis": []
}
mastoDirectory.append(acct)
ctr += 1
break
return mastoDirectory
def _getMastApiV1Id(path: str) -> int:
"""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]
def _getMastoApiV1Account(baseDir: str, nickname: str, domain: str) -> {}:
"""See https://github.com/McKael/mastodon-documentation/
blob/master/Using-the-API/API.md#account
Authorization has already been performed
"""
accountFilename = acctDir(baseDir, nickname, domain) + '.json'
if not os.path.isfile(accountFilename):
return {}
accountJson = loadJson(accountFilename)
if not accountJson:
return {}
mastoAccountJson = {
"id": getMastoApiV1IdFromNickname(nickname),
"username": nickname,
"acct": nickname,
"display_name": accountJson['name'],
"locked": accountJson['manuallyApprovesFollowers'],
"created_at": "2016-10-05T10:30:00Z",
"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
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
if path.startswith('/api/v1/blocks'):
sendJson = []
sendJsonStr = 'masto API instance blocks sent'
elif path.startswith('/api/v1/favorites'):
sendJson = []
sendJsonStr = 'masto API favorites sent'
elif path.startswith('/api/v1/follow_requests'):
sendJson = []
sendJsonStr = 'masto API follow requests sent'
elif path.startswith('/api/v1/mutes'):
sendJson = []
sendJsonStr = 'masto API mutes sent'
elif path.startswith('/api/v1/notifications'):
sendJson = []
sendJsonStr = 'masto API notifications sent'
elif path.startswith('/api/v1/reports'):
sendJson = []
sendJsonStr = 'masto API reports sent'
elif path.startswith('/api/v1/directory'):
sendJson = _getMastApiV1Directory(baseDir, domain)
sendJsonStr = 'masto API directory sent'
elif path.startswith('/api/v1/statuses'):
sendJson = []
sendJsonStr = 'masto API statuses sent'
elif path.startswith('/api/v1/timelines'):
sendJson = []
sendJsonStr = 'masto API timelines sent'
elif path.startswith('/api/v1/custom_emojis'):
sendJson = customEmoji
sendJsonStr = 'masto API custom emojis sent'
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