2020-04-04 10:12:34 +00:00
|
|
|
__filename__ = "question.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2024-01-21 19:01:20 +00:00
|
|
|
__version__ = "1.5.0"
|
2020-04-04 10:12:34 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2020-04-04 10:12:34 +00:00
|
|
|
__status__ = "Production"
|
2021-06-15 15:08:12 +00:00
|
|
|
__module_group__ = "ActivityPub"
|
2019-11-29 18:46:21 +00:00
|
|
|
|
|
|
|
import os
|
2021-12-26 20:36:08 +00:00
|
|
|
from utils import locate_post
|
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 10:57:03 +00:00
|
|
|
from utils import has_object_dict
|
2022-06-10 11:43:33 +00:00
|
|
|
from utils import text_in_file
|
2023-02-28 18:01:24 +00:00
|
|
|
from utils import dangerous_markup
|
2023-12-24 23:42:38 +00:00
|
|
|
from utils import get_reply_to
|
2024-01-09 16:59:23 +00:00
|
|
|
from utils import get_actor_from_post
|
2019-11-29 18:46:21 +00:00
|
|
|
|
2020-04-04 10:12:34 +00:00
|
|
|
|
2023-01-12 10:45:39 +00:00
|
|
|
def is_vote(base_dir: str, nickname: str, domain: str,
|
2023-01-12 11:58:41 +00:00
|
|
|
post_json_object: {}, debug: bool) -> bool:
|
2023-01-12 10:45:39 +00:00
|
|
|
""" is the given post a vote on a Question?
|
2019-11-29 18:46:21 +00:00
|
|
|
"""
|
2023-01-12 10:45:39 +00:00
|
|
|
post_obj = post_json_object
|
|
|
|
if has_object_dict(post_json_object):
|
|
|
|
post_obj = post_json_object['object']
|
|
|
|
|
2023-12-24 23:42:38 +00:00
|
|
|
reply_id = get_reply_to(post_obj)
|
|
|
|
if not reply_id:
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2023-12-24 23:42:38 +00:00
|
|
|
if not isinstance(reply_id, str):
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
|
|
|
if not post_obj.get('name'):
|
|
|
|
return False
|
|
|
|
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE: ' + str(post_obj))
|
|
|
|
|
2023-01-12 10:45:39 +00:00
|
|
|
# is the replied to post a Question?
|
2023-12-24 23:42:38 +00:00
|
|
|
in_reply_to = reply_id
|
2021-12-31 11:23:00 +00:00
|
|
|
question_post_filename = \
|
|
|
|
locate_post(base_dir, nickname, domain, in_reply_to)
|
|
|
|
if not question_post_filename:
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question does not exist ' + in_reply_to)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2021-12-31 11:23:00 +00:00
|
|
|
question_json = load_json(question_post_filename)
|
|
|
|
if not question_json:
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: invalid json ' + question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2021-12-31 11:23:00 +00:00
|
|
|
if not has_object_dict(question_json):
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question without object ' +
|
|
|
|
question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2021-12-31 11:23:00 +00:00
|
|
|
if not question_json['object'].get('type'):
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question without type ' +
|
|
|
|
question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2021-12-31 11:23:00 +00:00
|
|
|
if question_json['type'] != 'Question':
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: not a question ' +
|
|
|
|
question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
# does the question have options?
|
2021-12-31 11:23:00 +00:00
|
|
|
if not question_json['object'].get('oneOf'):
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question has no options ' +
|
|
|
|
question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
2021-12-31 11:23:00 +00:00
|
|
|
if not isinstance(question_json['object']['oneOf'], list):
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question options is not a list ' +
|
|
|
|
question_post_filename)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
|
|
|
|
2020-01-30 10:11:08 +00:00
|
|
|
# does the reply name field match any possible question option?
|
2023-01-12 10:45:39 +00:00
|
|
|
reply_vote = post_json_object['name']
|
|
|
|
found_answer_json = None
|
2021-12-31 11:23:00 +00:00
|
|
|
for possible_answer in question_json['object']['oneOf']:
|
|
|
|
if not possible_answer.get('name'):
|
2019-11-29 18:46:21 +00:00
|
|
|
continue
|
2021-12-31 11:23:00 +00:00
|
|
|
if possible_answer['name'] == reply_vote:
|
2023-01-12 10:45:39 +00:00
|
|
|
found_answer_json = possible_answer
|
2019-11-29 18:46:21 +00:00
|
|
|
break
|
2023-01-12 10:45:39 +00:00
|
|
|
if not found_answer_json:
|
2023-01-12 11:58:41 +00:00
|
|
|
if debug:
|
|
|
|
print('VOTE REJECT: question answer not found ' +
|
|
|
|
question_post_filename + ' ' + reply_vote)
|
2023-01-12 10:45:39 +00:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def question_update_votes(base_dir: str, nickname: str, domain: str,
|
2023-01-12 11:58:41 +00:00
|
|
|
reply_json: {}, debug: bool) -> ({}, str):
|
2023-01-12 10:45:39 +00:00
|
|
|
""" For a given reply update the votes on a question
|
|
|
|
Returns the question json object if the vote totals were changed
|
|
|
|
"""
|
2023-01-12 11:58:41 +00:00
|
|
|
if not is_vote(base_dir, nickname, domain, reply_json, debug):
|
2021-11-03 21:53:30 +00:00
|
|
|
return None, None
|
2023-01-12 10:45:39 +00:00
|
|
|
|
|
|
|
post_obj = reply_json
|
|
|
|
if has_object_dict(reply_json):
|
|
|
|
post_obj = reply_json['object']
|
|
|
|
reply_vote = post_obj['name']
|
|
|
|
|
2023-12-24 23:42:38 +00:00
|
|
|
in_reply_to = get_reply_to(post_obj)
|
2023-01-12 10:45:39 +00:00
|
|
|
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
|
|
|
|
|
2019-11-29 18:46:21 +00:00
|
|
|
# update the voters file
|
2021-12-31 11:23:00 +00:00
|
|
|
voters_file_separator = ';;;'
|
|
|
|
voters_filename = question_post_filename.replace('.json', '.voters')
|
2024-01-09 16:59:23 +00:00
|
|
|
actor_url = get_actor_from_post(reply_json)
|
2021-12-31 11:23:00 +00:00
|
|
|
if not os.path.isfile(voters_filename):
|
2019-11-29 18:46:21 +00:00
|
|
|
# create a new voters file
|
2021-12-31 11:23:00 +00:00
|
|
|
try:
|
2022-06-09 14:46:30 +00:00
|
|
|
with open(voters_filename, 'w+',
|
2024-07-14 13:01:46 +00:00
|
|
|
encoding='utf-8') as fp_voters:
|
|
|
|
fp_voters.write(actor_url +
|
|
|
|
voters_file_separator +
|
|
|
|
reply_vote + '\n')
|
2021-12-31 11:23:00 +00:00
|
|
|
except OSError:
|
|
|
|
print('EX: unable to write voters file ' + voters_filename)
|
2019-11-29 18:46:21 +00:00
|
|
|
else:
|
2024-01-09 16:59:23 +00:00
|
|
|
if not text_in_file(actor_url, voters_filename):
|
2019-11-29 18:46:21 +00:00
|
|
|
# append to the voters file
|
2021-12-31 11:23:00 +00:00
|
|
|
try:
|
2022-06-09 14:46:30 +00:00
|
|
|
with open(voters_filename, 'a+',
|
2024-07-14 13:01:46 +00:00
|
|
|
encoding='utf-8') as fp_voters:
|
|
|
|
fp_voters.write(actor_url +
|
|
|
|
voters_file_separator +
|
|
|
|
reply_vote + '\n')
|
2021-12-31 11:23:00 +00:00
|
|
|
except OSError:
|
|
|
|
print('EX: unable to append to voters file ' + voters_filename)
|
2019-11-29 18:46:21 +00:00
|
|
|
else:
|
|
|
|
# change an entry in the voters file
|
2024-07-13 14:38:11 +00:00
|
|
|
lines = []
|
|
|
|
try:
|
|
|
|
with open(voters_filename, 'r',
|
2024-07-14 13:01:46 +00:00
|
|
|
encoding='utf-8') as fp_voters:
|
|
|
|
lines = fp_voters.readlines()
|
2024-07-13 14:38:11 +00:00
|
|
|
except OSError:
|
|
|
|
print('EX: question_update_votes unable to read ' +
|
|
|
|
voters_filename)
|
|
|
|
|
|
|
|
newlines = []
|
|
|
|
save_voters_file = False
|
|
|
|
for vote_line in lines:
|
|
|
|
if vote_line.startswith(actor_url +
|
|
|
|
voters_file_separator):
|
|
|
|
new_vote_line = actor_url + \
|
|
|
|
voters_file_separator + reply_vote + '\n'
|
|
|
|
if vote_line == new_vote_line:
|
|
|
|
break
|
|
|
|
save_voters_file = True
|
|
|
|
newlines.append(new_vote_line)
|
2019-11-29 19:22:11 +00:00
|
|
|
else:
|
2024-07-13 14:38:11 +00:00
|
|
|
newlines.append(vote_line)
|
|
|
|
if save_voters_file:
|
|
|
|
try:
|
|
|
|
with open(voters_filename, 'w+',
|
2024-07-14 13:01:46 +00:00
|
|
|
encoding='utf-8') as fp_voters:
|
2024-07-13 14:38:11 +00:00
|
|
|
for vote_line in newlines:
|
2024-07-14 13:01:46 +00:00
|
|
|
fp_voters.write(vote_line)
|
2024-07-13 14:38:11 +00:00
|
|
|
except OSError:
|
|
|
|
print('EX: unable to write voters file2 ' +
|
|
|
|
voters_filename)
|
|
|
|
else:
|
|
|
|
return None, None
|
2023-01-12 10:45:39 +00:00
|
|
|
|
2019-11-29 18:46:21 +00:00
|
|
|
# update the vote counts
|
2021-12-31 11:23:00 +00:00
|
|
|
question_totals_changed = False
|
|
|
|
for possible_answer in question_json['object']['oneOf']:
|
|
|
|
if not possible_answer.get('name'):
|
2019-11-29 18:46:21 +00:00
|
|
|
continue
|
2021-12-31 11:23:00 +00:00
|
|
|
total_items = 0
|
2024-07-13 14:38:11 +00:00
|
|
|
lines = []
|
|
|
|
try:
|
|
|
|
with open(voters_filename, 'r', encoding='utf-8') as fp_voters:
|
|
|
|
lines = fp_voters.readlines()
|
|
|
|
except OSError:
|
|
|
|
print('EX: question_update_votes unable to read ' +
|
|
|
|
voters_filename)
|
|
|
|
for vote_line in lines:
|
|
|
|
if vote_line.endswith(voters_file_separator +
|
|
|
|
possible_answer['name'] + '\n'):
|
|
|
|
total_items += 1
|
2021-12-31 11:23:00 +00:00
|
|
|
if possible_answer['replies']['totalItems'] != total_items:
|
|
|
|
possible_answer['replies']['totalItems'] = total_items
|
|
|
|
question_totals_changed = True
|
|
|
|
if not question_totals_changed:
|
2021-11-03 21:53:30 +00:00
|
|
|
return None, None
|
2023-01-12 10:45:39 +00:00
|
|
|
|
2019-11-29 19:22:11 +00:00
|
|
|
# save the question with altered totals
|
2021-12-31 11:23:00 +00:00
|
|
|
save_json(question_json, question_post_filename)
|
|
|
|
return question_json, question_post_filename
|
2020-11-09 15:40:24 +00:00
|
|
|
|
|
|
|
|
2023-04-06 09:34:39 +00:00
|
|
|
def is_html_question(html_str: str) -> bool:
|
|
|
|
""" is the given html string a Question?
|
|
|
|
"""
|
|
|
|
if 'input type="radio" name="answer"' in html_str:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2023-01-12 10:45:39 +00:00
|
|
|
def is_question(post_json_object: {}) -> bool:
|
2020-11-09 15:40:24 +00:00
|
|
|
""" is the given post a question?
|
|
|
|
"""
|
2023-01-12 10:45:39 +00:00
|
|
|
if post_json_object['type'] != 'Create' and \
|
|
|
|
post_json_object['type'] != 'Update':
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
2023-01-12 10:45:39 +00:00
|
|
|
if not has_object_dict(post_json_object):
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
2023-01-12 10:45:39 +00:00
|
|
|
if not post_json_object['object'].get('type'):
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
2023-01-12 10:45:39 +00:00
|
|
|
if post_json_object['object']['type'] != 'Question':
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
2023-01-12 10:45:39 +00:00
|
|
|
if not post_json_object['object'].get('oneOf'):
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
2023-01-12 10:45:39 +00:00
|
|
|
if not isinstance(post_json_object['object']['oneOf'], list):
|
2020-11-09 15:40:24 +00:00
|
|
|
return False
|
|
|
|
return True
|
2023-02-28 18:01:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
def dangerous_question(question_json: {},
|
|
|
|
allow_local_network_access: bool) -> bool:
|
|
|
|
"""does the given question contain dangerous markup?
|
|
|
|
"""
|
|
|
|
if question_json.get('oneOf'):
|
|
|
|
question_options = question_json['oneOf']
|
|
|
|
else:
|
|
|
|
question_options = question_json['object']['oneOf']
|
|
|
|
for option in question_options:
|
|
|
|
if option.get('name'):
|
2023-05-18 11:15:18 +00:00
|
|
|
if dangerous_markup(option['name'],
|
|
|
|
allow_local_network_access, []):
|
2023-02-28 18:01:24 +00:00
|
|
|
return True
|
|
|
|
return False
|