followers_synchronization endpoint

main
Bob Mottram 2023-03-15 23:11:40 +00:00
parent d170f6222c
commit 99d3558a46
2 changed files with 104 additions and 2 deletions

View File

@ -119,6 +119,7 @@ from inbox import run_inbox_queue_watchdog
from inbox import save_post_to_inbox_queue from inbox import save_post_to_inbox_queue
from inbox import populate_replies from inbox import populate_replies
from inbox import receive_edit_to_post from inbox import receive_edit_to_post
from follow import get_followers_for_domain
from follow import follower_approval_active from follow import follower_approval_active
from follow import is_following_actor from follow import is_following_actor
from follow import get_following_feed from follow import get_following_feed
@ -876,6 +877,7 @@ class PubServer(BaseHTTPRequestHandler):
def _secure_mode(self, curr_session, proxy_type: str, def _secure_mode(self, curr_session, proxy_type: str,
force: bool = False) -> bool: force: bool = False) -> bool:
"""http authentication of GET requests for json """http authentication of GET requests for json
aka authorized fetch
""" """
if not self.server.secure_mode and not force: if not self.server.secure_mode and not force:
return True return True
@ -16901,11 +16903,63 @@ class PubServer(BaseHTTPRequestHandler):
'_GET', '_security_txt[calling_domain]', '_GET', '_security_txt[calling_domain]',
self.server.debug) self.server.debug)
# followers synchronization
if self.path.startswith('/users/') and \ if self.path.startswith('/users/') and \
self.path.endswith('/followers_synchronization'): self.path.endswith('/followers_synchronization'):
if self.server.followers_synchronization:
# only do one request at a time
self._503()
return
self.server.followers_synchronization = True
if self.server.debug:
print('DEBUG: followers synchronization request ' + print('DEBUG: followers synchronization request ' +
self.path + ' ' + calling_domain) self.path + ' ' + calling_domain)
# check authorized fetch
if self._secure_mode(curr_session, proxy_type):
nickname = get_nickname_from_actor(self.path)
sync_list = \
get_followers_for_domain(self.server.base_dir,
nickname, self.server.domain,
calling_domain)
id_str = self.server.http_prefix + '://' + \
self.server.domain_full + \
self.path.replace('/followers_synchronization',
'/followers?domain=' + calling_domain)
sync_json = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': id_str,
'orderedItems': sync_list,
'type': 'OrderedCollection'
}
msg_str = json.dumps(sync_json, ensure_ascii=False)
msg_str = self._convert_domains(calling_domain, referer_domain,
msg_str)
msg = msg_str.encode('utf-8')
msglen = len(msg)
self._set_headers('application/json', msglen,
None, calling_domain, False)
self._write(msg)
self.server.followers_synchronization = False
return
else:
# request was not signed
result_json = {
"error": "Request not signed"
}
msg_str = json.dumps(result_json, ensure_ascii=False)
msg = msg_str.encode('utf-8')
msglen = len(msg)
accept_str = self.headers['Accept']
if 'json' in accept_str:
protocol_str = \
get_json_content_from_accept(accept_str)
self._set_headers(protocol_str, msglen,
None, calling_domain, False)
self._write(msg)
self.server.followers_synchronization = False
return
self._404() self._404()
self.server.followers_synchronization = False
return return
if self.path == '/logout': if self.path == '/logout':
@ -23333,6 +23387,9 @@ def run_daemon(max_hashtags: int,
# scan the theme directory for any svg files containing scripts # scan the theme directory for any svg files containing scripts
assert not scan_themes_for_scripts(base_dir) assert not scan_themes_for_scripts(base_dir)
# lock for followers synchronization
httpd.followers_synchronization = False
# permitted sites from which the buy button may be displayed # permitted sites from which the buy button may be displayed
httpd.buy_sites = load_buy_sites(base_dir) httpd.buy_sites = load_buy_sites(base_dir)

View File

@ -9,6 +9,7 @@ __module_group__ = "ActivityPub"
from pprint import pprint from pprint import pprint
import os import os
from utils import get_user_paths
from utils import acct_handle_dir from utils import acct_handle_dir
from utils import has_object_string_object from utils import has_object_string_object
from utils import has_object_string_type from utils import has_object_string_type
@ -1360,6 +1361,50 @@ def deny_follow_request_via_server(session,
return deny_html return deny_html
def get_followers_for_domain(base_dir: str,
nickname: str, domain: str,
search_domain: str) -> []:
"""Returns the followers for a given domain
this is used for followers synchronization
"""
followers_filename = \
acct_dir(base_dir, nickname, domain) + '/followers.txt'
if not os.path.isfile(followers_filename):
return []
lines = []
try:
with open(followers_filename, 'r', encoding='utf-8') as fp_foll:
lines = fp_foll.read().splitlines()
except OSError:
print('EX: get_followers_for_domain unable to read followers ' +
followers_filename)
result = []
for line_str in lines:
if search_domain not in line_str:
continue
if line_str.endswith('@' + search_domain):
nick = line_str.split('@')[0]
paths_list = get_user_paths()
found = False
for prefix in ('https', 'http'):
if found:
break
for possible_path in paths_list:
url = prefix + '://' + search_domain + \
possible_path + nick
filename = base_dir + '/cache/actors/' + \
url.replace('/', '#') + '.json'
if not os.path.isfile(filename):
continue
if url not in result:
result.append(url)
found = True
break
elif '://' + search_domain in line_str:
result.append(line_str)
return result
def get_followers_of_actor(base_dir: str, actor: str, debug: bool) -> {}: def get_followers_of_actor(base_dir: str, actor: str, debug: bool) -> {}:
"""In a shared inbox if we receive a post we know who it's from """In a shared inbox if we receive a post we know who it's from
and if it's addressed to followers then we need to get a list of those. and if it's addressed to followers then we need to get a list of those.