epicyon/follow.py

1638 lines
62 KiB
Python
Raw Normal View History

2020-04-03 11:38:44 +00:00
__filename__ = "follow.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2024-01-21 19:01:20 +00:00
__version__ = "1.5.0"
2020-04-03 11:38:44 +00:00
__maintainer__ = "Bob Mottram"
2021-09-10 16:14:50 +00:00
__email__ = "bob@libreserver.org"
2020-04-03 11:38:44 +00:00
__status__ = "Production"
2021-06-15 15:08:12 +00:00
__module_group__ = "ActivityPub"
2020-04-03 11:38:44 +00:00
2019-06-29 18:23:13 +00:00
import os
2023-03-17 21:13:43 +00:00
from pprint import pprint
from flags import has_group_type
2023-03-15 23:11:40 +00:00
from utils import get_user_paths
2022-12-18 15:29:54 +00:00
from utils import acct_handle_dir
2021-12-26 15:54:46 +00:00
from utils import has_object_string_object
2022-04-09 15:11:22 +00:00
from utils import has_object_string_type
2021-12-26 18:17:37 +00:00
from utils import remove_domain_port
2021-12-26 12:19:00 +00:00
from utils import has_users_path
2021-12-26 12:45:03 +00:00
from utils import get_full_domain
2021-12-28 14:41:10 +00:00
from utils import valid_nickname
2021-12-27 18:28:26 +00:00
from utils import domain_permitted
2021-12-27 19:05:25 +00:00
from utils import get_domain_from_actor
2021-12-27 22:19:18 +00:00
from utils import get_nickname_from_actor
2021-12-27 17:42:35 +00:00
from utils import get_status_number
2021-12-27 17:08:19 +00:00
from utils import follow_person
2021-12-29 21:55:09 +00:00
from posts import send_signed_json
from posts import get_person_box
2021-12-26 15:13:34 +00:00
from utils import load_json
2021-12-26 14:47:21 +00:00
from utils import save_json
2021-12-26 18:46:43 +00:00
from utils import is_account_dir
2021-12-26 12:02:29 +00:00
from utils import acct_dir
2021-12-26 10:19:59 +00:00
from utils import local_actor_url
2022-06-10 11:43:33 +00:00
from utils import text_in_file
2022-06-21 11:58:50 +00:00
from utils import remove_eol
2024-01-09 16:59:23 +00:00
from utils import get_actor_from_post
2024-05-12 12:35:26 +00:00
from utils import data_dir
2021-12-29 21:55:09 +00:00
from acceptreject import create_accept
from acceptreject import create_reject
from webfinger import webfinger_handle
2021-12-28 21:36:27 +00:00
from auth import create_basic_auth_header
2021-12-29 21:55:09 +00:00
from session import get_json
2023-08-13 09:58:02 +00:00
from session import get_json_valid
2021-12-29 21:55:09 +00:00
from session import post_json
from followerSync import remove_followers_sync
2019-06-29 18:23:13 +00:00
2020-04-03 11:38:44 +00:00
2021-12-28 20:32:11 +00:00
def create_initial_last_seen(base_dir: str, http_prefix: str) -> None:
2021-06-07 08:59:43 +00:00
"""Creates initial lastseen files for all follows.
The lastseen files are used to generate the Zzz icons on
follows/following lists on the profile screen.
2020-12-13 22:01:10 +00:00
"""
2024-05-12 12:35:26 +00:00
dir_str = data_dir(base_dir)
for _, dirs, _ in os.walk(dir_str):
2020-12-13 22:01:10 +00:00
for acct in dirs:
2021-12-26 18:46:43 +00:00
if not is_account_dir(acct):
2020-12-13 22:01:10 +00:00
continue
2024-05-12 12:35:26 +00:00
account_dir = os.path.join(dir_str, acct)
2022-01-02 14:51:02 +00:00
following_filename = account_dir + '/following.txt'
if not os.path.isfile(following_filename):
2020-12-13 22:01:10 +00:00
continue
2022-01-02 14:51:02 +00:00
last_seen_dir = account_dir + '/lastseen'
if not os.path.isdir(last_seen_dir):
os.mkdir(last_seen_dir)
following_handles = []
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(following_filename, 'r',
encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
following_handles = fp_foll.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: create_initial_last_seen ' + following_filename)
for handle in following_handles:
2021-11-26 14:35:26 +00:00
if '#' in handle:
continue
if '@' not in handle:
continue
2022-06-21 11:58:50 +00:00
handle = remove_eol(handle)
2021-11-26 14:35:26 +00:00
nickname = handle.split('@')[0]
domain = handle.split('@')[1]
if nickname.startswith('!'):
nickname = nickname[1:]
2021-12-26 10:19:59 +00:00
actor = local_actor_url(http_prefix, nickname, domain)
2022-01-02 14:51:02 +00:00
last_seen_filename = \
last_seen_dir + '/' + actor.replace('/', '#') + '.txt'
2024-07-11 14:02:03 +00:00
if os.path.isfile(last_seen_filename):
continue
try:
with open(last_seen_filename, 'w+',
encoding='utf-8') as fp_last:
fp_last.write(str(100))
except OSError:
print('EX: create_initial_last_seen 2 ' +
last_seen_filename)
2020-12-13 22:01:10 +00:00
break
2021-12-29 21:55:09 +00:00
def _pre_approved_follower(base_dir: str,
nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
approve_handle: str) -> bool:
2019-12-31 09:23:41 +00:00
"""Is the given handle an already manually approved follower?
"""
2022-09-10 09:24:13 +00:00
account_dir = acct_dir(base_dir, nickname, domain)
2022-01-02 14:51:02 +00:00
approved_filename = account_dir + '/approved.txt'
if os.path.isfile(approved_filename):
2022-06-10 13:01:39 +00:00
if text_in_file(approve_handle, approved_filename):
2019-12-31 09:23:41 +00:00
return True
return False
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def _remove_from_follow_base(base_dir: str,
nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
accept_or_deny_handle: str, follow_file: str,
2021-12-29 21:55:09 +00:00
debug: bool) -> None:
"""Removes a handle/actor from follow requests or rejects file
2019-09-18 17:04:19 +00:00
"""
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, domain)
2022-01-02 14:51:02 +00:00
approve_follows_filename = accounts_dir + '/' + follow_file + '.txt'
if not os.path.isfile(approve_follows_filename):
2019-09-18 17:04:19 +00:00
if debug:
2022-03-13 12:09:52 +00:00
print('There is no ' + follow_file +
2022-09-10 09:24:13 +00:00
' to remove ' + nickname + '@' + domain + ' from')
2019-09-18 17:04:19 +00:00
return
2022-01-02 14:51:02 +00:00
accept_deny_actor = None
2022-06-10 11:43:33 +00:00
if not text_in_file(accept_or_deny_handle, approve_follows_filename):
# is this stored in the file as an actor rather than a handle?
2022-01-02 14:51:02 +00:00
accept_deny_nickname = accept_or_deny_handle.split('@')[0]
accept_deny_domain = accept_or_deny_handle.split('@')[1]
# for each possible users path construct an actor and
2024-02-27 11:38:09 +00:00
# check if it exists in the file
2022-01-02 14:51:02 +00:00
users_paths = get_user_paths()
actor_found = False
for users_name in users_paths:
accept_deny_actor = \
'://' + accept_deny_domain + users_name + accept_deny_nickname
2022-06-10 13:01:39 +00:00
if text_in_file(accept_deny_actor, approve_follows_filename):
2022-01-02 14:51:02 +00:00
actor_found = True
break
2023-05-10 11:53:38 +00:00
if not actor_found:
accept_deny_actor = \
'://' + accept_deny_domain + '/' + accept_deny_nickname
if text_in_file(accept_deny_actor, approve_follows_filename):
actor_found = True
2022-01-02 14:51:02 +00:00
if not actor_found:
return
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(approve_follows_filename + '.new', 'w+',
2024-07-16 12:20:58 +00:00
encoding='utf-8') as fp_approve_new:
2022-06-09 14:46:30 +00:00
with open(approve_follows_filename, 'r',
2024-07-16 11:21:28 +00:00
encoding='utf-8') as fp_approve:
2022-01-02 14:51:02 +00:00
if not accept_deny_actor:
2024-07-16 11:21:28 +00:00
for approve_handle in fp_approve:
2022-01-02 14:51:02 +00:00
accept_deny_handle = accept_or_deny_handle
if not approve_handle.startswith(accept_deny_handle):
2024-07-16 12:20:58 +00:00
fp_approve_new.write(approve_handle)
2021-11-26 14:35:26 +00:00
else:
2024-07-16 11:21:28 +00:00
for approve_handle in fp_approve:
2022-01-02 14:51:02 +00:00
if accept_deny_actor not in approve_handle:
2024-07-16 12:20:58 +00:00
fp_approve_new.write(approve_handle)
2021-12-25 15:28:52 +00:00
except OSError as ex:
2021-12-29 21:55:09 +00:00
print('EX: _remove_from_follow_base ' +
2022-01-02 14:51:02 +00:00
approve_follows_filename + ' ' + str(ex))
2021-06-22 12:27:10 +00:00
2022-01-02 14:51:02 +00:00
os.rename(approve_follows_filename + '.new', approve_follows_filename)
2020-04-03 11:38:44 +00:00
2019-09-18 17:04:19 +00:00
2021-12-29 21:55:09 +00:00
def remove_from_follow_requests(base_dir: str,
nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
deny_handle: str, debug: bool) -> None:
2019-10-06 09:48:37 +00:00
"""Removes a handle from follow requests
"""
2021-12-29 21:55:09 +00:00
_remove_from_follow_base(base_dir, nickname, domain,
2022-01-02 14:51:02 +00:00
deny_handle, 'followrequests', debug)
2019-10-06 09:48:37 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def _remove_from_follow_rejects(base_dir: str,
nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
accept_handle: str, debug: bool) -> None:
2019-10-06 09:48:37 +00:00
"""Removes a handle from follow rejects
"""
2021-12-29 21:55:09 +00:00
_remove_from_follow_base(base_dir, nickname, domain,
2022-01-02 14:51:02 +00:00
accept_handle, 'followrejects', debug)
2020-04-03 11:38:44 +00:00
2019-10-06 09:48:37 +00:00
2021-12-28 20:32:11 +00:00
def is_following_actor(base_dir: str,
nickname: str, domain: str, actor: str) -> bool:
2020-07-14 09:09:33 +00:00
"""Is the given nickname following the given actor?
The actor can also be a handle: nickname@domain
2019-07-29 19:46:30 +00:00
"""
2021-12-26 18:17:37 +00:00
domain = remove_domain_port(domain)
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, domain)
if not os.path.isdir(accounts_dir):
2019-07-29 19:46:30 +00:00
return False
2022-09-10 09:24:13 +00:00
following_file = accounts_dir + '/following.txt'
2022-01-02 14:51:02 +00:00
if not os.path.isfile(following_file):
2019-07-29 19:46:30 +00:00
return False
if actor.startswith('@'):
actor = actor[1:]
2022-06-10 13:01:39 +00:00
if text_in_file(actor, following_file, False):
2019-07-29 19:46:30 +00:00
return True
2022-01-02 14:51:02 +00:00
following_nickname = get_nickname_from_actor(actor)
if not following_nickname:
2020-04-03 11:38:44 +00:00
print('WARN: unable to find nickname in ' + actor)
2019-09-02 09:43:43 +00:00
return False
2022-01-02 14:51:02 +00:00
following_domain, following_port = get_domain_from_actor(actor)
2023-01-15 14:33:18 +00:00
if not following_domain:
print('WARN: unable to find domain in ' + actor)
return False
2022-01-02 14:51:02 +00:00
following_handle = \
get_full_domain(following_nickname + '@' + following_domain,
following_port)
2022-06-10 13:01:39 +00:00
if text_in_file(following_handle, following_file, False):
2019-07-29 19:46:30 +00:00
return True
return False
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def add_follower_of_person(base_dir: str, nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
follower_nickname: str, follower_domain: str,
2021-12-29 21:55:09 +00:00
federation_list: [], debug: bool,
group_account: bool) -> bool:
"""Adds a follower of the given person
"""
2021-12-27 17:08:19 +00:00
return follow_person(base_dir, nickname, domain,
2022-01-02 14:51:02 +00:00
follower_nickname, follower_domain,
2021-12-27 17:08:19 +00:00
federation_list, debug, group_account,
'followers.txt')
2020-04-03 11:38:44 +00:00
2019-06-29 18:23:13 +00:00
2021-12-29 21:55:09 +00:00
def get_follower_domains(base_dir: str, nickname: str, domain: str) -> []:
"""Returns a list of domains for followers
"""
2021-12-26 18:17:37 +00:00
domain = remove_domain_port(domain)
2022-01-02 14:51:02 +00:00
followers_file = acct_dir(base_dir, nickname, domain) + '/followers.txt'
if not os.path.isfile(followers_file):
return []
lines = []
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(followers_file, 'r', encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
lines = fp_foll.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: get_follower_domains ' + followers_file)
2022-01-02 14:51:02 +00:00
domains_list = []
for handle in lines:
2022-06-21 11:58:50 +00:00
handle = remove_eol(handle)
2022-01-02 14:51:02 +00:00
follower_domain, _ = get_domain_from_actor(handle)
if not follower_domain:
continue
2022-01-02 14:51:02 +00:00
if follower_domain not in domains_list:
domains_list.append(follower_domain)
return domains_list
2021-12-29 21:55:09 +00:00
def is_follower_of_person(base_dir: str, nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
follower_nickname: str,
follower_domain: str) -> bool:
"""is the given nickname a follower of follower_nickname?
2019-08-31 15:17:07 +00:00
"""
2022-01-02 14:51:02 +00:00
if not follower_domain:
print('No follower_domain')
2021-09-13 13:41:29 +00:00
return False
2022-01-02 14:51:02 +00:00
if not follower_nickname:
print('No follower_nickname for ' + follower_domain)
2021-09-13 13:41:29 +00:00
return False
2021-12-26 18:17:37 +00:00
domain = remove_domain_port(domain)
2022-01-02 14:51:02 +00:00
followers_file = acct_dir(base_dir, nickname, domain) + '/followers.txt'
if not os.path.isfile(followers_file):
2019-08-31 15:17:07 +00:00
return False
2022-01-02 14:51:02 +00:00
handle = follower_nickname + '@' + follower_domain
2022-01-02 14:51:02 +00:00
already_following = False
2022-01-02 14:51:02 +00:00
followers_str = ''
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(followers_file, 'r', encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
followers_str = fp_foll.read()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: is_follower_of_person ' + followers_file)
2022-01-02 14:51:02 +00:00
if handle in followers_str:
already_following = True
2021-07-03 17:51:58 +00:00
else:
2021-12-26 12:24:40 +00:00
paths = get_user_paths()
2022-01-02 14:51:02 +00:00
for user_path in paths:
url = '://' + follower_domain + user_path + follower_nickname
if url in followers_str:
already_following = True
2021-07-03 17:51:58 +00:00
break
2023-05-10 11:53:38 +00:00
if not already_following:
url = '://' + follower_domain + '/' + follower_nickname
if url in followers_str:
already_following = True
2022-01-02 14:51:02 +00:00
return already_following
2019-08-31 15:17:07 +00:00
2020-04-03 11:38:44 +00:00
2021-12-28 20:32:11 +00:00
def unfollow_account(base_dir: str, nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
follow_nickname: str, follow_domain: str,
2021-12-28 20:32:11 +00:00
debug: bool, group_account: bool,
2024-02-19 15:38:08 +00:00
follow_file: str) -> bool:
2019-06-29 18:23:13 +00:00
"""Removes a person to the follow list
"""
2021-12-26 18:17:37 +00:00
domain = remove_domain_port(domain)
2020-04-03 11:38:44 +00:00
handle = nickname + '@' + domain
2022-01-02 14:51:02 +00:00
handle_to_unfollow = follow_nickname + '@' + follow_domain
2021-12-26 00:07:44 +00:00
if group_account:
2022-01-02 14:51:02 +00:00
handle_to_unfollow = '!' + handle_to_unfollow
2024-05-12 12:35:26 +00:00
dir_str = data_dir(base_dir)
if not os.path.isdir(dir_str):
os.mkdir(dir_str)
2022-12-18 15:29:54 +00:00
handle_dir = acct_handle_dir(base_dir, handle)
if not os.path.isdir(handle_dir):
os.mkdir(handle_dir)
2020-04-03 11:38:44 +00:00
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, domain)
filename = accounts_dir + '/' + follow_file
2019-07-17 10:34:00 +00:00
if not os.path.isfile(filename):
2019-07-17 11:54:13 +00:00
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: follow file ' + filename + ' was not found')
2019-07-17 10:34:00 +00:00
return False
2022-01-02 14:51:02 +00:00
handle_to_unfollow_lower = handle_to_unfollow.lower()
2022-06-10 11:43:33 +00:00
if not text_in_file(handle_to_unfollow_lower, filename, False):
2019-07-17 11:54:13 +00:00
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: handle to unfollow ' + handle_to_unfollow +
2020-04-03 11:38:44 +00:00
' is not in ' + filename)
return False
2021-11-26 14:35:26 +00:00
lines = []
try:
2022-06-09 14:46:30 +00:00
with open(filename, 'r', encoding='utf-8') as fp_unfoll:
2022-01-02 14:51:02 +00:00
lines = fp_unfoll.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2021-12-28 20:32:11 +00:00
print('EX: unfollow_account ' + filename)
2021-11-26 14:35:26 +00:00
if lines:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(filename, 'w+', encoding='utf-8') as fp_unfoll:
2021-11-25 21:18:53 +00:00
for line in lines:
2022-01-02 14:51:02 +00:00
check_handle = line.strip("\n").strip("\r").lower()
if check_handle not in (handle_to_unfollow_lower,
'!' + handle_to_unfollow_lower):
fp_unfoll.write(line)
2021-12-25 15:28:52 +00:00
except OSError as ex:
2024-07-02 22:16:13 +00:00
print('EX: unfollow_account unable to write ' +
filename + ' ' + str(ex))
# write to an unfollowed file so that if a follow accept
# later arrives then it can be ignored
2022-09-10 09:24:13 +00:00
unfollowed_filename = accounts_dir + '/unfollowed.txt'
2022-01-02 14:51:02 +00:00
if os.path.isfile(unfollowed_filename):
if not text_in_file(handle_to_unfollow_lower,
unfollowed_filename, False):
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(unfollowed_filename, 'a+',
encoding='utf-8') as fp_unfoll:
2022-01-02 14:51:02 +00:00
fp_unfoll.write(handle_to_unfollow + '\n')
2021-11-26 14:35:26 +00:00
except OSError:
2024-07-02 22:16:13 +00:00
print('EX: unfollow_account unable to append ' +
unfollowed_filename)
else:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(unfollowed_filename, 'w+',
encoding='utf-8') as fp_unfoll:
2022-01-02 14:51:02 +00:00
fp_unfoll.write(handle_to_unfollow + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2024-07-02 22:16:13 +00:00
print('EX: unfollow_account unable to write ' +
unfollowed_filename)
2019-10-30 21:44:16 +00:00
return True
2019-06-29 18:23:13 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def unfollower_of_account(base_dir: str, nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
follower_nickname: str, follower_domain: str,
2021-12-29 21:55:09 +00:00
debug: bool, group_account: bool) -> bool:
"""Remove a follower of a person
"""
2021-12-28 20:32:11 +00:00
return unfollow_account(base_dir, nickname, domain,
2022-01-02 14:51:02 +00:00
follower_nickname, follower_domain,
2021-12-28 20:32:11 +00:00
debug, group_account, 'followers.txt')
2019-06-29 18:23:13 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def clear_follows(base_dir: str, nickname: str, domain: str,
2024-02-19 15:38:08 +00:00
follow_file: str) -> None:
2019-06-29 18:23:13 +00:00
"""Removes all follows
"""
2024-05-12 12:35:26 +00:00
dir_str = data_dir(base_dir)
if not os.path.isdir(dir_str):
os.mkdir(dir_str)
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, domain)
if not os.path.isdir(accounts_dir):
os.mkdir(accounts_dir)
filename = accounts_dir + '/' + follow_file
2019-06-29 18:23:13 +00:00
if os.path.isfile(filename):
try:
os.remove(filename)
2021-11-25 18:42:38 +00:00
except OSError:
2021-12-29 21:55:09 +00:00
print('EX: clear_follows unable to delete ' + filename)
2019-06-29 18:23:13 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def clear_followers(base_dir: str, nickname: str, domain: str) -> None:
"""Removes all followers
"""
2021-12-29 21:55:09 +00:00
clear_follows(base_dir, nickname, domain, 'followers.txt')
2020-04-03 11:38:44 +00:00
2019-06-29 20:21:37 +00:00
2021-12-29 21:55:09 +00:00
def _get_no_of_follows(base_dir: str, nickname: str, domain: str,
2022-01-02 14:51:02 +00:00
follow_file='following.txt') -> int:
"""Returns the number of follows or followers
"""
# only show number of followers to authenticated
# account holders
2020-04-03 11:38:44 +00:00
# if not authenticated:
# return 9999
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, domain)
filename = accounts_dir + '/' + follow_file
2019-06-29 20:21:37 +00:00
if not os.path.isfile(filename):
return 0
2020-04-03 11:38:44 +00:00
ctr = 0
2021-11-26 14:35:26 +00:00
lines = []
try:
2022-06-09 14:46:30 +00:00
with open(filename, 'r', encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
lines = fp_foll.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2021-12-29 21:55:09 +00:00
print('EX: _get_no_of_follows ' + filename)
2021-11-26 14:35:26 +00:00
if lines:
2019-06-29 20:21:37 +00:00
for line in lines:
if '#' in line:
continue
if '@' in line and \
'.' in line and \
not line.startswith('http'):
ctr += 1
elif ((line.startswith('http') or
2022-04-29 13:54:13 +00:00
line.startswith('ipfs') or
line.startswith('ipns') or
2021-07-01 17:59:24 +00:00
line.startswith('hyper')) and
2021-12-26 12:19:00 +00:00
has_users_path(line)):
ctr += 1
2019-06-29 20:21:37 +00:00
return ctr
2020-04-03 11:38:44 +00:00
def get_no_of_followers(base_dir: str, nickname: str, domain: str) -> int:
"""Returns the number of followers of the given person
"""
return _get_no_of_follows(base_dir, nickname, domain, 'followers.txt')
2020-04-03 11:38:44 +00:00
2019-06-29 20:21:37 +00:00
2021-12-28 20:32:11 +00:00
def get_following_feed(base_dir: str, domain: str, port: int, path: str,
http_prefix: str, authorized: bool,
follows_per_page=12,
2022-01-02 14:51:02 +00:00
follow_file='following') -> {}:
2020-10-24 09:28:21 +00:00
"""Returns the following and followers feeds from GET requests.
This accesses the following.txt or followers.txt and builds a collection.
"""
2021-03-24 13:15:43 +00:00
# Show a small number of follows to non-authorized viewers
if not authorized:
2021-12-28 16:00:24 +00:00
follows_per_page = 6
2022-01-02 14:51:02 +00:00
if '/' + follow_file not in path:
2019-06-29 20:21:37 +00:00
return None
# handle page numbers
2022-01-02 14:51:02 +00:00
header_only = True
page_number = None
2019-06-29 20:21:37 +00:00
if '?page=' in path:
2022-01-02 14:51:02 +00:00
page_number = path.split('?page=')[1]
if len(page_number) > 5:
page_number = "1"
2022-01-02 14:51:02 +00:00
if page_number == 'true' or not authorized:
page_number = 1
2019-06-29 20:21:37 +00:00
else:
try:
2022-01-02 14:51:02 +00:00
page_number = int(page_number)
2020-04-03 11:38:44 +00:00
except BaseException:
2021-12-28 20:32:11 +00:00
print('EX: get_following_feed unable to convert to int ' +
2022-01-02 14:51:02 +00:00
str(page_number))
2020-04-03 11:38:44 +00:00
path = path.split('?page=')[0]
2022-01-02 14:51:02 +00:00
header_only = False
2020-03-22 21:16:02 +00:00
2022-01-02 14:51:02 +00:00
if not path.endswith('/' + follow_file):
2019-06-29 20:21:37 +00:00
return None
2020-04-03 11:38:44 +00:00
nickname = None
2019-06-29 20:21:37 +00:00
if path.startswith('/users/'):
2022-01-02 14:51:02 +00:00
nickname = \
path.replace('/users/', '', 1).replace('/' + follow_file, '')
2019-06-29 20:21:37 +00:00
if path.startswith('/@'):
2022-01-02 14:51:02 +00:00
nickname = path.replace('/@', '', 1).replace('/' + follow_file, '')
2019-07-03 09:40:27 +00:00
if not nickname:
2019-06-29 20:21:37 +00:00
return None
2021-12-28 14:41:10 +00:00
if not valid_nickname(domain, nickname):
2019-06-29 20:21:37 +00:00
return None
2021-12-26 12:45:03 +00:00
domain = get_full_domain(domain, port)
2019-06-30 19:01:43 +00:00
2022-01-02 14:51:02 +00:00
if header_only:
first_str = \
2021-12-26 10:19:59 +00:00
local_actor_url(http_prefix, nickname, domain) + \
2022-01-02 14:51:02 +00:00
'/' + follow_file + '?page=1'
id_str = \
local_actor_url(http_prefix, nickname, domain) + '/' + follow_file
total_str = \
_get_no_of_follows(base_dir, nickname, domain)
2020-04-03 11:38:44 +00:00
following = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
2022-01-02 14:51:02 +00:00
'first': first_str,
'id': id_str,
'totalItems': total_str,
2020-03-22 20:36:19 +00:00
'type': 'OrderedCollection'
}
2019-06-29 20:21:37 +00:00
return following
2022-01-02 14:51:02 +00:00
if not page_number:
page_number = 1
2020-04-03 11:38:44 +00:00
2022-01-02 14:51:02 +00:00
next_page_number = int(page_number + 1)
id_str = \
2021-12-26 10:19:59 +00:00
local_actor_url(http_prefix, nickname, domain) + \
2022-01-02 14:51:02 +00:00
'/' + follow_file + '?page=' + str(page_number)
part_of_str = \
local_actor_url(http_prefix, nickname, domain) + '/' + follow_file
2020-04-03 11:38:44 +00:00
following = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
2022-01-02 14:51:02 +00:00
'id': id_str,
2019-06-29 20:21:37 +00:00
'orderedItems': [],
2022-01-02 14:51:02 +00:00
'partOf': part_of_str,
2019-06-29 20:21:37 +00:00
'totalItems': 0,
2020-03-22 20:36:19 +00:00
'type': 'OrderedCollectionPage'
}
2019-06-29 20:21:37 +00:00
2022-01-02 14:51:02 +00:00
handle_domain = domain
handle_domain = remove_domain_port(handle_domain)
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname, handle_domain)
filename = accounts_dir + '/' + follow_file + '.txt'
2019-06-29 20:21:37 +00:00
if not os.path.isfile(filename):
return following
2022-01-02 14:51:02 +00:00
curr_page = 1
page_ctr = 0
total_ctr = 0
2021-11-26 14:35:26 +00:00
lines = []
try:
2022-06-09 14:46:30 +00:00
with open(filename, 'r', encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
lines = fp_foll.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2021-12-28 20:32:11 +00:00
print('EX: get_following_feed ' + filename)
2021-11-26 14:35:26 +00:00
for line in lines:
if '#' not in line:
if '@' in line and not line.startswith('http'):
# nickname@domain
2022-01-02 14:51:02 +00:00
page_ctr += 1
total_ctr += 1
if curr_page == page_number:
2022-06-21 11:58:50 +00:00
line2_lower = line.lower()
line2 = remove_eol(line2_lower)
2021-11-26 14:35:26 +00:00
nick = line2.split('@')[0]
dom = line2.split('@')[1]
if not nick.startswith('!'):
# person actor
2021-12-26 10:19:59 +00:00
url = local_actor_url(http_prefix, nick, dom)
2021-11-26 14:35:26 +00:00
else:
# group actor
2021-12-25 17:09:22 +00:00
url = http_prefix + '://' + dom + '/c/' + nick
2021-11-26 14:35:26 +00:00
following['orderedItems'].append(url)
elif ((line.startswith('http') or
2022-04-29 13:54:13 +00:00
line.startswith('ipfs') or
line.startswith('ipns') or
2021-11-26 14:35:26 +00:00
line.startswith('hyper')) and
2021-12-26 12:19:00 +00:00
has_users_path(line)):
2021-11-26 14:35:26 +00:00
# https://domain/users/nickname
2022-01-02 14:51:02 +00:00
page_ctr += 1
total_ctr += 1
if curr_page == page_number:
2022-06-21 11:58:50 +00:00
append_str1 = line.lower()
append_str = remove_eol(append_str1)
2022-01-02 14:51:02 +00:00
following['orderedItems'].append(append_str)
if page_ctr >= follows_per_page:
page_ctr = 0
curr_page += 1
following['totalItems'] = total_ctr
last_page = int(total_ctr / follows_per_page)
2022-05-30 21:41:18 +00:00
last_page = max(last_page, 1)
2022-01-02 14:51:02 +00:00
if next_page_number > last_page:
2020-04-03 11:38:44 +00:00
following['next'] = \
2021-12-26 10:19:59 +00:00
local_actor_url(http_prefix, nickname, domain) + \
2022-01-02 14:51:02 +00:00
'/' + follow_file + '?page=' + str(last_page)
2019-06-29 20:21:37 +00:00
return following
2019-07-02 18:17:04 +00:00
2020-04-03 11:38:44 +00:00
2022-01-02 14:51:02 +00:00
def follow_approval_required(base_dir: str, nickname_to_follow: str,
domain_to_follow: str, debug: bool,
follow_request_handle: str) -> bool:
2019-07-19 20:03:50 +00:00
""" Returns the policy for follower approvals
"""
2020-01-02 22:42:06 +00:00
# has this handle already been manually approved?
2022-01-02 14:51:02 +00:00
if _pre_approved_follower(base_dir, nickname_to_follow, domain_to_follow,
follow_request_handle):
2019-12-31 09:23:41 +00:00
return False
2022-01-02 14:51:02 +00:00
manually_approve_follows = False
domain_to_follow = remove_domain_port(domain_to_follow)
2024-05-12 12:35:26 +00:00
actor_filename = data_dir(base_dir) + '/' + \
2022-01-02 14:51:02 +00:00
nickname_to_follow + '@' + domain_to_follow + '.json'
if os.path.isfile(actor_filename):
actor = load_json(actor_filename)
2019-09-30 22:39:02 +00:00
if actor:
2022-02-28 19:37:33 +00:00
if 'manuallyApprovesFollowers' in actor:
2022-01-02 14:51:02 +00:00
manually_approve_follows = actor['manuallyApprovesFollowers']
2019-07-19 20:03:50 +00:00
else:
if debug:
2022-01-02 14:51:02 +00:00
print(nickname_to_follow + '@' + domain_to_follow +
2020-04-03 11:38:44 +00:00
' automatically approves followers')
2019-07-19 20:03:50 +00:00
else:
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: Actor file not found: ' + actor_filename)
return manually_approve_follows
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def no_of_follow_requests(base_dir: str,
2022-01-02 14:51:02 +00:00
nickname_to_follow: str, domain_to_follow: str,
follow_type: str) -> int:
"""Returns the current number of follow requests
"""
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname_to_follow, domain_to_follow)
2022-01-02 14:51:02 +00:00
approve_follows_filename = accounts_dir + '/followrequests.txt'
if not os.path.isfile(approve_follows_filename):
return 0
2020-04-03 11:38:44 +00:00
ctr = 0
2021-11-26 14:35:26 +00:00
lines = []
try:
2022-06-09 14:46:30 +00:00
with open(approve_follows_filename, 'r',
encoding='utf-8') as fp_approve:
2022-01-02 14:51:02 +00:00
lines = fp_approve.readlines()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: no_of_follow_requests ' + approve_follows_filename)
2021-11-26 14:35:26 +00:00
if lines:
2022-01-02 14:51:02 +00:00
if follow_type == "onion":
for file_line in lines:
if '.onion' in file_line:
2020-06-03 20:21:44 +00:00
ctr += 1
2022-01-02 14:51:02 +00:00
elif follow_type == "i2p":
for file_line in lines:
if '.i2p' in file_line:
2020-06-03 20:21:44 +00:00
ctr += 1
else:
return len(lines)
return ctr
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def store_follow_request(base_dir: str,
2022-01-02 14:51:02 +00:00
nickname_to_follow: str,
domain_to_follow: str, port: int,
nickname: str, domain: str, from_port: int,
follow_json: {},
debug: bool, person_url: str,
2021-12-29 21:55:09 +00:00
group_account: bool) -> bool:
"""Stores the follow request for later use
"""
2022-09-10 09:24:13 +00:00
accounts_dir = acct_dir(base_dir, nickname_to_follow, domain_to_follow)
2022-01-02 14:51:02 +00:00
if not os.path.isdir(accounts_dir):
return False
2022-01-02 14:51:02 +00:00
domain_full = get_full_domain(domain, from_port)
approve_handle = get_full_domain(nickname + '@' + domain, from_port)
2021-12-26 00:07:44 +00:00
if group_account:
2022-01-02 14:51:02 +00:00
approve_handle = '!' + approve_handle
2022-01-02 14:51:02 +00:00
followers_filename = accounts_dir + '/followers.txt'
if os.path.isfile(followers_filename):
already_following = False
2022-01-02 14:51:02 +00:00
followers_str = ''
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(followers_filename, 'r',
encoding='utf-8') as fp_foll:
2022-01-02 14:51:02 +00:00
followers_str = fp_foll.read()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: store_follow_request ' + followers_filename)
2022-01-02 14:51:02 +00:00
if approve_handle in followers_str:
already_following = True
2021-07-30 19:20:49 +00:00
else:
2022-01-02 14:51:02 +00:00
users_paths = get_user_paths()
for possible_users_path in users_paths:
url = '://' + domain_full + possible_users_path + nickname
if url in followers_str:
already_following = True
2021-07-30 19:20:49 +00:00
break
2023-05-10 11:53:38 +00:00
if not already_following:
url = '://' + domain_full + '/' + nickname
if url in followers_str:
already_following = True
2022-01-02 14:51:02 +00:00
if already_following:
2019-08-26 22:38:09 +00:00
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: ' +
2022-01-02 14:51:02 +00:00
nickname_to_follow + '@' + domain_to_follow +
' already following ' + approve_handle)
2019-08-26 22:38:09 +00:00
return True
# should this follow be denied?
2022-01-02 14:51:02 +00:00
deny_follows_filename = accounts_dir + '/followrejects.txt'
if os.path.isfile(deny_follows_filename):
2022-06-10 13:01:39 +00:00
if text_in_file(approve_handle, deny_follows_filename):
2022-01-02 14:51:02 +00:00
remove_from_follow_requests(base_dir, nickname_to_follow,
domain_to_follow, approve_handle,
debug)
print(approve_handle + ' was already denied as a follower of ' +
nickname_to_follow)
return True
# add to a file which contains a list of requests
2022-01-02 14:51:02 +00:00
approve_follows_filename = accounts_dir + '/followrequests.txt'
# store either nick@domain or the full person/actor url
2022-01-02 14:51:02 +00:00
approve_handle_stored = approve_handle
if '/users/' not in person_url:
approve_handle_stored = person_url
2021-12-26 00:07:44 +00:00
if group_account:
2022-01-02 14:51:02 +00:00
approve_handle = '!' + approve_handle
2022-01-02 14:51:02 +00:00
if os.path.isfile(approve_follows_filename):
2022-06-10 11:43:33 +00:00
if not text_in_file(approve_handle, approve_follows_filename):
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(approve_follows_filename, 'a+',
encoding='utf-8') as fp_approve:
2022-01-02 14:51:02 +00:00
fp_approve.write(approve_handle_stored + '\n')
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: store_follow_request 2 ' + approve_follows_filename)
else:
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: ' + approve_handle_stored +
2020-04-03 11:38:44 +00:00
' is already awaiting approval')
else:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(approve_follows_filename, 'w+',
encoding='utf-8') as fp_approve:
2022-01-02 14:51:02 +00:00
fp_approve.write(approve_handle_stored + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: store_follow_request 3 ' + approve_follows_filename)
# store the follow request in its own directory
# We don't rely upon the inbox because items in there could expire
2022-01-02 14:51:02 +00:00
requests_dir = accounts_dir + '/requests'
if not os.path.isdir(requests_dir):
os.mkdir(requests_dir)
follow_activity_filename = requests_dir + '/' + approve_handle + '.follow'
return save_json(follow_json, follow_activity_filename)
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def followed_account_accepts(session, base_dir: str, http_prefix: str,
2022-01-02 14:51:02 +00:00
nickname_to_follow: str, domain_to_follow: str,
2021-12-29 21:55:09 +00:00
port: int,
2022-01-02 14:51:02 +00:00
nickname: str, domain: str, from_port: int,
person_url: str, federation_list: [],
2022-05-30 21:41:18 +00:00
follow_json: {}, send_threads: [], post_log: [],
2021-12-29 21:55:09 +00:00
cached_webfingers: {}, person_cache: {},
debug: bool, project_version: str,
2022-05-30 21:41:18 +00:00
remove_follow_activity: bool,
signing_priv_key_pem: str,
curr_domain: str,
onion_domain: str, i2p_domain: str,
2023-09-15 21:04:31 +00:00
followers_sync_cache: {},
2023-10-25 19:55:40 +00:00
sites_unavailable: [],
2024-12-17 13:50:48 +00:00
system_language: str,
mitm_servers: []):
2019-07-20 08:33:18 +00:00
"""The person receiving a follow request accepts the new follower
and sends back an Accept activity
"""
2022-01-02 14:51:02 +00:00
accept_handle = nickname + '@' + domain
2019-10-06 09:57:49 +00:00
2019-07-05 18:57:19 +00:00
# send accept back
2022-02-28 16:19:26 +00:00
print('Sending follow Accept activity for ' +
'follow request which arrived at ' +
nickname_to_follow + '@' + domain_to_follow +
' back to ' + accept_handle)
2024-05-08 10:44:38 +00:00
accept_json = create_accept(federation_list,
2022-01-02 14:51:02 +00:00
nickname_to_follow, domain_to_follow, port,
person_url, '', http_prefix,
follow_json)
2022-02-28 16:19:26 +00:00
pprint(accept_json)
print('DEBUG: sending follow Accept from ' +
nickname_to_follow + '@' + domain_to_follow +
' port ' + str(port) + ' to ' +
accept_handle + ' port ' + str(from_port))
2021-12-25 20:39:35 +00:00
client_to_server = False
2019-12-16 10:19:21 +00:00
2022-05-30 21:41:18 +00:00
if remove_follow_activity:
# remove the follow request json
2022-01-02 14:51:02 +00:00
follow_activity_filename = \
acct_dir(base_dir, nickname_to_follow, domain_to_follow) + \
2022-02-28 19:37:33 +00:00
'/requests/' + nickname + '@' + domain + '.follow'
2022-01-02 14:51:02 +00:00
if os.path.isfile(follow_activity_filename):
try:
2022-01-02 14:51:02 +00:00
os.remove(follow_activity_filename)
2021-11-25 18:42:38 +00:00
except OSError:
2022-02-28 16:19:26 +00:00
print('EX: follow Accept ' +
'followed_account_accepts unable to delete ' +
2022-01-02 14:51:02 +00:00
follow_activity_filename)
2019-12-16 10:19:21 +00:00
2021-12-26 00:07:44 +00:00
group_account = False
2022-01-02 14:51:02 +00:00
if follow_json:
if follow_json.get('actor'):
2024-01-09 16:59:23 +00:00
actor_url = get_actor_from_post(follow_json)
if has_group_type(base_dir, actor_url, person_cache):
2021-12-26 00:07:44 +00:00
group_account = True
2023-03-17 21:13:43 +00:00
extra_headers = {}
domain_full = get_full_domain(domain, from_port)
remove_followers_sync(followers_sync_cache,
nickname_to_follow,
domain_full)
2022-01-02 14:51:02 +00:00
return send_signed_json(accept_json, session, base_dir,
nickname_to_follow, domain_to_follow, port,
2022-05-31 13:34:29 +00:00
nickname, domain, from_port,
http_prefix, client_to_server,
2021-12-29 21:55:09 +00:00
federation_list,
2022-05-30 21:41:18 +00:00
send_threads, post_log, cached_webfingers,
2021-12-29 21:55:09 +00:00
person_cache, debug, project_version, None,
group_account, signing_priv_key_pem,
2023-03-17 21:13:43 +00:00
7856837, curr_domain, onion_domain, i2p_domain,
2023-10-25 19:55:40 +00:00
extra_headers, sites_unavailable,
2024-12-17 13:50:48 +00:00
system_language, mitm_servers)
2021-12-29 21:55:09 +00:00
def followed_account_rejects(session, session_onion, session_i2p,
onion_domain: str, i2p_domain: str,
base_dir: str, http_prefix: str,
2022-01-02 14:51:02 +00:00
nickname_to_follow: str, domain_to_follow: str,
2021-12-29 21:55:09 +00:00
port: int,
2022-01-02 14:51:02 +00:00
nickname: str, domain: str, from_port: int,
2021-12-29 21:55:09 +00:00
federation_list: [],
2022-05-30 21:41:18 +00:00
send_threads: [], post_log: [],
2021-12-29 21:55:09 +00:00
cached_webfingers: {}, person_cache: {},
debug: bool, project_version: str,
signing_priv_key_pem: str,
2023-09-15 21:04:31 +00:00
followers_sync_cache: {},
2023-10-25 19:55:40 +00:00
sites_unavailable: [],
2024-12-17 13:50:48 +00:00
system_language: str,
mitm_servers: []):
"""The person receiving a follow request rejects the new follower
and sends back a Reject activity
"""
# send reject back
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: sending Reject activity for ' +
'follow request which arrived at ' +
2022-01-02 14:51:02 +00:00
nickname_to_follow + '@' + domain_to_follow +
2020-04-03 11:38:44 +00:00
' back to ' + nickname + '@' + domain)
2019-12-16 10:01:57 +00:00
# get the json for the original follow request
2022-01-02 14:51:02 +00:00
follow_activity_filename = \
acct_dir(base_dir, nickname_to_follow, domain_to_follow) + \
'/requests/' + nickname + '@' + domain + '.follow'
follow_json = load_json(follow_activity_filename)
if not follow_json:
2020-04-03 11:38:44 +00:00
print('No follow request json was found for ' +
2022-01-02 14:51:02 +00:00
follow_activity_filename)
2019-12-16 10:01:57 +00:00
return None
# actor who made the follow request
2024-01-09 16:59:23 +00:00
person_url = get_actor_from_post(follow_json)
2019-12-16 10:01:57 +00:00
# create the reject activity
2022-01-02 14:51:02 +00:00
reject_json = \
2024-05-08 10:44:38 +00:00
create_reject(federation_list,
2022-01-02 14:51:02 +00:00
nickname_to_follow, domain_to_follow, port,
person_url, '', http_prefix, follow_json)
if debug:
2022-01-02 14:51:02 +00:00
pprint(reject_json)
2020-04-03 11:38:44 +00:00
print('DEBUG: sending follow Reject from ' +
2022-01-02 14:51:02 +00:00
nickname_to_follow + '@' + domain_to_follow +
2020-04-03 11:38:44 +00:00
' port ' + str(port) + ' to ' +
2022-01-02 14:51:02 +00:00
nickname + '@' + domain + ' port ' + str(from_port))
2021-12-25 20:39:35 +00:00
client_to_server = False
2022-01-02 14:51:02 +00:00
deny_handle = get_full_domain(nickname + '@' + domain, from_port)
2021-12-26 00:07:44 +00:00
group_account = False
2022-01-02 14:51:02 +00:00
if has_group_type(base_dir, person_url, person_cache):
2021-12-26 00:07:44 +00:00
group_account = True
# remove from the follow requests file
2022-01-02 14:51:02 +00:00
remove_from_follow_requests(base_dir, nickname_to_follow, domain_to_follow,
deny_handle, debug)
# remove the follow request json
try:
2022-01-02 14:51:02 +00:00
os.remove(follow_activity_filename)
2021-11-25 18:42:38 +00:00
except OSError:
2021-12-29 21:55:09 +00:00
print('EX: followed_account_rejects unable to delete ' +
2022-01-02 14:51:02 +00:00
follow_activity_filename)
curr_session = session
if domain.endswith('.onion') and session_onion:
curr_session = session_onion
elif domain.endswith('.i2p') and session_i2p:
curr_session = session_i2p
2023-03-17 21:13:43 +00:00
extra_headers = {}
domain_full = get_full_domain(domain, from_port)
remove_followers_sync(followers_sync_cache,
nickname_to_follow,
domain_full)
# send the reject activity
return send_signed_json(reject_json, curr_session, base_dir,
2022-01-02 14:51:02 +00:00
nickname_to_follow, domain_to_follow, port,
2022-05-31 13:34:29 +00:00
nickname, domain, from_port,
http_prefix, client_to_server,
2021-12-29 21:55:09 +00:00
federation_list,
2022-05-30 21:41:18 +00:00
send_threads, post_log, cached_webfingers,
2021-12-29 21:55:09 +00:00
person_cache, debug, project_version, None,
group_account, signing_priv_key_pem,
6393063,
2023-03-17 21:13:43 +00:00
domain, onion_domain, i2p_domain,
2023-10-25 19:55:40 +00:00
extra_headers, sites_unavailable,
2024-12-17 13:50:48 +00:00
system_language, mitm_servers)
2020-04-03 11:38:44 +00:00
2021-12-28 20:32:11 +00:00
def send_follow_request(session, base_dir: str,
nickname: str, domain: str,
sender_domain: str, sender_port: int,
2021-12-28 20:32:11 +00:00
http_prefix: str,
2022-01-02 14:51:02 +00:00
follow_nickname: str, follow_domain: str,
2022-05-30 21:41:18 +00:00
followed_actor: str,
follow_port: int, follow_http_prefix: str,
2021-12-28 20:32:11 +00:00
client_to_server: bool, federation_list: [],
2022-05-30 21:41:18 +00:00
send_threads: [], post_log: [], cached_webfingers: {},
2021-12-28 20:32:11 +00:00
person_cache: {}, debug: bool,
project_version: str, signing_priv_key_pem: str,
curr_domain: str,
2023-09-15 21:04:31 +00:00
onion_domain: str, i2p_domain: str,
2023-10-25 19:55:40 +00:00
sites_unavailable: [],
2024-12-17 13:50:48 +00:00
system_language: str,
mitm_servers: []) -> {}:
2019-07-02 20:54:22 +00:00
"""Gets the json object for sending a follow request
2020-03-22 21:16:02 +00:00
"""
2021-12-25 23:03:28 +00:00
if not signing_priv_key_pem:
2021-09-20 16:14:24 +00:00
print('WARN: follow request without signing key')
2022-01-02 14:51:02 +00:00
if not domain_permitted(follow_domain, federation_list):
print('You are not permitted to follow the domain ' + follow_domain)
2019-07-02 18:38:51 +00:00
return None
2020-03-22 21:16:02 +00:00
full_domain = get_full_domain(sender_domain, sender_port)
2022-01-02 14:51:02 +00:00
follow_actor = local_actor_url(http_prefix, nickname, full_domain)
2019-07-02 18:38:51 +00:00
2022-05-30 21:41:18 +00:00
request_domain = get_full_domain(follow_domain, follow_port)
2019-07-06 10:33:57 +00:00
2022-01-02 14:51:02 +00:00
status_number, _ = get_status_number()
2020-03-22 21:16:02 +00:00
2021-12-26 00:07:44 +00:00
group_account = False
2022-01-02 14:51:02 +00:00
if follow_nickname:
2022-05-30 21:41:18 +00:00
followed_id = followed_actor
2022-01-02 14:51:02 +00:00
follow_handle = follow_nickname + '@' + request_domain
2022-05-30 21:41:18 +00:00
group_account = has_group_type(base_dir, followed_actor, person_cache)
2021-12-26 00:07:44 +00:00
if group_account:
2022-01-02 14:51:02 +00:00
follow_handle = '!' + follow_handle
2021-08-02 20:57:44 +00:00
print('Follow request being sent to group account')
2019-10-21 14:12:22 +00:00
else:
if debug:
2021-12-28 20:32:11 +00:00
print('DEBUG: send_follow_request - assuming single user instance')
2022-05-30 21:41:18 +00:00
followed_id = follow_http_prefix + '://' + request_domain
2022-01-02 14:51:02 +00:00
single_user_nickname = 'dev'
follow_handle = single_user_nickname + '@' + request_domain
2019-07-06 19:24:52 +00:00
2021-10-17 17:45:49 +00:00
# remove follow handle from unfollowed.txt
2022-01-02 14:51:02 +00:00
unfollowed_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + '/unfollowed.txt'
2022-01-02 14:51:02 +00:00
if os.path.isfile(unfollowed_filename):
2022-06-10 13:01:39 +00:00
if text_in_file(follow_handle, unfollowed_filename):
2022-01-02 14:51:02 +00:00
unfollowed_file = None
2021-11-26 14:35:26 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(unfollowed_filename, 'r',
encoding='utf-8') as fp_unfoll:
2022-01-02 14:51:02 +00:00
unfollowed_file = fp_unfoll.read()
2021-11-26 14:35:26 +00:00
except OSError:
2022-01-02 14:51:02 +00:00
print('EX: send_follow_request ' + unfollowed_filename)
if unfollowed_file:
unfollowed_file = \
unfollowed_file.replace(follow_handle + '\n', '')
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(unfollowed_filename, 'w+',
encoding='utf-8') as fp_unfoll:
2022-01-02 14:51:02 +00:00
fp_unfoll.write(unfollowed_file)
2021-11-25 21:18:53 +00:00
except OSError:
2024-07-02 22:16:13 +00:00
print('EX: send_follow_request unable to write ' +
unfollowed_filename)
2021-10-17 17:45:49 +00:00
2022-01-02 14:51:02 +00:00
new_follow_json = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
2022-01-02 14:51:02 +00:00
'id': follow_actor + '/statuses/' + str(status_number),
2019-07-02 18:38:51 +00:00
'type': 'Follow',
2022-01-02 14:51:02 +00:00
'actor': follow_actor,
'object': followed_id
2019-07-02 18:38:51 +00:00
}
2021-12-26 00:07:44 +00:00
if group_account:
2024-03-29 15:33:37 +00:00
new_follow_json['to'] = [followed_id]
2022-01-02 14:51:02 +00:00
print('Follow request: ' + str(new_follow_json))
2019-07-02 19:05:59 +00:00
2021-12-29 21:55:09 +00:00
if follow_approval_required(base_dir, nickname, domain, debug,
2022-01-02 14:51:02 +00:00
follow_handle):
2020-02-19 12:51:14 +00:00
# Remove any follow requests rejected for the account being followed.
# It's assumed that if you are following someone then you are
# ok with them following back. If this isn't the case then a rejected
# follow request will block them again.
2021-12-29 21:55:09 +00:00
_remove_from_follow_rejects(base_dir,
nickname, domain,
2022-01-02 14:51:02 +00:00
follow_handle, debug)
2023-03-17 21:13:43 +00:00
extra_headers = {}
2022-01-02 14:51:02 +00:00
send_signed_json(new_follow_json, session, base_dir,
nickname, sender_domain, sender_port,
2022-05-30 21:41:18 +00:00
follow_nickname, follow_domain, follow_port,
2022-05-31 13:34:29 +00:00
http_prefix, client_to_server,
2021-12-29 21:55:09 +00:00
federation_list,
2022-05-30 21:41:18 +00:00
send_threads, post_log, cached_webfingers, person_cache,
2021-12-29 21:55:09 +00:00
debug, project_version, None, group_account,
signing_priv_key_pem, 8234389,
2023-03-17 21:13:43 +00:00
curr_domain, onion_domain, i2p_domain,
2023-10-25 19:55:40 +00:00
extra_headers, sites_unavailable,
2024-12-17 13:50:48 +00:00
system_language, mitm_servers)
2019-07-05 20:32:21 +00:00
2022-01-02 14:51:02 +00:00
return new_follow_json
2020-04-03 11:38:44 +00:00
2022-03-13 12:15:52 +00:00
def send_follow_request_via_server(base_dir: str, session,
from_nickname: str, password: str,
from_domain: str, from_port: int,
follow_nickname: str, follow_domain: str,
2022-05-30 21:41:18 +00:00
follow_port: int,
2022-03-13 12:15:52 +00:00
http_prefix: str,
cached_webfingers: {}, person_cache: {},
debug: bool, project_version: str,
2023-10-25 19:55:40 +00:00
signing_priv_key_pem: str,
2024-12-17 13:50:48 +00:00
system_language: str,
mitm_servers: []) -> {}:
2019-07-16 21:38:06 +00:00
"""Creates a follow request via c2s
"""
if not session:
2022-03-13 12:15:52 +00:00
print('WARN: No session for send_follow_request_via_server')
2019-07-16 21:38:06 +00:00
return 6
2022-01-02 14:51:02 +00:00
from_domain_full = get_full_domain(from_domain, from_port)
2019-07-18 11:35:48 +00:00
2022-05-30 21:41:18 +00:00
follow_domain_full = get_full_domain(follow_domain, follow_port)
2019-07-16 21:38:06 +00:00
2022-01-02 14:51:02 +00:00
follow_actor = \
local_actor_url(http_prefix, from_nickname, from_domain_full)
followed_id = \
http_prefix + '://' + follow_domain_full + '/@' + follow_nickname
2019-07-16 21:38:06 +00:00
status_number, _ = get_status_number()
2022-01-02 14:51:02 +00:00
new_follow_json = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
2022-01-02 14:51:02 +00:00
'id': follow_actor + '/statuses/' + str(status_number),
2019-07-16 21:38:06 +00:00
'type': 'Follow',
2022-01-02 14:51:02 +00:00
'actor': follow_actor,
'object': followed_id
2019-07-16 21:38:06 +00:00
}
2022-01-02 14:51:02 +00:00
handle = http_prefix + '://' + from_domain_full + '/@' + from_nickname
2019-07-16 21:38:06 +00:00
# lookup the inbox for the To handle
2022-01-02 14:51:02 +00:00
wf_request = \
2021-12-29 21:55:09 +00:00
webfinger_handle(session, handle, http_prefix, cached_webfingers,
2022-01-02 14:51:02 +00:00
from_domain, project_version, debug, False,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem, mitm_servers)
2022-01-02 14:51:02 +00:00
if not wf_request:
2019-07-16 21:38:06 +00:00
if debug:
2021-03-18 10:01:01 +00:00
print('DEBUG: follow request webfinger failed for ' + handle)
2019-07-16 21:38:06 +00:00
return 1
2022-01-02 14:51:02 +00:00
if not isinstance(wf_request, dict):
2021-03-18 10:01:01 +00:00
print('WARN: follow request Webfinger for ' + handle +
2022-01-02 14:51:02 +00:00
' did not return a dict. ' + str(wf_request))
2020-06-23 10:41:12 +00:00
return 1
2019-07-16 21:38:06 +00:00
2022-01-02 14:51:02 +00:00
post_to_box = 'outbox'
2019-07-16 21:38:06 +00:00
# get the actor inbox for the To handle
2022-01-02 14:51:02 +00:00
origin_domain = from_domain
2022-01-14 12:53:29 +00:00
(inbox_url, _, _, from_person_id, _, _,
_, _) = get_person_box(signing_priv_key_pem, origin_domain,
base_dir, session, wf_request,
person_cache,
project_version, http_prefix,
from_nickname,
2023-10-25 19:55:40 +00:00
from_domain, post_to_box, 52025,
2024-12-17 13:50:48 +00:00
system_language, mitm_servers)
2022-01-14 12:53:29 +00:00
if not inbox_url:
2019-07-16 21:38:06 +00:00
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: follow request no ' + post_to_box +
2021-03-18 10:01:01 +00:00
' was found for ' + handle)
2019-07-16 21:38:06 +00:00
return 3
2022-01-14 12:53:29 +00:00
if not from_person_id:
2019-07-16 21:38:06 +00:00
if debug:
2021-03-18 10:01:01 +00:00
print('DEBUG: follow request no actor was found for ' + handle)
2019-07-16 21:38:06 +00:00
return 4
2020-03-22 21:16:02 +00:00
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(from_nickname, password)
2020-03-22 21:16:02 +00:00
2020-04-03 11:38:44 +00:00
headers = {
2022-01-02 14:51:02 +00:00
'host': from_domain,
2020-04-03 11:38:44 +00:00
'Content-type': 'application/json',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
2020-03-22 20:36:19 +00:00
}
2022-01-02 14:51:02 +00:00
post_result = \
post_json(http_prefix, from_domain_full,
2022-01-14 12:53:29 +00:00
session, new_follow_json, [], inbox_url, headers, 3, True)
2022-01-02 14:51:02 +00:00
if not post_result:
2020-04-03 11:38:44 +00:00
if debug:
2022-01-14 12:53:29 +00:00
print('DEBUG: POST follow request failed for c2s to ' + inbox_url)
2020-04-03 11:38:44 +00:00
return 5
2019-07-16 21:38:06 +00:00
if debug:
2021-03-18 10:01:01 +00:00
print('DEBUG: c2s POST follow request success')
2019-07-16 21:38:06 +00:00
2022-01-02 14:51:02 +00:00
return new_follow_json
2019-07-16 21:38:06 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def send_unfollow_request_via_server(base_dir: str, session,
2022-01-02 14:51:02 +00:00
from_nickname: str, password: str,
from_domain: str, from_port: int,
follow_nickname: str, follow_domain: str,
2022-05-30 21:41:18 +00:00
follow_port: int,
2021-12-29 21:55:09 +00:00
http_prefix: str,
cached_webfingers: {}, person_cache: {},
debug: bool, project_version: str,
2023-10-25 19:55:40 +00:00
signing_priv_key_pem: str,
2024-12-17 13:50:48 +00:00
system_language: str,
mitm_servers: []) -> {}:
2019-07-17 10:34:00 +00:00
"""Creates a unfollow request via c2s
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for send_unfollow_request_via_server')
2019-07-17 10:34:00 +00:00
return 6
2022-01-02 14:51:02 +00:00
from_domain_full = get_full_domain(from_domain, from_port)
2022-05-30 21:41:18 +00:00
follow_domain_full = get_full_domain(follow_domain, follow_port)
2019-07-17 10:34:00 +00:00
2022-01-02 14:51:02 +00:00
follow_actor = \
local_actor_url(http_prefix, from_nickname, from_domain_full)
followed_id = \
http_prefix + '://' + follow_domain_full + '/@' + follow_nickname
2022-05-30 21:41:18 +00:00
status_number, _ = get_status_number()
2019-07-17 10:34:00 +00:00
2022-01-02 14:51:02 +00:00
unfollow_json = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
2022-01-02 14:51:02 +00:00
'id': follow_actor + '/statuses/' + str(status_number) + '/undo',
2019-07-17 10:34:00 +00:00
'type': 'Undo',
2022-01-02 14:51:02 +00:00
'actor': follow_actor,
2019-07-17 10:34:00 +00:00
'object': {
2022-01-02 14:51:02 +00:00
'id': follow_actor + '/statuses/' + str(status_number),
2019-07-17 10:34:00 +00:00
'type': 'Follow',
2022-01-02 14:51:02 +00:00
'actor': follow_actor,
'object': followed_id
2019-07-17 10:34:00 +00:00
}
}
2022-01-02 14:51:02 +00:00
handle = http_prefix + '://' + from_domain_full + '/@' + from_nickname
2019-07-17 10:34:00 +00:00
# lookup the inbox for the To handle
2022-01-02 14:51:02 +00:00
wf_request = \
2021-12-29 21:55:09 +00:00
webfinger_handle(session, handle, http_prefix, cached_webfingers,
2022-01-02 14:51:02 +00:00
from_domain, project_version, debug, False,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem, mitm_servers)
2022-01-02 14:51:02 +00:00
if not wf_request:
2019-07-17 10:34:00 +00:00
if debug:
2021-03-18 10:01:01 +00:00
print('DEBUG: unfollow webfinger failed for ' + handle)
2019-07-17 10:34:00 +00:00
return 1
2022-01-02 14:51:02 +00:00
if not isinstance(wf_request, dict):
2021-03-18 10:01:01 +00:00
print('WARN: unfollow webfinger for ' + handle +
2022-01-02 14:51:02 +00:00
' did not return a dict. ' + str(wf_request))
2020-06-23 10:41:12 +00:00
return 1
2019-07-17 10:34:00 +00:00
2022-01-02 14:51:02 +00:00
post_to_box = 'outbox'
2019-07-17 10:34:00 +00:00
# get the actor inbox for the To handle
2022-01-02 14:51:02 +00:00
origin_domain = from_domain
2022-01-14 12:53:29 +00:00
(inbox_url, _, _, from_person_id, _, _,
_, _) = get_person_box(signing_priv_key_pem,
origin_domain,
base_dir, session,
wf_request, person_cache,
project_version, http_prefix,
from_nickname,
from_domain, post_to_box,
2024-12-17 13:50:48 +00:00
76536, system_language,
mitm_servers)
2022-01-14 12:53:29 +00:00
if not inbox_url:
2019-07-17 10:34:00 +00:00
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: unfollow no ' + post_to_box +
2021-03-18 10:01:01 +00:00
' was found for ' + handle)
2019-07-17 10:34:00 +00:00
return 3
2022-01-14 12:53:29 +00:00
if not from_person_id:
2019-07-17 10:34:00 +00:00
if debug:
2021-03-18 10:01:01 +00:00
print('DEBUG: unfollow no actor was found for ' + handle)
2019-07-17 10:34:00 +00:00
return 4
2020-03-22 21:16:02 +00:00
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(from_nickname, password)
2020-03-22 21:16:02 +00:00
2020-04-03 11:38:44 +00:00
headers = {
2022-01-02 14:51:02 +00:00
'host': from_domain,
2020-04-03 11:38:44 +00:00
'Content-type': 'application/json',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
2020-03-22 20:36:19 +00:00
}
2022-01-02 14:51:02 +00:00
post_result = \
post_json(http_prefix, from_domain_full,
2022-01-14 12:53:29 +00:00
session, unfollow_json, [], inbox_url, headers, 3, True)
2022-01-02 14:51:02 +00:00
if not post_result:
2020-04-03 11:38:44 +00:00
if debug:
2022-01-14 12:53:29 +00:00
print('DEBUG: POST unfollow failed for c2s to ' + inbox_url)
2020-04-03 11:38:44 +00:00
return 5
2019-07-17 10:34:00 +00:00
if debug:
print('DEBUG: c2s POST unfollow success')
2022-01-02 14:51:02 +00:00
return unfollow_json
2019-07-17 10:34:00 +00:00
2020-04-03 11:38:44 +00:00
2022-06-01 15:54:28 +00:00
def get_following_via_server(session, nickname: str, password: str,
2021-12-29 21:55:09 +00:00
domain: str, port: int,
2022-01-02 14:51:02 +00:00
http_prefix: str, page_number: int,
2021-12-29 21:55:09 +00:00
debug: bool, project_version: str,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem: str,
mitm_servers: []) -> {}:
2021-03-24 12:43:24 +00:00
"""Gets a page from the following collection as json
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for get_following_via_server')
2021-03-24 12:43:24 +00:00
return 6
2021-12-26 12:45:03 +00:00
domain_full = get_full_domain(domain, port)
2022-01-02 14:51:02 +00:00
follow_actor = local_actor_url(http_prefix, nickname, domain_full)
2021-03-24 12:43:24 +00:00
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(nickname, password)
2021-03-24 12:43:24 +00:00
headers = {
'host': domain,
'Content-type': 'application/json',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
2021-03-24 12:43:24 +00:00
}
2022-05-30 21:41:18 +00:00
page_number = max(page_number, 1)
2022-01-02 14:51:02 +00:00
url = follow_actor + '/following?page=' + str(page_number)
2022-05-30 21:41:18 +00:00
following_json = \
2021-12-29 21:55:09 +00:00
get_json(signing_priv_key_pem, session, url, headers, {}, debug,
2024-12-17 13:50:48 +00:00
mitm_servers, project_version, http_prefix, domain, 10, True)
2023-08-13 09:58:02 +00:00
if not get_json_valid(following_json):
2021-03-24 12:43:24 +00:00
if debug:
print('DEBUG: GET following list failed for c2s to ' + url)
return 5
if debug:
print('DEBUG: c2s GET following list request success')
2022-05-30 21:41:18 +00:00
return following_json
2021-03-24 12:43:24 +00:00
2022-06-01 15:54:28 +00:00
def get_followers_via_server(session, nickname: str, password: str,
2021-12-29 21:55:09 +00:00
domain: str, port: int,
2022-01-02 14:51:02 +00:00
http_prefix: str, page_number: int,
2021-12-29 21:55:09 +00:00
debug: bool, project_version: str,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem: str,
mitm_servers: []) -> {}:
2021-03-24 13:52:20 +00:00
"""Gets a page from the followers collection as json
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for get_followers_via_server')
2021-03-24 13:52:20 +00:00
return 6
2021-12-26 12:45:03 +00:00
domain_full = get_full_domain(domain, port)
2022-01-02 14:51:02 +00:00
follow_actor = local_actor_url(http_prefix, nickname, domain_full)
2021-03-24 13:52:20 +00:00
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(nickname, password)
2021-03-24 13:52:20 +00:00
headers = {
'host': domain,
'Content-type': 'application/json',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
2021-03-24 13:52:20 +00:00
}
2022-06-01 15:54:28 +00:00
page_number = max(page_number, 1)
2022-01-02 14:51:02 +00:00
url = follow_actor + '/followers?page=' + str(page_number)
followers_json = \
2021-12-29 21:55:09 +00:00
get_json(signing_priv_key_pem, session, url, headers, {}, debug,
2024-12-17 13:50:48 +00:00
mitm_servers, project_version, http_prefix, domain, 10, True)
2023-08-13 09:58:02 +00:00
if not get_json_valid(followers_json):
2021-03-24 13:52:20 +00:00
if debug:
print('DEBUG: GET followers list failed for c2s to ' + url)
return 5
if debug:
print('DEBUG: c2s GET followers list request success')
2022-01-02 14:51:02 +00:00
return followers_json
2021-03-24 13:52:20 +00:00
2022-06-01 15:54:28 +00:00
def get_follow_requests_via_server(session,
2021-12-29 21:55:09 +00:00
nickname: str, password: str,
domain: str, port: int,
2022-01-02 14:51:02 +00:00
http_prefix: str, page_number: int,
2021-12-29 21:55:09 +00:00
debug: bool, project_version: str,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem: str,
mitm_servers: []) -> {}:
"""Gets a page from the follow requests collection as json
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for get_follow_requests_via_server')
return 6
2021-12-26 12:45:03 +00:00
domain_full = get_full_domain(domain, port)
2022-01-02 14:51:02 +00:00
follow_actor = local_actor_url(http_prefix, nickname, domain_full)
auth_header = create_basic_auth_header(nickname, password)
headers = {
'host': domain,
'Content-type': 'application/json',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
}
2022-05-30 21:41:18 +00:00
page_number = max(page_number, 1)
2022-01-02 14:51:02 +00:00
url = follow_actor + '/followrequests?page=' + str(page_number)
followers_json = \
2021-12-29 21:55:09 +00:00
get_json(signing_priv_key_pem, session, url, headers, {}, debug,
2024-12-17 13:50:48 +00:00
mitm_servers, project_version, http_prefix, domain, 10, True)
2023-08-13 09:58:02 +00:00
if not get_json_valid(followers_json):
if debug:
print('DEBUG: GET follow requests list failed for c2s to ' + url)
return 5
if debug:
print('DEBUG: c2s GET follow requests list request success')
2022-01-02 14:51:02 +00:00
return followers_json
2022-06-01 15:54:28 +00:00
def approve_follow_request_via_server(session,
2021-12-29 21:55:09 +00:00
nickname: str, password: str,
domain: str, port: int,
2022-01-02 14:51:02 +00:00
http_prefix: str, approve_handle: int,
2021-12-29 21:55:09 +00:00
debug: bool, project_version: str,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem: str,
mitm_servers: []) -> str:
"""Approves a follow request
This is not exactly via c2s though. It simulates pressing the Approve
button on the web interface
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for approve_follow_request_via_server')
return 6
2021-12-26 12:45:03 +00:00
domain_full = get_full_domain(domain, port)
2021-12-26 10:19:59 +00:00
actor = local_actor_url(http_prefix, nickname, domain_full)
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(nickname, password)
headers = {
'host': domain,
'Content-type': 'text/html; charset=utf-8',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
}
2022-01-02 14:51:02 +00:00
url = actor + '/followapprove=' + approve_handle
approve_html = \
2021-12-29 21:55:09 +00:00
get_json(signing_priv_key_pem, session, url, headers, {}, debug,
2024-12-17 13:50:48 +00:00
mitm_servers, project_version, http_prefix, domain, 10, True)
2023-08-13 09:58:02 +00:00
if not get_json_valid(approve_html):
if debug:
print('DEBUG: GET approve follow request failed for c2s to ' + url)
return 5
if debug:
print('DEBUG: c2s GET approve follow request request success')
2022-01-02 14:51:02 +00:00
return approve_html
2022-06-01 15:54:28 +00:00
def deny_follow_request_via_server(session,
2021-12-29 21:55:09 +00:00
nickname: str, password: str,
domain: str, port: int,
2022-01-02 14:51:02 +00:00
http_prefix: str, deny_handle: int,
2021-12-29 21:55:09 +00:00
debug: bool, project_version: str,
2024-12-17 13:50:48 +00:00
signing_priv_key_pem: str,
mitm_servers: []) -> str:
"""Denies a follow request
This is not exactly via c2s though. It simulates pressing the Deny
button on the web interface
"""
if not session:
2021-12-29 21:55:09 +00:00
print('WARN: No session for deny_follow_request_via_server')
return 6
2021-12-26 12:45:03 +00:00
domain_full = get_full_domain(domain, port)
2021-12-26 10:19:59 +00:00
actor = local_actor_url(http_prefix, nickname, domain_full)
2022-01-02 14:51:02 +00:00
auth_header = create_basic_auth_header(nickname, password)
headers = {
'host': domain,
'Content-type': 'text/html; charset=utf-8',
2022-01-02 14:51:02 +00:00
'Authorization': auth_header
}
2022-01-02 14:51:02 +00:00
url = actor + '/followdeny=' + deny_handle
deny_html = \
2021-12-29 21:55:09 +00:00
get_json(signing_priv_key_pem, session, url, headers, {}, debug,
2024-12-17 13:50:48 +00:00
mitm_servers, project_version, http_prefix, domain, 10, True)
2023-08-13 09:58:02 +00:00
if not get_json_valid(deny_html):
if debug:
print('DEBUG: GET deny follow request failed for c2s to ' + url)
return 5
if debug:
print('DEBUG: c2s GET deny follow request request success')
2022-01-02 14:51:02 +00:00
return deny_html
2021-12-29 21:55:09 +00:00
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
2019-07-08 17:15:55 +00:00
and if it's addressed to followers then we need to get a list of those.
This returns a list of account handles which follow the given actor
"""
2019-07-11 12:29:31 +00:00
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: getting followers of ' + actor)
2022-01-02 14:51:02 +00:00
recipients_dict = {}
2019-07-08 17:15:55 +00:00
if ':' not in actor:
2022-01-02 14:51:02 +00:00
return recipients_dict
2021-12-27 22:19:18 +00:00
nickname = get_nickname_from_actor(actor)
if not nickname:
2019-07-11 12:29:31 +00:00
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: no nickname found in ' + actor)
2022-01-02 14:51:02 +00:00
return recipients_dict
domain, _ = get_domain_from_actor(actor)
if not domain:
2019-07-11 12:29:31 +00:00
if debug:
2020-04-03 11:38:44 +00:00
print('DEBUG: no domain found in ' + actor)
2022-01-02 14:51:02 +00:00
return recipients_dict
actor_handle = nickname + '@' + domain
2019-07-11 12:29:31 +00:00
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: searching for handle ' + actor_handle)
# for each of the accounts
2024-05-12 12:35:26 +00:00
dir_str = data_dir(base_dir)
for subdir, dirs, _ in os.walk(dir_str):
for account in dirs:
2022-06-01 15:54:28 +00:00
if '@' not in account:
continue
if account.startswith('inbox@'):
continue
if account.startswith('Actor@'):
continue
following_filename = \
os.path.join(subdir, account) + '/following.txt'
if debug:
print('DEBUG: examining follows of ' + account)
print(following_filename)
if os.path.isfile(following_filename):
# does this account follow the given actor?
2019-07-11 12:29:31 +00:00
if debug:
2022-06-01 15:54:28 +00:00
print('DEBUG: checking if ' + actor_handle +
' in ' + following_filename)
2022-06-10 13:01:39 +00:00
if text_in_file(actor_handle, following_filename):
2019-07-11 12:29:31 +00:00
if debug:
2022-06-01 15:54:28 +00:00
print('DEBUG: ' + account +
' follows ' + actor_handle)
recipients_dict[account] = None
2020-12-13 22:13:45 +00:00
break
2022-01-02 14:51:02 +00:00
return recipients_dict
2019-07-17 10:34:00 +00:00
2020-04-03 11:38:44 +00:00
2021-12-29 21:55:09 +00:00
def outbox_undo_follow(base_dir: str, message_json: {}, debug: bool) -> None:
2019-07-17 10:34:00 +00:00
"""When an unfollow request is received by the outbox from c2s
This removes the followed handle from the following.txt file
of the relevant account
"""
2021-12-25 23:51:19 +00:00
if not message_json.get('type'):
2019-07-17 10:34:00 +00:00
return
2021-12-25 23:51:19 +00:00
if not message_json['type'] == 'Undo':
2019-07-17 10:34:00 +00:00
return
2022-04-09 15:11:22 +00:00
if not has_object_string_type(message_json, debug):
2019-07-17 10:34:00 +00:00
return
2021-12-25 23:51:19 +00:00
if not message_json['object']['type'] == 'Follow':
if not message_json['object']['type'] == 'Join':
return
2021-12-26 15:54:46 +00:00
if not has_object_string_object(message_json, debug):
2019-07-17 10:34:00 +00:00
return
2021-12-25 23:51:19 +00:00
if not message_json['object'].get('actor'):
2019-07-17 10:34:00 +00:00
return
if debug:
print('DEBUG: undo follow arrived in outbox')
2024-01-09 16:59:23 +00:00
actor_url = get_actor_from_post(message_json['object'])
nickname_follower = get_nickname_from_actor(actor_url)
2022-01-02 14:51:02 +00:00
if not nickname_follower:
2020-04-03 11:38:44 +00:00
print('WARN: unable to find nickname in ' +
2024-01-09 16:59:23 +00:00
actor_url)
2019-09-02 09:43:43 +00:00
return
2024-01-09 16:59:23 +00:00
domain_follower, port_follower = get_domain_from_actor(actor_url)
2023-01-15 14:33:18 +00:00
if not domain_follower:
2024-01-09 16:59:23 +00:00
print('WARN: unable to find domain in ' + actor_url)
2023-01-15 14:33:18 +00:00
return
2022-01-02 14:51:02 +00:00
domain_follower_full = get_full_domain(domain_follower, port_follower)
2020-03-22 21:16:02 +00:00
2022-01-02 14:51:02 +00:00
nickname_following = \
2021-12-27 22:19:18 +00:00
get_nickname_from_actor(message_json['object']['object'])
2022-01-02 14:51:02 +00:00
if not nickname_following:
2020-04-03 11:38:44 +00:00
print('WARN: unable to find nickname in ' +
2021-12-25 23:51:19 +00:00
message_json['object']['object'])
2019-09-02 09:43:43 +00:00
return
2022-01-02 14:51:02 +00:00
domain_following, port_following = \
2021-12-27 19:05:25 +00:00
get_domain_from_actor(message_json['object']['object'])
2023-01-15 14:33:18 +00:00
if not domain_following:
print('WARN: unable to find domain in ' +
message_json['object']['object'])
return
2022-01-02 14:51:02 +00:00
domain_following_full = get_full_domain(domain_following, port_following)
2019-07-17 10:34:00 +00:00
2021-12-26 00:07:44 +00:00
group_account = \
2021-12-26 17:53:07 +00:00
has_group_type(base_dir, message_json['object']['object'], None)
2022-01-02 14:51:02 +00:00
if unfollow_account(base_dir, nickname_follower, domain_follower_full,
nickname_following, domain_following_full,
2024-02-19 15:38:08 +00:00
debug, group_account, 'following.txt'):
2019-07-17 10:34:00 +00:00
if debug:
2022-01-02 14:51:02 +00:00
print('DEBUG: ' + nickname_follower + ' unfollowed ' +
nickname_following + '@' + domain_following_full)
2019-07-17 10:34:00 +00:00
else:
if debug:
2022-01-02 14:51:02 +00:00
print('WARN: ' + nickname_follower + ' could not unfollow ' +
nickname_following + '@' + domain_following_full)
2020-11-09 15:40:24 +00:00
2021-12-28 20:32:11 +00:00
def follower_approval_active(base_dir: str,
nickname: str, domain: str) -> bool:
2020-11-09 15:40:24 +00:00
"""Returns true if the given account requires follower approval
"""
2022-01-02 14:51:02 +00:00
manually_approves_followers = False
actor_filename = acct_dir(base_dir, nickname, domain) + '.json'
if os.path.isfile(actor_filename):
actor_json = load_json(actor_filename)
2021-12-26 10:29:52 +00:00
if actor_json:
2022-02-28 19:37:33 +00:00
if 'manuallyApprovesFollowers' in actor_json:
2022-01-02 14:51:02 +00:00
manually_approves_followers = \
2021-12-26 10:29:52 +00:00
actor_json['manuallyApprovesFollowers']
2022-01-02 14:51:02 +00:00
return manually_approves_followers
2022-12-01 23:00:30 +00:00
def remove_follower(base_dir: str,
nickname: str, domain: str,
remove_nickname: str, remove_domain: str) -> bool:
"""Removes a follower
"""
followers_filename = \
acct_dir(base_dir, nickname, domain) + '/followers.txt'
if not os.path.isfile(followers_filename):
return False
followers_str = ''
try:
with open(followers_filename, 'r', encoding='utf-8') as fp_foll:
followers_str = fp_foll.read()
except OSError:
print('EX: remove_follower unable to read followers ' +
followers_filename)
2022-12-01 23:26:56 +00:00
return False
2022-12-01 23:19:34 +00:00
followers_list = followers_str.split('\n')
handle = remove_nickname + '@' + remove_domain
handle = handle.lower()
new_followers_str = ''
found = False
for handle2 in followers_list:
if handle2.lower() != handle:
new_followers_str += handle2 + '\n'
else:
2022-12-01 23:36:14 +00:00
found = True
2022-12-01 23:19:34 +00:00
if not found:
2022-12-01 23:00:30 +00:00
return False
try:
with open(followers_filename, 'w+', encoding='utf-8') as fp_foll:
2022-12-01 23:26:56 +00:00
fp_foll.write(new_followers_str)
2022-12-01 23:00:30 +00:00
except OSError:
print('EX: remove_follower unable to write followers ' +
followers_filename)
return True
2023-07-11 11:15:27 +00:00
def pending_followers_timeline_json(actor: str, base_dir: str,
nickname: str, domain: str) -> {}:
"""Returns pending followers collection for an account
https://codeberg.org/fediverse/fep/src/branch/main/fep/4ccd/fep-4ccd.md
"""
result_json = {
"@context": [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
2023-07-11 11:15:27 +00:00
],
"id": actor,
"type": "OrderedCollection",
"name": nickname + "'s Pending Followers",
"orderedItems": []
}
follow_requests_filename = \
acct_dir(base_dir, nickname, domain) + '/followrequests.txt'
if os.path.isfile(follow_requests_filename):
try:
with open(follow_requests_filename, 'r',
encoding='utf-8') as fp_req:
for follower_handle in fp_req:
if len(follower_handle) == 0:
continue
follower_handle = remove_eol(follower_handle)
foll_domain, _ = get_domain_from_actor(follower_handle)
if not foll_domain:
continue
foll_nickname = get_nickname_from_actor(follower_handle)
if not foll_nickname:
continue
follow_activity_filename = \
acct_dir(base_dir, nickname, domain) + \
'/requests/' + \
foll_nickname + '@' + foll_domain + '.follow'
if not os.path.isfile(follow_activity_filename):
continue
follow_json = load_json(follow_activity_filename)
if not follow_json:
continue
result_json['orderedItems'].append(follow_json)
except OSError as exc:
print('EX: unable to read follow requests ' +
follow_requests_filename + ' ' + str(exc))
2023-07-11 11:15:27 +00:00
return result_json