mirror of https://gitlab.com/bashrc2/epicyon
merge-requests/30/head
commit
d2cc70d58c
54
daemon.py
54
daemon.py
|
@ -674,30 +674,35 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
nickname,
|
nickname,
|
||||||
self.server.domain_full,
|
self.server.domain_full,
|
||||||
self.server.person_cache)
|
self.server.person_cache)
|
||||||
|
reply_to_nickname = get_nickname_from_actor(in_reply_to)
|
||||||
|
reply_to_domain, reply_to_port = get_domain_from_actor(in_reply_to)
|
||||||
|
reply_to_domain_full = get_full_domain(reply_to_domain, reply_to_port)
|
||||||
|
mentions_str = '@' + reply_to_nickname + '@' + reply_to_domain_full
|
||||||
|
|
||||||
message_json = \
|
message_json = \
|
||||||
create_public_post(self.server.base_dir,
|
create_direct_message_post(self.server.base_dir, nickname,
|
||||||
nickname,
|
|
||||||
self.server.domain, self.server.port,
|
self.server.domain, self.server.port,
|
||||||
self.server.http_prefix,
|
self.server.http_prefix,
|
||||||
answer, False, False,
|
mentions_str + ' ' + answer,
|
||||||
|
False, False,
|
||||||
comments_enabled,
|
comments_enabled,
|
||||||
attach_image_filename, media_type,
|
attach_image_filename,
|
||||||
image_description, city,
|
media_type, image_description, city,
|
||||||
in_reply_to,
|
in_reply_to, in_reply_to_atom_uri,
|
||||||
in_reply_to_atom_uri,
|
subject, self.server.debug,
|
||||||
subject,
|
|
||||||
schedule_post,
|
schedule_post,
|
||||||
event_date,
|
event_date, event_time,
|
||||||
event_time, event_end_time,
|
event_end_time,
|
||||||
location, False,
|
location, self.server.system_language,
|
||||||
self.server.system_language,
|
|
||||||
conversation_id,
|
conversation_id,
|
||||||
self.server.low_bandwidth,
|
self.server.low_bandwidth,
|
||||||
self.server.content_license_url,
|
self.server.content_license_url,
|
||||||
languages_understood,
|
languages_understood, False,
|
||||||
self.server.translate)
|
self.server.translate)
|
||||||
if message_json:
|
if message_json:
|
||||||
|
# NOTE: content and contentMap are not required, but we will keep
|
||||||
|
# them in there so that the post does not get filtered out by
|
||||||
|
# inbox processing.
|
||||||
# name field contains the answer
|
# name field contains the answer
|
||||||
message_json['object']['name'] = answer
|
message_json['object']['name'] = answer
|
||||||
if self._post_to_outbox(message_json,
|
if self._post_to_outbox(message_json,
|
||||||
|
@ -1732,17 +1737,16 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
# protocol handler. See https://fedi-to.github.io/protocol-handler.html
|
# protocol handler. See https://fedi-to.github.io/protocol-handler.html
|
||||||
if self.path.startswith('/.well-known/protocol-handler'):
|
if self.path.startswith('/.well-known/protocol-handler'):
|
||||||
if calling_domain.endswith('.onion'):
|
if calling_domain.endswith('.onion'):
|
||||||
protocol_url = \
|
protocol_url, _ = \
|
||||||
wellknown_protocol_handler(self.path,
|
wellknown_protocol_handler(self.path, 'http',
|
||||||
self.server.base_dir, 'http',
|
|
||||||
self.server.onion_domain)
|
self.server.onion_domain)
|
||||||
elif calling_domain.endswith('.i2p'):
|
elif calling_domain.endswith('.i2p'):
|
||||||
protocol_url = \
|
protocol_url, _ = \
|
||||||
wellknown_protocol_handler(self.path, self.server.base_dir,
|
wellknown_protocol_handler(self.path,
|
||||||
'http', self.server.i2p_domain)
|
'http', self.server.i2p_domain)
|
||||||
else:
|
else:
|
||||||
protocol_url = \
|
protocol_url, _ = \
|
||||||
wellknown_protocol_handler(self.path, self.server.base_dir,
|
wellknown_protocol_handler(self.path,
|
||||||
self.server.http_prefix,
|
self.server.http_prefix,
|
||||||
self.server.domain_full)
|
self.server.domain_full)
|
||||||
if protocol_url:
|
if protocol_url:
|
||||||
|
@ -8082,6 +8086,16 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
"platform": "fdroid",
|
"platform": "fdroid",
|
||||||
"url": app3
|
"url": app3
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"protocol_handlers": [
|
||||||
|
{
|
||||||
|
"protocol": "web+ap",
|
||||||
|
"url": "?target=%s"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol": "web+epicyon",
|
||||||
|
"url": "?target=%s"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
msg_str = json.dumps(manifest, ensure_ascii=False)
|
msg_str = json.dumps(manifest, ensure_ascii=False)
|
||||||
|
|
74
inbox.py
74
inbox.py
|
@ -119,6 +119,7 @@ from posts import send_signed_json
|
||||||
from posts import send_to_followers_thread
|
from posts import send_to_followers_thread
|
||||||
from webapp_post import individual_post_as_html
|
from webapp_post import individual_post_as_html
|
||||||
from question import question_update_votes
|
from question import question_update_votes
|
||||||
|
from question import is_vote
|
||||||
from media import replace_you_tube
|
from media import replace_you_tube
|
||||||
from media import replace_twitter
|
from media import replace_twitter
|
||||||
from git import is_git_patch
|
from git import is_git_patch
|
||||||
|
@ -3820,6 +3821,20 @@ def _is_valid_dm(base_dir: str, nickname: str, domain: str, port: int,
|
||||||
|
|
||||||
# Not sending to yourself
|
# Not sending to yourself
|
||||||
if not sending_to_self:
|
if not sending_to_self:
|
||||||
|
# is this a vote on a question?
|
||||||
|
if is_vote(base_dir, nickname, domain,
|
||||||
|
post_json_object, debug):
|
||||||
|
# make the content the same as the vote answer
|
||||||
|
post_json_object['object']['content'] = \
|
||||||
|
post_json_object['object']['name']
|
||||||
|
# remove any other content
|
||||||
|
if post_json_object['object'].get("contentMap"):
|
||||||
|
del post_json_object['object']['contentMap']
|
||||||
|
# remove any summary / cw
|
||||||
|
post_json_object['object']['summary'] = None
|
||||||
|
if post_json_object['object'].get("summaryMap"):
|
||||||
|
del post_json_object['object']['summaryMap']
|
||||||
|
return True
|
||||||
# get the handle of the DM sender
|
# get the handle of the DM sender
|
||||||
send_h = sending_actor_nickname + '@' + sending_actor_domain
|
send_h = sending_actor_nickname + '@' + sending_actor_domain
|
||||||
# check the follow
|
# check the follow
|
||||||
|
@ -3831,11 +3846,12 @@ def _is_valid_dm(base_dir: str, nickname: str, domain: str, port: int,
|
||||||
# send back a bounce DM
|
# send back a bounce DM
|
||||||
if post_json_object.get('id') and \
|
if post_json_object.get('id') and \
|
||||||
post_json_object.get('object'):
|
post_json_object.get('object'):
|
||||||
|
obj_has_dict = has_object_dict(post_json_object)
|
||||||
# don't send bounces back to
|
# don't send bounces back to
|
||||||
# replies to bounce messages
|
# replies to bounce messages
|
||||||
obj = post_json_object['object']
|
obj = post_json_object['object']
|
||||||
if isinstance(obj, dict):
|
if obj_has_dict and \
|
||||||
if not obj.get('inReplyTo'):
|
not obj.get('inReplyTo'):
|
||||||
bounced_id = \
|
bounced_id = \
|
||||||
remove_id_ending(post_json_object['id'])
|
remove_id_ending(post_json_object['id'])
|
||||||
bounce_chat = False
|
bounce_chat = False
|
||||||
|
@ -3892,7 +3908,8 @@ def _receive_question_vote(server, base_dir: str, nickname: str, domain: str,
|
||||||
"""
|
"""
|
||||||
# if this is a reply to a question then update the votes
|
# if this is a reply to a question then update the votes
|
||||||
question_json, question_post_filename = \
|
question_json, question_post_filename = \
|
||||||
question_update_votes(base_dir, nickname, domain, post_json_object)
|
question_update_votes(base_dir, nickname, domain,
|
||||||
|
post_json_object, debug)
|
||||||
if not question_json:
|
if not question_json:
|
||||||
return
|
return
|
||||||
if not question_post_filename:
|
if not question_post_filename:
|
||||||
|
@ -4424,8 +4441,33 @@ def _inbox_after_initial(server, inbox_start_time,
|
||||||
post_json_object = message_json['post']
|
post_json_object = message_json['post']
|
||||||
else:
|
else:
|
||||||
post_json_object = message_json
|
post_json_object = message_json
|
||||||
|
|
||||||
nickname = handle.split('@')[0]
|
nickname = handle.split('@')[0]
|
||||||
|
|
||||||
|
if is_vote(base_dir, nickname, domain, post_json_object, debug):
|
||||||
|
_receive_question_vote(server, base_dir, nickname, domain,
|
||||||
|
http_prefix, handle, debug,
|
||||||
|
post_json_object, recent_posts_cache,
|
||||||
|
session, session_onion, session_i2p,
|
||||||
|
onion_domain, i2p_domain, port,
|
||||||
|
federation_list, send_threads, post_log,
|
||||||
|
cached_webfingers, person_cache,
|
||||||
|
signing_priv_key_pem,
|
||||||
|
max_recent_posts, translate,
|
||||||
|
allow_deletion,
|
||||||
|
yt_replace_domain,
|
||||||
|
twitter_replacement_domain,
|
||||||
|
peertube_instances,
|
||||||
|
allow_local_network_access,
|
||||||
|
theme_name, system_language,
|
||||||
|
max_like_count,
|
||||||
|
cw_lists, lists_enabled,
|
||||||
|
bold_reading, dogwhistles,
|
||||||
|
server.min_images_for_accounts)
|
||||||
|
fitness_performance(inbox_start_time, server.fitness,
|
||||||
|
'INBOX', '_receive_question_vote',
|
||||||
|
debug)
|
||||||
|
inbox_start_time = time.time()
|
||||||
|
|
||||||
json_obj = None
|
json_obj = None
|
||||||
domain_full = get_full_domain(domain, port)
|
domain_full = get_full_domain(domain, port)
|
||||||
if _valid_post_content(base_dir, nickname, domain,
|
if _valid_post_content(base_dir, nickname, domain,
|
||||||
|
@ -4495,30 +4537,6 @@ def _inbox_after_initial(server, inbox_start_time,
|
||||||
debug)
|
debug)
|
||||||
inbox_start_time = time.time()
|
inbox_start_time = time.time()
|
||||||
|
|
||||||
_receive_question_vote(server, base_dir, nickname, domain,
|
|
||||||
http_prefix, handle, debug,
|
|
||||||
post_json_object, recent_posts_cache,
|
|
||||||
session, session_onion, session_i2p,
|
|
||||||
onion_domain, i2p_domain, port,
|
|
||||||
federation_list, send_threads, post_log,
|
|
||||||
cached_webfingers, person_cache,
|
|
||||||
signing_priv_key_pem,
|
|
||||||
max_recent_posts, translate,
|
|
||||||
allow_deletion,
|
|
||||||
yt_replace_domain,
|
|
||||||
twitter_replacement_domain,
|
|
||||||
peertube_instances,
|
|
||||||
allow_local_network_access,
|
|
||||||
theme_name, system_language,
|
|
||||||
max_like_count,
|
|
||||||
cw_lists, lists_enabled,
|
|
||||||
bold_reading, dogwhistles,
|
|
||||||
server.min_images_for_accounts)
|
|
||||||
fitness_performance(inbox_start_time, server.fitness,
|
|
||||||
'INBOX', '_receive_question_vote',
|
|
||||||
debug)
|
|
||||||
inbox_start_time = time.time()
|
|
||||||
|
|
||||||
is_reply_to_muted_post = False
|
is_reply_to_muted_post = False
|
||||||
|
|
||||||
if not is_group:
|
if not is_group:
|
||||||
|
|
133
question.py
133
question.py
|
@ -15,52 +15,105 @@ from utils import has_object_dict
|
||||||
from utils import text_in_file
|
from utils import text_in_file
|
||||||
|
|
||||||
|
|
||||||
def question_update_votes(base_dir: str, nickname: str, domain: str,
|
def is_vote(base_dir: str, nickname: str, domain: str,
|
||||||
reply_json: {}) -> ({}, str):
|
post_json_object: {}, debug: bool) -> bool:
|
||||||
""" For a given reply update the votes on a question
|
""" is the given post a vote on a Question?
|
||||||
Returns the question json object if the vote totals were changed
|
|
||||||
"""
|
"""
|
||||||
if not has_object_dict(reply_json):
|
post_obj = post_json_object
|
||||||
return None, None
|
if has_object_dict(post_json_object):
|
||||||
if not reply_json['object'].get('inReplyTo'):
|
post_obj = post_json_object['object']
|
||||||
return None, None
|
|
||||||
if not reply_json['object']['inReplyTo']:
|
if not post_obj.get('inReplyTo'):
|
||||||
return None, None
|
return False
|
||||||
if not isinstance(reply_json['object']['inReplyTo'], str):
|
if not isinstance(post_obj['inReplyTo'], str):
|
||||||
return None, None
|
return False
|
||||||
if not reply_json['object'].get('name'):
|
if not post_obj.get('name'):
|
||||||
return None, None
|
return False
|
||||||
in_reply_to = reply_json['object']['inReplyTo']
|
|
||||||
|
if debug:
|
||||||
|
print('VOTE: ' + str(post_obj))
|
||||||
|
|
||||||
|
# is the replied to post a Question?
|
||||||
|
in_reply_to = post_obj['inReplyTo']
|
||||||
question_post_filename = \
|
question_post_filename = \
|
||||||
locate_post(base_dir, nickname, domain, in_reply_to)
|
locate_post(base_dir, nickname, domain, in_reply_to)
|
||||||
if not question_post_filename:
|
if not question_post_filename:
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: question does not exist ' + in_reply_to)
|
||||||
|
return False
|
||||||
question_json = load_json(question_post_filename)
|
question_json = load_json(question_post_filename)
|
||||||
if not question_json:
|
if not question_json:
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: invalid json ' + question_post_filename)
|
||||||
|
return False
|
||||||
if not has_object_dict(question_json):
|
if not has_object_dict(question_json):
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: question without object ' +
|
||||||
|
question_post_filename)
|
||||||
|
return False
|
||||||
if not question_json['object'].get('type'):
|
if not question_json['object'].get('type'):
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: question without type ' +
|
||||||
|
question_post_filename)
|
||||||
|
return False
|
||||||
if question_json['type'] != 'Question':
|
if question_json['type'] != 'Question':
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: not a question ' +
|
||||||
|
question_post_filename)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# does the question have options?
|
||||||
if not question_json['object'].get('oneOf'):
|
if not question_json['object'].get('oneOf'):
|
||||||
return None, None
|
if debug:
|
||||||
|
print('VOTE REJECT: question has no options ' +
|
||||||
|
question_post_filename)
|
||||||
|
return False
|
||||||
if not isinstance(question_json['object']['oneOf'], list):
|
if not isinstance(question_json['object']['oneOf'], list):
|
||||||
return None, None
|
if debug:
|
||||||
if not question_json['object'].get('content'):
|
print('VOTE REJECT: question options is not a list ' +
|
||||||
return None, None
|
question_post_filename)
|
||||||
reply_vote = reply_json['object']['name']
|
return False
|
||||||
|
|
||||||
# does the reply name field match any possible question option?
|
# does the reply name field match any possible question option?
|
||||||
found_answer = None, None
|
reply_vote = post_json_object['name']
|
||||||
|
found_answer_json = None
|
||||||
for possible_answer in question_json['object']['oneOf']:
|
for possible_answer in question_json['object']['oneOf']:
|
||||||
if not possible_answer.get('name'):
|
if not possible_answer.get('name'):
|
||||||
continue
|
continue
|
||||||
if possible_answer['name'] == reply_vote:
|
if possible_answer['name'] == reply_vote:
|
||||||
found_answer = possible_answer
|
found_answer_json = possible_answer
|
||||||
break
|
break
|
||||||
if not found_answer:
|
if not found_answer_json:
|
||||||
|
if debug:
|
||||||
|
print('VOTE REJECT: question answer not found ' +
|
||||||
|
question_post_filename + ' ' + reply_vote)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
|
reply_json: {}, debug: bool) -> ({}, str):
|
||||||
|
""" For a given reply update the votes on a question
|
||||||
|
Returns the question json object if the vote totals were changed
|
||||||
|
"""
|
||||||
|
if not is_vote(base_dir, nickname, domain, reply_json, debug):
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
post_obj = reply_json
|
||||||
|
if has_object_dict(reply_json):
|
||||||
|
post_obj = reply_json['object']
|
||||||
|
reply_vote = post_obj['name']
|
||||||
|
|
||||||
|
in_reply_to = post_obj['inReplyTo']
|
||||||
|
question_post_filename = \
|
||||||
|
locate_post(base_dir, nickname, domain, in_reply_to)
|
||||||
|
if not question_post_filename:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
question_json = load_json(question_post_filename)
|
||||||
|
if not question_json:
|
||||||
|
return None, None
|
||||||
|
|
||||||
# update the voters file
|
# update the voters file
|
||||||
voters_file_separator = ';;;'
|
voters_file_separator = ';;;'
|
||||||
voters_filename = question_post_filename.replace('.json', '.voters')
|
voters_filename = question_post_filename.replace('.json', '.voters')
|
||||||
|
@ -71,7 +124,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
encoding='utf-8') as voters_file:
|
encoding='utf-8') as voters_file:
|
||||||
voters_file.write(reply_json['actor'] +
|
voters_file.write(reply_json['actor'] +
|
||||||
voters_file_separator +
|
voters_file_separator +
|
||||||
found_answer + '\n')
|
reply_vote + '\n')
|
||||||
except OSError:
|
except OSError:
|
||||||
print('EX: unable to write voters file ' + voters_filename)
|
print('EX: unable to write voters file ' + voters_filename)
|
||||||
else:
|
else:
|
||||||
|
@ -82,7 +135,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
encoding='utf-8') as voters_file:
|
encoding='utf-8') as voters_file:
|
||||||
voters_file.write(reply_json['actor'] +
|
voters_file.write(reply_json['actor'] +
|
||||||
voters_file_separator +
|
voters_file_separator +
|
||||||
found_answer + '\n')
|
reply_vote + '\n')
|
||||||
except OSError:
|
except OSError:
|
||||||
print('EX: unable to append to voters file ' + voters_filename)
|
print('EX: unable to append to voters file ' + voters_filename)
|
||||||
else:
|
else:
|
||||||
|
@ -96,7 +149,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
if vote_line.startswith(reply_json['actor'] +
|
if vote_line.startswith(reply_json['actor'] +
|
||||||
voters_file_separator):
|
voters_file_separator):
|
||||||
new_vote_line = reply_json['actor'] + \
|
new_vote_line = reply_json['actor'] + \
|
||||||
voters_file_separator + found_answer + '\n'
|
voters_file_separator + reply_vote + '\n'
|
||||||
if vote_line == new_vote_line:
|
if vote_line == new_vote_line:
|
||||||
break
|
break
|
||||||
save_voters_file = True
|
save_voters_file = True
|
||||||
|
@ -114,6 +167,7 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
voters_filename)
|
voters_filename)
|
||||||
else:
|
else:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# update the vote counts
|
# update the vote counts
|
||||||
question_totals_changed = False
|
question_totals_changed = False
|
||||||
for possible_answer in question_json['object']['oneOf']:
|
for possible_answer in question_json['object']['oneOf']:
|
||||||
|
@ -131,25 +185,26 @@ def question_update_votes(base_dir: str, nickname: str, domain: str,
|
||||||
question_totals_changed = True
|
question_totals_changed = True
|
||||||
if not question_totals_changed:
|
if not question_totals_changed:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
# save the question with altered totals
|
# save the question with altered totals
|
||||||
save_json(question_json, question_post_filename)
|
save_json(question_json, question_post_filename)
|
||||||
return question_json, question_post_filename
|
return question_json, question_post_filename
|
||||||
|
|
||||||
|
|
||||||
def is_question(post_object_json: {}) -> bool:
|
def is_question(post_json_object: {}) -> bool:
|
||||||
""" is the given post a question?
|
""" is the given post a question?
|
||||||
"""
|
"""
|
||||||
if post_object_json['type'] != 'Create' and \
|
if post_json_object['type'] != 'Create' and \
|
||||||
post_object_json['type'] != 'Update':
|
post_json_object['type'] != 'Update':
|
||||||
return False
|
return False
|
||||||
if not has_object_dict(post_object_json):
|
if not has_object_dict(post_json_object):
|
||||||
return False
|
return False
|
||||||
if not post_object_json['object'].get('type'):
|
if not post_json_object['object'].get('type'):
|
||||||
return False
|
return False
|
||||||
if post_object_json['object']['type'] != 'Question':
|
if post_json_object['object']['type'] != 'Question':
|
||||||
return False
|
return False
|
||||||
if not post_object_json['object'].get('oneOf'):
|
if not post_json_object['object'].get('oneOf'):
|
||||||
return False
|
return False
|
||||||
if not isinstance(post_object_json['object']['oneOf'], list):
|
if not isinstance(post_json_object['object']['oneOf'], list):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -121,6 +121,7 @@ Let's see an example! Let's say Alyssa wants to catch up with her friend, Ben Bi
|
||||||
"rejectReplies": False,
|
"rejectReplies": False,
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"attachment": [],
|
"attachment": [],
|
||||||
|
"conversation": "3728447592750257207548",
|
||||||
"summary": "Book",
|
"summary": "Book",
|
||||||
"content": "Say, did you finish reading that book I lent you?"
|
"content": "Say, did you finish reading that book I lent you?"
|
||||||
}
|
}
|
||||||
|
@ -149,6 +150,7 @@ Since this is a non-activity object, the server recognizes that this is an objec
|
||||||
"rejectReplies": False,
|
"rejectReplies": False,
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"attachment": [],
|
"attachment": [],
|
||||||
|
"conversation": "3728447592750257207548",
|
||||||
"summary": "Book",
|
"summary": "Book",
|
||||||
"content": "Say, did you finish reading that book I lent you?"
|
"content": "Say, did you finish reading that book I lent you?"
|
||||||
}
|
}
|
||||||
|
@ -182,6 +184,7 @@ Cool! A while later, Alyssa checks what new messages she's gotten. Her phone pol
|
||||||
"published": "2039-10-15T12:45:45Z",
|
"published": "2039-10-15T12:45:45Z",
|
||||||
"rejectReplies": False,
|
"rejectReplies": False,
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
|
"conversation": "3728447592750257207548",
|
||||||
"content": "<p>Argh, yeah, sorry, I'll get it back to you tomorrow.</p>
|
"content": "<p>Argh, yeah, sorry, I'll get it back to you tomorrow.</p>
|
||||||
<p>I was reviewing the section on register machines,
|
<p>I was reviewing the section on register machines,
|
||||||
since it's been a while since I wrote one.</p>"
|
since it's been a while since I wrote one.</p>"
|
||||||
|
@ -189,6 +192,8 @@ Cool! A while later, Alyssa checks what new messages she's gotten. Her phone pol
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here the *conversation* field is any unique identifier grouping the posts within this thread together. Hence even if some posts within a chain of replies are subsequently deleted the overall thread can still be obtained.
|
||||||
|
|
||||||
Alyssa is relieved, and likes Ben's post:
|
Alyssa is relieved, and likes Ben's post:
|
||||||
|
|
||||||
### Example 5
|
### Example 5
|
||||||
|
@ -241,6 +246,7 @@ Feeling happy about things, she decides to post a public message to her follower
|
||||||
"https://www.w3.org/ns/activitystreams#Public"],
|
"https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"published": "2039-10-15T13:11:16Z",
|
"published": "2039-10-15T13:11:16Z",
|
||||||
"rejectReplies": False,
|
"rejectReplies": False,
|
||||||
|
"conversation": "57834623544792956335",
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"content": "Lending books to friends is nice. Getting them back is even nicer! :)"
|
"content": "Lending books to friends is nice. Getting them back is even nicer! :)"
|
||||||
}
|
}
|
||||||
|
@ -303,6 +309,7 @@ As an example, if example.com receives the activity
|
||||||
"attributedTo": "https://example.org/users/alice",
|
"attributedTo": "https://example.org/users/alice",
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"published": "2031-03-27T14:10:25Z",
|
"published": "2031-03-27T14:10:25Z",
|
||||||
|
"conversation": "7342325925675729",
|
||||||
"content": "I'm a goat"
|
"content": "I'm a goat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -347,6 +354,7 @@ The value of `source` is itself an object which uses its own `content` and `medi
|
||||||
{"@language": "en"}],
|
{"@language": "en"}],
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"id": "http://postparty.example/users/username/statuses/2415",
|
"id": "http://postparty.example/users/username/statuses/2415",
|
||||||
|
"conversation": "45327948756365",
|
||||||
"mediaType": "text/html"
|
"mediaType": "text/html"
|
||||||
"content": "<p>I <em>really</em> like strawberries!</p>",
|
"content": "<p>I <em>really</em> like strawberries!</p>",
|
||||||
"source": {
|
"source": {
|
||||||
|
@ -386,6 +394,7 @@ In the case of attached images, the `name` field can be used to supply a descrip
|
||||||
"published": "2032-09-14T19:17:02Z",
|
"published": "2032-09-14T19:17:02Z",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"sensitive": False,
|
"sensitive": False,
|
||||||
|
"conversation": "67243561372468724",
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"content": "This is a post with an attached image",
|
"content": "This is a post with an attached image",
|
||||||
"attachment": [
|
"attachment": [
|
||||||
|
@ -422,6 +431,7 @@ When a new post is created, if it has `content` containing one or more hashtags
|
||||||
"to": ["https://example.net/users/fearghus/followers",
|
"to": ["https://example.net/users/fearghus/followers",
|
||||||
"https://www.w3.org/ns/activitystreams#Public"],
|
"https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"published": "2032-05-29T15:08:47Z",
|
"published": "2032-05-29T15:08:47Z",
|
||||||
|
"conversation": "5342890426429480",
|
||||||
"mediaType": "text/html",
|
"mediaType": "text/html",
|
||||||
"content": "Posting with <a href=\"https://example.net/tags/ActivityPub\" class=\"mention hashtag\" rel=\"tag\">#<span>ActivityPub</span></a>",
|
"content": "Posting with <a href=\"https://example.net/tags/ActivityPub\" class=\"mention hashtag\" rel=\"tag\">#<span>ActivityPub</span></a>",
|
||||||
"tag": [
|
"tag": [
|
||||||
|
@ -768,6 +778,7 @@ For example, when Chris likes the following article by Amy:
|
||||||
"type": "Article",
|
"type": "Article",
|
||||||
"name": "Minimal ActivityPub update client",
|
"name": "Minimal ActivityPub update client",
|
||||||
"content": "Today I finished morph, a client for posting ActivityStreams2...",
|
"content": "Today I finished morph, a client for posting ActivityStreams2...",
|
||||||
|
"conversation": "1894367735757303",
|
||||||
"attributedTo": "https://rhiaro.co.uk/@amy",
|
"attributedTo": "https://rhiaro.co.uk/@amy",
|
||||||
"to": ["https://rhiaro.co.uk/followers"],
|
"to": ["https://rhiaro.co.uk/followers"],
|
||||||
"cc": ["https://e14n.com/@evan"]
|
"cc": ["https://e14n.com/@evan"]
|
||||||
|
@ -837,6 +848,7 @@ The above example could be converted to this:
|
||||||
"id": "https://example.com/@mallory/statuses/72",
|
"id": "https://example.com/@mallory/statuses/72",
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"attributedTo": "https://example.net/users/mallory",
|
"attributedTo": "https://example.net/users/mallory",
|
||||||
|
"conversation": "784365462623755",
|
||||||
"content": "This is a note",
|
"content": "This is a note",
|
||||||
"published": "2015-02-10T15:04:55Z",
|
"published": "2015-02-10T15:04:55Z",
|
||||||
"to": ["https://example.org/@john"],
|
"to": ["https://example.org/@john"],
|
||||||
|
|
27
webfinger.py
27
webfinger.py
|
@ -252,12 +252,12 @@ def webfinger_meta(http_prefix: str, domain_full: str) -> str:
|
||||||
return meta_str
|
return meta_str
|
||||||
|
|
||||||
|
|
||||||
def wellknown_protocol_handler(path: str, base_dir: str,
|
def wellknown_protocol_handler(path: str, http_prefix: str,
|
||||||
http_prefix: str, domain_full: str) -> {}:
|
domain_full: str) -> ({}, str):
|
||||||
"""See https://fedi-to.github.io/protocol-handler.html
|
"""See https://fedi-to.github.io/protocol-handler.html
|
||||||
"""
|
"""
|
||||||
if not path.startswith('/.well-known/protocol-handler?'):
|
if not path.startswith('/.well-known/protocol-handler?'):
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
if 'target=' in path:
|
if 'target=' in path:
|
||||||
path = urllib.parse.unquote(path)
|
path = urllib.parse.unquote(path)
|
||||||
|
@ -265,11 +265,11 @@ def wellknown_protocol_handler(path: str, base_dir: str,
|
||||||
if ';' in target:
|
if ';' in target:
|
||||||
target = target.split(';')[0]
|
target = target.split(';')[0]
|
||||||
if not target:
|
if not target:
|
||||||
return None
|
return None, None
|
||||||
if not target.startswith('web+epicyon:') and \
|
if not target.startswith('web+epicyon:') and \
|
||||||
not target.startswith('web+mastodon:') and \
|
not target.startswith('web+mastodon:') and \
|
||||||
not target.startswith('web+ap:'):
|
not target.startswith('web+ap:'):
|
||||||
return None
|
return None, None
|
||||||
handle = target.split(':', 1)[1].strip()
|
handle = target.split(':', 1)[1].strip()
|
||||||
if handle.startswith('//'):
|
if handle.startswith('//'):
|
||||||
handle = handle[2:]
|
handle = handle[2:]
|
||||||
|
@ -277,14 +277,21 @@ def wellknown_protocol_handler(path: str, base_dir: str,
|
||||||
handle = handle[1:]
|
handle = handle[1:]
|
||||||
if '@' in handle:
|
if '@' in handle:
|
||||||
nickname = handle.split('@')[0]
|
nickname = handle.split('@')[0]
|
||||||
domain = handle.split('@')[1]
|
domain_and_path = handle.split('@')[1]
|
||||||
else:
|
else:
|
||||||
nickname = handle
|
nickname = handle
|
||||||
domain = domain_full
|
domain_and_path = domain_full
|
||||||
# not an open redirect
|
# not an open redirect
|
||||||
if domain == domain_full:
|
if domain_and_path.startswith(domain_full):
|
||||||
return http_prefix + '://' + domain_full + '/users/' + nickname
|
command = ''
|
||||||
return None
|
if '/' in nickname:
|
||||||
|
command = nickname.split('/')[0]
|
||||||
|
nickname = nickname.split('/')[1]
|
||||||
|
domain_length = len(domain_full)
|
||||||
|
path_str = domain_and_path[domain_length:]
|
||||||
|
return http_prefix + '://' + domain_full + \
|
||||||
|
'/users/' + nickname + path_str, command
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def webfinger_lookup(path: str, base_dir: str,
|
def webfinger_lookup(path: str, base_dir: str,
|
||||||
|
|
Loading…
Reference in New Issue