mirror of https://gitlab.com/bashrc2/epicyon
				
				
				
			
		
			
				
	
	
		
			255 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Python
		
	
	
| __filename__ = "mastoapiv1.py"
 | |
| __author__ = "Bob Mottram"
 | |
| __license__ = "AGPL3+"
 | |
| __version__ = "1.3.0"
 | |
| __maintainer__ = "Bob Mottram"
 | |
| __email__ = "bob@libreserver.org"
 | |
| __status__ = "Production"
 | |
| __module_group__ = "API"
 | |
| 
 | |
| import os
 | |
| from utils import load_json
 | |
| from utils import get_config_param
 | |
| from utils import acct_dir
 | |
| from metadata import meta_data_instance
 | |
| 
 | |
| 
 | |
| def _get_mast_api_v1id(path: str) -> int:
 | |
|     """Extracts the mastodon Id number from the given path
 | |
|     """
 | |
|     masto_id = None
 | |
|     id_path = '/api/v1/accounts/:'
 | |
|     if not path.startswith(id_path):
 | |
|         return None
 | |
|     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
 | |
|     return None
 | |
| 
 | |
| 
 | |
| def get_masto_api_v1id_from_nickname(nickname: str) -> int:
 | |
|     """Given an account nickname return the corresponding mastodon id
 | |
|     """
 | |
|     return int.from_bytes(nickname.encode('utf-8'), 'little')
 | |
| 
 | |
| 
 | |
| def _int_to_bytes(num: int) -> str:
 | |
|     """Integer conversion
 | |
|     """
 | |
|     if num == 0:
 | |
|         return b""
 | |
