epicyon/daemon_post_newswire.py

475 lines
18 KiB
Python

__filename__ = "daemon_post_newswire.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.5.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core POST"
import os
import errno
from socket import error as SocketError
from utils import clear_from_post_caches
from utils import remove_id_ending
from utils import save_json
from utils import first_paragraph_from_string
from utils import date_from_string_format
from utils import load_json
from utils import locate_post
from utils import is_editor
from utils import acct_dir
from utils import get_instance_url
from utils import get_nickname_from_actor
from httpheaders import redirect_headers
from posts import is_moderator
from content import extract_text_fields_in_post
from content import load_dogwhistles
def newswire_update(self, calling_domain: str, cookie: str,
path: str, base_dir: str,
domain: str, debug: bool,
default_timeline: str,
http_prefix: str, domain_full: str,
onion_domain: str, i2p_domain: str,
max_post_length: int) -> None:
"""Updates the right newswire column of the timeline
"""
users_path = path.replace('/newswiredata', '')
users_path = users_path.replace('/editnewswire', '')
actor_str = \
get_instance_url(calling_domain,
http_prefix, domain_full,
onion_domain, i2p_domain) + \
users_path
boundary = None
if ' boundary=' in self.headers['Content-type']:
boundary = self.headers['Content-type'].split('boundary=')[1]
if ';' in boundary:
boundary = boundary.split(';')[0]
# get the nickname
nickname = get_nickname_from_actor(actor_str)
moderator = None
if nickname:
moderator = is_moderator(base_dir, nickname)
if not nickname or not moderator:
if not nickname:
print('WARN: nickname not found in ' + actor_str)
else:
print('WARN: nickname is not a moderator' + actor_str)
redirect_headers(self, actor_str, cookie, calling_domain, 303)
self.server.postreq_busy = False
return
if self.headers.get('Content-length'):
length = int(self.headers['Content-length'])
# check that the POST isn't too large
if length > max_post_length:
print('Maximum newswire data length exceeded ' + str(length))
redirect_headers(self, actor_str, cookie, calling_domain, 303)
self.server.postreq_busy = False
return
try:
# read the bytes of the http form POST
post_bytes = self.rfile.read(length)
except SocketError as ex:
if ex.errno == errno.ECONNRESET:
print('EX: connection was reset while ' +
'reading bytes from http form POST')
else:
print('EX: error while reading bytes ' +
'from http form POST')
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
except ValueError as ex:
print('EX: failed to read bytes for POST, ' + str(ex))
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
newswire_filename = base_dir + '/accounts/newswire.txt'
if not boundary:
if b'--LYNX' in post_bytes:
boundary = '--LYNX'
if boundary:
# extract all of the text fields into a dict
fields = \
extract_text_fields_in_post(post_bytes, boundary, debug, None)
if fields.get('editedNewswire'):
newswire_str = fields['editedNewswire']
# append a new newswire entry
if fields.get('newNewswireFeed'):
if newswire_str:
if not newswire_str.endswith('\n'):
newswire_str += '\n'
newswire_str += fields['newNewswireFeed'] + '\n'
try:
with open(newswire_filename, 'w+',
encoding='utf-8') as newsfile:
newsfile.write(newswire_str)
except OSError:
print('EX: unable to write ' + newswire_filename)
else:
if fields.get('newNewswireFeed'):
# the text area is empty but there is a new feed added
newswire_str = fields['newNewswireFeed'] + '\n'
try:
with open(newswire_filename, 'w+',
encoding='utf-8') as newsfile:
newsfile.write(newswire_str)
except OSError:
print('EX: unable to write ' + newswire_filename)
else:
# text area has been cleared and there is no new feed
if os.path.isfile(newswire_filename):
try:
os.remove(newswire_filename)
except OSError:
print('EX: _newswire_update unable to delete ' +
newswire_filename)
# save filtered words list for the newswire
filter_newswire_filename = \
base_dir + '/accounts/' + \
'news@' + domain + '/filters.txt'
if fields.get('filteredWordsNewswire'):
try:
with open(filter_newswire_filename, 'w+',
encoding='utf-8') as filterfile:
filterfile.write(fields['filteredWordsNewswire'])
except OSError:
print('EX: unable to write ' + filter_newswire_filename)
else:
if os.path.isfile(filter_newswire_filename):
try:
os.remove(filter_newswire_filename)
except OSError:
print('EX: _newswire_update unable to delete ' +
filter_newswire_filename)
# save dogwhistle words list
dogwhistles_filename = base_dir + '/accounts/dogwhistles.txt'
if fields.get('dogwhistleWords'):
try:
with open(dogwhistles_filename, 'w+',
encoding='utf-8') as fp_dogwhistles:
fp_dogwhistles.write(fields['dogwhistleWords'])
except OSError:
print('EX: unable to write ' + dogwhistles_filename)
self.server.dogwhistles = \
load_dogwhistles(dogwhistles_filename)
else:
# save an empty file
try:
with open(dogwhistles_filename, 'w+',
encoding='utf-8') as fp_dogwhistles:
fp_dogwhistles.write('')
except OSError:
print('EX: unable to write ' + dogwhistles_filename)
self.server.dogwhistles = {}
# save news tagging rules
hashtag_rules_filename = \
base_dir + '/accounts/hashtagrules.txt'
if fields.get('hashtagRulesList'):
try:
with open(hashtag_rules_filename, 'w+',
encoding='utf-8') as rulesfile:
rulesfile.write(fields['hashtagRulesList'])
except OSError:
print('EX: unable to write ' + hashtag_rules_filename)
else:
if os.path.isfile(hashtag_rules_filename):
try:
os.remove(hashtag_rules_filename)
except OSError:
print('EX: _newswire_update unable to delete ' +
hashtag_rules_filename)
newswire_tusted_filename = \
base_dir + '/accounts/newswiretrusted.txt'
if fields.get('trustedNewswire'):
newswire_trusted = fields['trustedNewswire']
if not newswire_trusted.endswith('\n'):
newswire_trusted += '\n'
try:
with open(newswire_tusted_filename, 'w+',
encoding='utf-8') as trustfile:
trustfile.write(newswire_trusted)
except OSError:
print('EX: unable to write ' + newswire_tusted_filename)
else:
if os.path.isfile(newswire_tusted_filename):
try:
os.remove(newswire_tusted_filename)
except OSError:
print('EX: _newswire_update unable to delete ' +
newswire_tusted_filename)
# redirect back to the default timeline
redirect_headers(self, actor_str + '/' + default_timeline,
cookie, calling_domain, 303)
self.server.postreq_busy = False
def citations_update(self, calling_domain: str, cookie: str,
path: str, base_dir: str,
domain: str, debug: bool,
newswire: {},
http_prefix: str, domain_full: str,
onion_domain: str, i2p_domain: str,
max_post_length: int) -> None:
"""Updates the citations for a blog post after hitting
update button on the citations screen
"""
users_path = path.replace('/citationsdata', '')
actor_str = \
get_instance_url(calling_domain,
http_prefix, domain_full,
onion_domain, i2p_domain) + \
users_path
nickname = get_nickname_from_actor(actor_str)
if not nickname:
self.server.postreq_busy = False
return
citations_filename = \
acct_dir(base_dir, nickname, domain) + '/.citations.txt'
# remove any existing citations file
if os.path.isfile(citations_filename):
try:
os.remove(citations_filename)
except OSError:
print('EX: _citations_update unable to delete ' +
citations_filename)
if newswire and \
' boundary=' in self.headers['Content-type']:
boundary = self.headers['Content-type'].split('boundary=')[1]
if ';' in boundary:
boundary = boundary.split(';')[0]
length = int(self.headers['Content-length'])
# check that the POST isn't too large
if length > max_post_length:
print('Maximum citations data length exceeded ' + str(length))
redirect_headers(self, actor_str, cookie, calling_domain, 303)
self.server.postreq_busy = False
return
try:
# read the bytes of the http form POST
post_bytes = self.rfile.read(length)
except SocketError as ex:
if ex.errno == errno.ECONNRESET:
print('EX: connection was reset while ' +
'reading bytes from http form ' +
'citation screen POST')
else:
print('EX: error while reading bytes ' +
'from http form citations screen POST')
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
except ValueError as ex:
print('EX: failed to read bytes for ' +
'citations screen POST, ' + str(ex))
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
# extract all of the text fields into a dict
fields = \
extract_text_fields_in_post(post_bytes, boundary, debug, None)
print('citationstest: ' + str(fields))
citations = []
for ctr in range(0, 128):
field_name = 'newswire' + str(ctr)
if not fields.get(field_name):
continue
citations.append(fields[field_name])
if citations:
citations_str = ''
for citation_date in citations:
citations_str += citation_date + '\n'
# save citations dates, so that they can be added when
# reloading the newblog screen
try:
with open(citations_filename, 'w+',
encoding='utf-8') as citfile:
citfile.write(citations_str)
except OSError:
print('EX: unable to write ' + citations_filename)
# redirect back to the default timeline
redirect_headers(self, actor_str + '/newblog',
cookie, calling_domain, 303)
self.server.postreq_busy = False
def news_post_edit(self, calling_domain: str, cookie: str,
path: str, base_dir: str,
domain: str, debug: bool,
http_prefix: str, domain_full: str,
onion_domain: str, i2p_domain: str,
news_instance: bool,
max_post_length: int,
system_language: str,
recent_posts_cache: {},
newswire: {}) -> None:
"""edits a news post after receiving POST
"""
users_path = path.replace('/newseditdata', '')
users_path = users_path.replace('/editnewspost', '')
actor_str = \
get_instance_url(calling_domain,
http_prefix, domain_full,
onion_domain, i2p_domain) + \
users_path
boundary = None
if ' boundary=' in self.headers['Content-type']:
boundary = self.headers['Content-type'].split('boundary=')[1]
if ';' in boundary:
boundary = boundary.split(';')[0]
# get the nickname
nickname = get_nickname_from_actor(actor_str)
editor_role = None
if nickname:
editor_role = is_editor(base_dir, nickname)
if not nickname or not editor_role:
if not nickname:
print('WARN: nickname not found in ' + actor_str)
else:
print('WARN: nickname is not an editor' + actor_str)
if news_instance:
redirect_headers(self, actor_str + '/tlfeatures',
cookie, calling_domain, 303)
else:
redirect_headers(self, actor_str + '/tlnews',
cookie, calling_domain, 303)
self.server.postreq_busy = False
return
if self.headers.get('Content-length'):
length = int(self.headers['Content-length'])
# check that the POST isn't too large
if length > max_post_length:
print('Maximum news data length exceeded ' + str(length))
if news_instance:
redirect_headers(self, actor_str + '/tlfeatures',
cookie, calling_domain, 303)
else:
redirect_headers(self, actor_str + '/tlnews',
cookie, calling_domain, 303)
self.server.postreq_busy = False
return
try:
# read the bytes of the http form POST
post_bytes = self.rfile.read(length)
except SocketError as ex:
if ex.errno == errno.ECONNRESET:
print('EX: connection was reset while ' +
'reading bytes from http form POST')
else:
print('EX: error while reading bytes ' +
'from http form POST')
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
except ValueError as ex:
print('EX: failed to read bytes for POST, ' + str(ex))
self.send_response(400)
self.end_headers()
self.server.postreq_busy = False
return
if not boundary:
if b'--LYNX' in post_bytes:
boundary = '--LYNX'
if boundary:
# extract all of the text fields into a dict
fields = \
extract_text_fields_in_post(post_bytes, boundary, debug, None)
news_post_url = None
news_post_title = None
news_post_content = None
if fields.get('newsPostUrl'):
news_post_url = fields['newsPostUrl']
if fields.get('newsPostTitle'):
news_post_title = fields['newsPostTitle']
if fields.get('editedNewsPost'):
news_post_content = fields['editedNewsPost']
if news_post_url and news_post_content and news_post_title:
# load the post
post_filename = \
locate_post(base_dir, nickname, domain,
news_post_url)
if post_filename:
post_json_object = load_json(post_filename)
# update the content and title
post_json_object['object']['summary'] = \
news_post_title
post_json_object['object']['content'] = \
news_post_content
content_map = post_json_object['object']['contentMap']
content_map[system_language] = \
news_post_content
# update newswire
pub_date = post_json_object['object']['published']
published_date = \
date_from_string_format(pub_date,
["%Y-%m-%dT%H:%M:%S%z"])
if newswire.get(str(published_date)):
newswire[published_date][0] = news_post_title
newswire[published_date][4] = \
first_paragraph_from_string(news_post_content)
# save newswire
newswire_state_filename = \
base_dir + '/accounts/.newswirestate.json'
try:
save_json(newswire, newswire_state_filename)
except BaseException as ex:
print('EX: saving newswire state, ' + str(ex))
# remove any previous cached news posts
news_id = \
remove_id_ending(post_json_object['object']['id'])
news_id = news_id.replace('/', '#')
clear_from_post_caches(base_dir, recent_posts_cache,
news_id)
# save the news post
save_json(post_json_object, post_filename)
# redirect back to the default timeline
if news_instance:
redirect_headers(self, actor_str + '/tlfeatures',
cookie, calling_domain, 303)
else:
redirect_headers(self, actor_str + '/tlnews',
cookie, calling_domain, 303)
self.server.postreq_busy = False