|     return _int_to_bytes(num // 256) + bytes([num % 256])
 | |
| 
 | |
| 
 | |
| def get_nickname_from_masto_api_v1id(masto_id: int) -> str:
 | |
|     """Given the mastodon Id return the nickname
 | |
|     """
 | |
|     nickname = _int_to_bytes(masto_id).decode()
 | |
|     return nickname[::-1]
 | |
| 
 | |
| 
 | |
| def _get_masto_api_v1account(base_dir: 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
 | |
|     """
 | |
|     account_filename = acct_dir(base_dir, nickname, domain) + '.json'
 | |
|     if not os.path.isfile(account_filename):
 | |
|         return {}
 | |
|     account_json = load_json(account_filename)
 | |
|     if not account_json:
 | |
|         return {}
 | |
|     masto_account_json = {
 | |
|         "id": get_masto_api_v1id_from_nickname(nickname),
 | |
|         "username": nickname,
 | |
|         "acct": nickname,
 | |
|         "display_name": account_json['name'],
 | |
|         "locked": account_json['manuallyApprovesFollowers'],
 | |
|         "created_at": "2016-10-05T10:30:00Z",
 | |
|         "followers_count": 0,
 | |
|         "following_count": 0,
 | |
|         "statuses_count": 0,
 | |
|         "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']
 | |
|     }
 | |
|     return masto_account_json
 | |
| 
 | |
| 
 | |
| def masto_api_v1_response(path: str, calling_domain: str,
 | |
|                           ua_str: str,
 | |
|                           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,
 | |
|                           custom_emoji: [],
 | |
|                           show_node_info_accounts: bool,
 | |
|                           broch_mode: bool) -> ({}, str):
 | |
|     """This is a vestigil mastodon API for the purpose
 | |
|        of returning an empty result to sites like
 | |
|        https://mastopeek.app-dist.eu
 | |
|     """
 | |
|     send_json = None
 | |
|     send_json_str = ''
 | |
|     if not ua_str:
 | |
|         ua_str = ''
 | |
| 
 | |
|     # parts of the api needing authorization
 | |
|     if authorized and nickname:
 | |
|         if path == '/api/v1/accounts/verify_credentials':
 | |
|             send_json = _get_masto_api_v1account(base_dir, nickname, domain)
 | |
|             send_json_str = \
 | |
|                 'masto API account sent for ' + nickname + ' ' + ua_str
 | |
| 
 | |
|     # information about where the request is coming from
 | |
|     calling_info = ' ' + ua_str + ', ' + calling_domain
 | |
| 
 | |
|     # Parts of the api which don't need authorization
 | |
|     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
 | |
|             if '/followers?' in path or \
 | |
|                '/following?' in path or \
 | |
|                '/streaming/' in path or \
 | |
|                '/search?' in path or \
 | |
|                '/relationships?' in path or \
 | |
|                '/statuses?' in path:
 | |
|                 path = path.split('?')[0]
 | |
|             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'
 | |
|             if path.endswith('/followers'):
 | |
|                 send_json = []
 | |
|                 send_json_str = \
 | |
|                     'masto API followers sent for ' + nickname + \
 | |
|                     calling_info
 | |
|             elif path.endswith('/following'):
 | |
|                 send_json = []
 | |
|                 send_json_str = \
 | |
|                     'masto API following sent for ' + nickname + \
 | |
|                     calling_info
 | |
|             elif path.endswith('/statuses'):
 | |
|                 send_json = []
 | |
|                 send_json_str = \
 | |
|                     'masto API statuses sent for ' + nickname + \
 | |
|                     calling_info
 | |
|             elif path.endswith('/search'):
 | |
|                 send_json = []
 | |
|                 send_json_str = \
 | |
|                     'masto API search sent ' + original_path + \
 | |
|                     calling_info
 | |
|             elif path.endswith('/relationships'):
 | |
|                 send_json = []
 | |
|                 send_json_str = \
 | |
|                     'masto API relationships sent ' + original_path + \
 | |
|                     calling_info
 | |
|             else:
 | |
|                 send_json = \
 | |
|                     _get_masto_api_v1account(base_dir, path_nickname, domain)
 | |
|                 send_json_str = \
 | |
|                     'masto API account sent for ' + nickname + \
 | |
|                     calling_info
 | |
| 
 | |
|     # NOTE: adding support for '/api/v1/directory seems to create
 | |
|     # federation problems, so avoid implementing that
 | |
| 
 | |
|     if path.startswith('/api/v1/blocks'):
 | |
|         send_json = []
 | |
|         send_json_str = \
 | |
|             'masto API instance blocks sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/favorites'):
 | |
|         send_json = []
 | |
|         send_json_str = 'masto API favorites sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/follow_requests'):
 | |
|         send_json = []
 | |
|         send_json_str = \
 | |
|             'masto API follow requests sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/mutes'):
 | |
|         send_json = []
 | |
|         send_json_str = \
 | |
|             'masto API mutes sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/notifications'):
 | |
|         send_json = []
 | |
|         send_json_str = \
 | |
|             'masto API notifications sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/reports'):
 | |
|         send_json = []
 | |
|         send_json_str = 'masto API reports sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/statuses'):
 | |
|         send_json = []
 | |
|         send_json_str = 'masto API statuses sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/timelines'):
 | |
|         send_json = {
 | |
|             'error': 'This method requires an authenticated user'
 | |
|         }
 | |
|         send_json_str = 'masto API timelines sent ' + path + calling_info
 | |
|     elif path.startswith('/api/v1/custom_emojis'):
 | |
|         send_json = custom_emoji
 | |
|         send_json_str = \
 | |
|             'masto API custom emojis sent ' + path + calling_info
 | |
| 
 | |
|     admin_nickname = get_config_param(base_dir, 'admin')
 | |
|     if admin_nickname and path == '/api/v1/instance':
 | |
|         instance_description_short = \
 | |
|             get_config_param(base_dir, 'instanceDescriptionShort')
 | |
|         if not instance_description_short:
 | |
|             instance_description_short = \
 | |
|                 translate['Yet another Epicyon Instance']
 | |
|         instance_description = \
 | |
|             get_config_param(base_dir, 'instanceDescription')
 | |
|         instance_title = get_config_param(base_dir, 'instanceTitle')
 | |
| 
 | |
|         if calling_domain.endswith('.onion') and onion_domain:
 | |
|             domain_full = onion_domain
 | |
|             http_prefix = 'http'
 | |
|         elif (calling_domain.endswith('.i2p') and i2p_domain):
 | |
|             domain_full = i2p_domain
 | |
|             http_prefix = 'http'
 | |
| 
 | |
|         if broch_mode:
 | |
|             show_node_info_accounts = False
 | |
| 
 | |
|         send_json = \
 | |
|             meta_data_instance(show_node_info_accounts,
 | |
|                                instance_title,
 | |
|                                instance_description_short,
 | |
|                                instance_description,
 | |
|                                http_prefix,
 | |
|                                base_dir,
 | |
|                                admin_nickname,
 | |
|                                domain,
 | |
|                                domain_full,
 | |
|                                registration,
 | |
|                                system_language,
 | |
|                                project_version)
 | |
|         send_json_str = 'masto API instance metadata sent ' + ua_str
 | |
|     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
 | |
|         send_json = ['mastodon.social', domain_full]
 | |
|         send_json_str = 'masto API peers metadata sent ' + ua_str
 | |
|     elif path.startswith('/api/v1/instance/activity'):
 | |
|         send_json = []
 | |
|         send_json_str = 'masto API activity metadata sent ' + ua_str
 | |
|     return send_json, send_json_str
 |