mirror of https://gitlab.com/bashrc2/epicyon
9316 lines
420 KiB
Python
9316 lines
420 KiB
Python
__filename__ = "daemon_post.py"
|
|
__author__ = "Bob Mottram"
|
|
__license__ = "AGPL3+"
|
|
__version__ = "1.5.0"
|
|
__maintainer__ = "Bob Mottram"
|
|
__email__ = "bob@libreserver.org"
|
|
__status__ = "Production"
|
|
__module_group__ = "Core"
|
|
|
|
import time
|
|
import copy
|
|
import errno
|
|
import json
|
|
import os
|
|
import urllib.parse
|
|
from hashlib import sha256
|
|
from socket import error as SocketError
|
|
from utils import is_float
|
|
from utils import get_base_content_from_post
|
|
from utils import is_image_file
|
|
from utils import remove_avatar_from_cache
|
|
from utils import is_memorial_account
|
|
from utils import save_reverse_timeline
|
|
from utils import set_minimize_all_images
|
|
from utils import get_occupation_name
|
|
from utils import set_occupation_name
|
|
from utils import set_account_timezone
|
|
from utils import get_account_timezone
|
|
from utils import set_memorials
|
|
from utils import get_memorials
|
|
from utils import license_link_from_name
|
|
from utils import resembles_url
|
|
from utils import set_config_param
|
|
from utils import set_reply_interval_hours
|
|
from utils import remove_html
|
|
from utils import get_url_from_post
|
|
from utils import is_artist
|
|
from utils import clear_from_post_caches
|
|
from utils import first_paragraph_from_string
|
|
from utils import date_from_string_format
|
|
from utils import dangerous_markup
|
|
from utils import binary_is_image
|
|
from utils import get_image_extension_from_mime_type
|
|
from utils import remove_post_from_cache
|
|
from utils import get_cached_post_filename
|
|
from utils import text_in_file
|
|
from utils import has_users_path
|
|
from utils import has_group_type
|
|
from utils import get_status_number
|
|
from utils import refresh_newswire
|
|
from utils import remove_eol
|
|
from utils import load_json
|
|
from utils import save_json
|
|
from utils import delete_post
|
|
from utils import locate_post
|
|
from utils import get_full_domain
|
|
from utils import get_domain_from_actor
|
|
from utils import is_editor
|
|
from utils import get_config_param
|
|
from utils import decoded_host
|
|
from utils import get_new_post_endpoints
|
|
from utils import local_actor_url
|
|
from utils import contains_invalid_chars
|
|
from utils import remove_id_ending
|
|
from utils import check_bad_path
|
|
from utils import is_system_account
|
|
from utils import valid_password
|
|
from utils import get_instance_url
|
|
from utils import is_local_network_address
|
|
from utils import is_suspended
|
|
from utils import acct_dir
|
|
from utils import get_nickname_from_actor
|
|
from blocking import save_block_federated_endpoints
|
|
from blocking import import_blocking_file
|
|
from blocking import add_account_blocks
|
|
from blocking import save_blocked_military
|
|
from blocking import set_broch_mode
|
|
from blocking import is_blocked_hashtag
|
|
from blocking import contains_military_domain
|
|
from blocking import add_global_block
|
|
from blocking import update_blocked_cache
|
|
from blocking import remove_global_block
|
|
from blocking import allowed_announce_add
|
|
from blocking import allowed_announce_remove
|
|
from blocking import remove_block
|
|
from blocking import add_block
|
|
from crawlers import blocked_user_agent
|
|
from session import site_is_verified
|
|
from session import get_session_for_domain
|
|
from session import establish_session
|
|
from fitnessFunctions import fitness_performance
|
|
from shares import add_share
|
|
from shares import merge_shared_item_tokens
|
|
from shares import add_shares_to_actor
|
|
from shares import remove_shared_item2
|
|
from shares import update_shared_item_federation_token
|
|
from inbox import update_edited_post
|
|
from inbox import populate_replies
|
|
from inbox import inbox_message_has_params
|
|
from inbox import inbox_permitted_message
|
|
from httpsig import getheader_signature_input
|
|
from webapp_login import html_get_login_credentials
|
|
from webapp_suspended import html_suspended
|
|
from person import deactivate_account
|
|
from person import get_actor_move_json
|
|
from person import add_actor_update_timestamp
|
|
from person import randomize_actor_images
|
|
from person import get_default_person_context
|
|
from person import get_featured_hashtags
|
|
from person import set_featured_hashtags
|
|
from person import update_memorial_flags
|
|
from person import get_actor_update_json
|
|
from person import activate_account
|
|
from person import register_account
|
|
from person import person_upgrade_actor
|
|
from person import person_snooze
|
|
from person import person_unsnooze
|
|
from auth import store_basic_credentials
|
|
from auth import create_basic_auth_header
|
|
from auth import authorize_basic
|
|
from auth import record_login_failure
|
|
from auth import create_password
|
|
from content import get_price_from_string
|
|
from content import replace_emoji_from_tags
|
|
from content import add_name_emojis_to_tags
|
|
from content import add_html_tags
|
|
from content import save_media_in_form_post
|
|
from content import extract_media_in_form_post
|
|
from content import load_dogwhistles
|
|
from content import extract_text_fields_in_post
|
|
from filters import is_filtered
|
|
from filters import add_global_filter
|
|
from filters import remove_global_filter
|
|
from categories import set_hashtag_category
|
|
from httpcodes import write2
|
|
from httpcodes import http_200
|
|
from httpcodes import http_401
|
|
from httpcodes import http_404
|
|
from httpcodes import http_400
|
|
from httpcodes import http_503
|
|
from httpheaders import login_headers
|
|
from httpheaders import redirect_headers
|
|
from httpheaders import clear_login_details
|
|
from httpheaders import set_headers
|
|
from daemon_utils import get_user_agent
|
|
from daemon_utils import show_person_options
|
|
from daemon_utils import post_to_outbox
|
|
from daemon_utils import update_inbox_queue
|
|
from daemon_utils import is_authorized
|
|
from posts import create_reading_post
|
|
from posts import create_question_post
|
|
from posts import create_report_post
|
|
from posts import create_followers_only_post
|
|
from posts import create_unlisted_post
|
|
from posts import create_blog_post
|
|
from posts import pin_post2
|
|
from posts import create_public_post
|
|
from posts import undo_pinned_post
|
|
from posts import get_post_expiry_keep_dms
|
|
from posts import set_post_expiry_keep_dms
|
|
from posts import set_max_profile_posts
|
|
from posts import get_max_profile_posts
|
|
from posts import set_post_expiry_days
|
|
from posts import get_post_expiry_days
|
|
from posts import is_moderator
|
|
from webapp_moderation import html_account_info
|
|
from webapp_moderation import html_moderation_info
|
|
from person import suspend_account
|
|
from person import reenable_account
|
|
from person import remove_account
|
|
from person import can_remove_post
|
|
from person import set_person_notes
|
|
from cache import store_person_in_cache
|
|
from cache import remove_person_from_cache
|
|
from cache import get_person_from_cache
|
|
from cache import clear_actor_cache
|
|
from theme import enable_grayscale
|
|
from theme import disable_grayscale
|
|
from theme import get_theme
|
|
from theme import is_news_theme_name
|
|
from theme import set_news_avatar
|
|
from theme import get_text_mode_banner
|
|
from theme import export_theme
|
|
from theme import import_theme
|
|
from theme import reset_theme_designer_settings
|
|
from theme import set_theme
|
|
from theme import set_theme_from_designer
|
|
from webapp_profile import html_profile_after_search
|
|
from webapp_search import html_hashtag_search
|
|
from petnames import set_pet_name
|
|
from followingCalendar import add_person_to_calendar
|
|
from followingCalendar import remove_person_from_calendar
|
|
from webapp_person_options import person_minimize_images
|
|
from webapp_person_options import person_undo_minimize_images
|
|
from notifyOnPost import add_notify_on_post
|
|
from notifyOnPost import remove_notify_on_post
|
|
from webapp_confirm import html_confirm_block
|
|
from webapp_confirm import html_confirm_unblock
|
|
from webapp_confirm import html_confirm_follow
|
|
from webapp_confirm import html_confirm_unfollow
|
|
from languages import set_default_post_language
|
|
from languages import set_actor_languages
|
|
from languages import get_actor_languages
|
|
from languages import get_understood_languages
|
|
from webapp_create_post import html_new_post
|
|
from follow import send_follow_request
|
|
from follow import unfollow_account
|
|
from follow import remove_follower
|
|
from follow import is_follower_of_person
|
|
from follow import is_following_actor
|
|
from daemon_utils import post_to_outbox_thread
|
|
from reading import remove_reading_event
|
|
from webapp_search import html_skills_search
|
|
from webapp_search import html_history_search
|
|
from webapp_search import html_search_emoji
|
|
from webapp_search import html_search_shared_items
|
|
from webapp_utils import get_avatar_image_url
|
|
from city import get_spoofed_city
|
|
from posts import create_direct_message_post
|
|
from happening import remove_calendar_event
|
|
from media import replace_you_tube
|
|
from media import replace_twitter
|
|
from media import attach_media
|
|
from media import convert_image_to_low_bandwidth
|
|
from media import process_meta_data
|
|
from webapp_welcome import welcome_screen_is_complete
|
|
from skills import no_of_actor_skills
|
|
from skills import actor_has_skill
|
|
from skills import actor_skill_value
|
|
from skills import set_actor_skill_level
|
|
from pgp import set_pgp_fingerprint
|
|
from pgp import get_pgp_fingerprint
|
|
from pgp import set_pgp_pub_key
|
|
from pgp import get_pgp_pub_key
|
|
from pgp import get_email_address
|
|
from pgp import set_email_address
|
|
from xmpp import get_xmpp_address
|
|
from xmpp import set_xmpp_address
|
|
from matrix import get_matrix_address
|
|
from matrix import set_matrix_address
|
|
from ssb import get_ssb_address
|
|
from ssb import set_ssb_address
|
|
from blog import get_blog_address
|
|
from webapp_utils import set_blog_address
|
|
from tox import get_tox_address
|
|
from tox import set_tox_address
|
|
from briar import get_briar_address
|
|
from briar import set_briar_address
|
|
from cwtch import get_cwtch_address
|
|
from cwtch import set_cwtch_address
|
|
from enigma import get_enigma_pub_key
|
|
from enigma import set_enigma_pub_key
|
|
from donate import get_donation_url
|
|
from donate import set_donation_url
|
|
from donate import get_website
|
|
from donate import set_website
|
|
from donate import get_gemini_link
|
|
from donate import set_gemini_link
|
|
from roles import set_roles_from_list
|
|
from schedule import remove_scheduled_posts
|
|
from cwlists import get_cw_list_variable
|
|
from webfinger import webfinger_update
|
|
from webapp_column_right import html_citations
|
|
|
|
# maximum number of posts in a hashtag feed
|
|
MAX_POSTS_IN_HASHTAG_FEED = 6
|
|
|
|
# maximum number of posts to list in outbox feed
|
|
MAX_POSTS_IN_FEED = 12
|
|
|
|
|
|
def daemon_http_post(self) -> None:
|
|
"""HTTP POST handler
|
|
"""
|
|
if self.server.starting_daemon:
|
|
return
|
|
if check_bad_path(self.path):
|
|
http_400(self)
|
|
return
|
|
|
|
proxy_type = self.server.proxy_type
|
|
postreq_start_time = time.time()
|
|
|
|
if self.server.debug:
|
|
print('DEBUG: POST to ' + self.server.base_dir +
|
|
' path: ' + self.path + ' busy: ' +
|
|
str(self.server.postreq_busy))
|
|
|
|
calling_domain = self.server.domain_full
|
|
if self.headers.get('Host'):
|
|
calling_domain = decoded_host(self.headers['Host'])
|
|
if self.server.onion_domain:
|
|
if calling_domain not in (self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.onion_domain):
|
|
print('POST domain blocked: ' + calling_domain)
|
|
http_400(self)
|
|
return
|
|
elif self.server.i2p_domain:
|
|
if calling_domain not in (self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.i2p_domain):
|
|
print('POST domain blocked: ' + calling_domain)
|
|
http_400(self)
|
|
return
|
|
else:
|
|
if calling_domain not in (self.server.domain,
|
|
self.server.domain_full):
|
|
print('POST domain blocked: ' + calling_domain)
|
|
http_400(self)
|
|
return
|
|
|
|
curr_time_postreq = int(time.time() * 1000)
|
|
if self.server.postreq_busy:
|
|
if curr_time_postreq - self.server.last_postreq < 500:
|
|
self.send_response(429)
|
|
self.end_headers()
|
|
return
|
|
self.server.postreq_busy = True
|
|
self.server.last_postreq = curr_time_postreq
|
|
|
|
ua_str = get_user_agent(self)
|
|
|
|
block, self.server.blocked_cache_last_updated = \
|
|
blocked_user_agent(calling_domain, ua_str,
|
|
self.server.news_instance,
|
|
self.server.debug,
|
|
self.server.user_agents_blocked,
|
|
self.server.blocked_cache_last_updated,
|
|
self.server.base_dir,
|
|
self.server.blocked_cache,
|
|
self.server.block_federated,
|
|
self.server.blocked_cache_update_secs,
|
|
self.server.crawlers_allowed,
|
|
self.server.known_bots,
|
|
self.path, self.server.block_military)
|
|
if block:
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if not self.headers.get('Content-type'):
|
|
print('Content-type header missing')
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
curr_session, proxy_type = \
|
|
get_session_for_domain(self.server, calling_domain)
|
|
|
|
curr_session = \
|
|
establish_session("POST", curr_session,
|
|
proxy_type, self.server)
|
|
if not curr_session:
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'create_session',
|
|
self.server.debug)
|
|
http_404(self, 152)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# returns after this point should set postreq_busy to False
|
|
|
|
# remove any trailing slashes from the path
|
|
if not self.path.endswith('confirm'):
|
|
self.path = self.path.replace('/outbox/', '/outbox')
|
|
self.path = self.path.replace('/tlblogs/', '/tlblogs')
|
|
self.path = self.path.replace('/inbox/', '/inbox')
|
|
self.path = self.path.replace('/shares/', '/shares')
|
|
self.path = self.path.replace('/wanted/', '/wanted')
|
|
self.path = self.path.replace('/sharedInbox/', '/sharedInbox')
|
|
|
|
if self.path == '/inbox':
|
|
if not self.server.enable_shared_inbox:
|
|
http_503(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
cookie = None
|
|
if self.headers.get('Cookie'):
|
|
cookie = self.headers['Cookie']
|
|
|
|
# check authorization
|
|
authorized = is_authorized(self)
|
|
if not authorized and self.server.debug:
|
|
print('POST Not authorized')
|
|
print(str(self.headers))
|
|
|
|
# if this is a POST to the outbox then check authentication
|
|
self.outbox_authenticated = False
|
|
self.post_to_nickname = None
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'start',
|
|
self.server.debug)
|
|
|
|
# POST to login screen, containing credentials
|
|
if self.path.startswith('/login'):
|
|
_post_login_screen(self, calling_domain, cookie,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.port,
|
|
ua_str, self.server.debug,
|
|
self.server.registration)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_login_screen',
|
|
self.server.debug)
|
|
|
|
if authorized and self.path.endswith('/sethashtagcategory'):
|
|
_set_hashtag_category2(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.domain,
|
|
self.server.debug,
|
|
self.server.system_language)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# update of profile/avatar from web interface,
|
|
# after selecting Edit button then Submit
|
|
if authorized and self.path.endswith('/profiledata'):
|
|
_profile_edit(self, calling_domain, cookie, self.path,
|
|
self.server.base_dir, self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain, self.server.debug,
|
|
self.server.allow_local_network_access,
|
|
self.server.system_language,
|
|
self.server.content_license_url,
|
|
curr_session,
|
|
proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if authorized and self.path.endswith('/linksdata'):
|
|
_links_update(self, calling_domain, cookie, self.path,
|
|
self.server.base_dir, self.server.debug,
|
|
self.server.default_timeline,
|
|
self.server.allow_local_network_access)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if authorized and self.path.endswith('/newswiredata'):
|
|
_newswire_update(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.domain, self.server.debug,
|
|
self.server.default_timeline)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if authorized and self.path.endswith('/citationsdata'):
|
|
_citations_update(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.domain,
|
|
self.server.debug,
|
|
self.server.newswire)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if authorized and self.path.endswith('/newseditdata'):
|
|
_news_post_edit(self, calling_domain, cookie, self.path,
|
|
self.server.base_dir,
|
|
self.server.domain,
|
|
self.server.debug)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_news_post_edit',
|
|
self.server.debug)
|
|
|
|
users_in_path = False
|
|
if '/users/' in self.path:
|
|
users_in_path = True
|
|
|
|
# moderator action buttons
|
|
if authorized and users_in_path and \
|
|
self.path.endswith('/moderationaction'):
|
|
_moderator_actions(self,
|
|
self.path, calling_domain, cookie,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.debug)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_moderator_actions',
|
|
self.server.debug)
|
|
|
|
search_for_emoji = False
|
|
if self.path.endswith('/searchhandleemoji'):
|
|
search_for_emoji = True
|
|
self.path = self.path.replace('/searchhandleemoji',
|
|
'/searchhandle')
|
|
if self.server.debug:
|
|
print('DEBUG: searching for emoji')
|
|
print('authorized: ' + str(authorized))
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'searchhandleemoji',
|
|
self.server.debug)
|
|
|
|
# a search was made
|
|
if ((authorized or search_for_emoji) and
|
|
(self.path.endswith('/searchhandle') or
|
|
'/searchhandle?page=' in self.path)):
|
|
_receive_search_query(self, calling_domain, cookie,
|
|
authorized, self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
search_for_emoji,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
postreq_start_time,
|
|
self.server.debug,
|
|
curr_session,
|
|
proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_receive_search_query',
|
|
self.server.debug)
|
|
|
|
if not authorized:
|
|
if self.path.endswith('/rmpost'):
|
|
print('ERROR: attempt to remove post was not authorized. ' +
|
|
self.path)
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
# a vote/question/poll is posted
|
|
if self.path.endswith('/question') or \
|
|
'/question?page=' in self.path or \
|
|
'/question?firstpost=' in self.path or \
|
|
'/question?lastpost=' in self.path:
|
|
_receive_vote(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
curr_session,
|
|
proxy_type,
|
|
self.server.base_dir,
|
|
self.server.city,
|
|
self.server.person_cache,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.low_bandwidth,
|
|
self.server.dm_license_url,
|
|
self.server.content_license_url,
|
|
self.server.translate,
|
|
self.server.max_replies,
|
|
self.server.project_version,
|
|
self.server.recent_posts_cache)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# removes a shared item
|
|
if self.path.endswith('/rmshare'):
|
|
_remove_share(self, calling_domain, cookie,
|
|
authorized, self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
curr_session, proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# removes a wanted item
|
|
if self.path.endswith('/rmwanted'):
|
|
_remove_wanted(self, calling_domain, cookie,
|
|
authorized, self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_remove_wanted',
|
|
self.server.debug)
|
|
|
|
# removes a post
|
|
if self.path.endswith('/rmpost'):
|
|
if '/users/' not in self.path:
|
|
print('ERROR: attempt to remove post ' +
|
|
'was not authorized. ' + self.path)
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
if self.path.endswith('/rmpost'):
|
|
_receive_remove_post(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
curr_session, proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_remove_post',
|
|
self.server.debug)
|
|
|
|
# decision to follow in the web interface is confirmed
|
|
if self.path.endswith('/followconfirm'):
|
|
_follow_confirm(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug,
|
|
curr_session,
|
|
proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_follow_confirm',
|
|
self.server.debug)
|
|
|
|
# remove a reading status from the profile screen
|
|
if self.path.endswith('/removereadingstatus'):
|
|
_remove_reading_status(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug,
|
|
self.server.books_cache)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_remove_reading_status',
|
|
self.server.debug)
|
|
|
|
# decision to unfollow in the web interface is confirmed
|
|
if self.path.endswith('/unfollowconfirm'):
|
|
_unfollow_confirm(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug,
|
|
curr_session, proxy_type)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_unfollow_confirm',
|
|
self.server.debug)
|
|
|
|
# decision to unblock in the web interface is confirmed
|
|
if self.path.endswith('/unblockconfirm'):
|
|
_unblock_confirm(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_unblock_confirm',
|
|
self.server.debug)
|
|
|
|
# decision to block in the web interface is confirmed
|
|
if self.path.endswith('/blockconfirm'):
|
|
_block_confirm(self, calling_domain, cookie,
|
|
self.path,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', '_block_confirm',
|
|
self.server.debug)
|
|
|
|
# an option was chosen from person options screen
|
|
# view/follow/block/report
|
|
if self.path.endswith('/personoptions'):
|
|
_person_options2(self, self.path,
|
|
calling_domain, cookie,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.port,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.debug,
|
|
curr_session,
|
|
authorized)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# Change the key shortcuts
|
|
if users_in_path and \
|
|
self.path.endswith('/changeAccessKeys'):
|
|
nickname = self.path.split('/users/')[1]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
|
|
if not self.server.key_shortcuts.get(nickname):
|
|
access_keys = self.server.access_keys
|
|
self.server.key_shortcuts[nickname] = access_keys.copy()
|
|
access_keys = self.server.key_shortcuts[nickname]
|
|
|
|
_key_shortcuts(self, calling_domain, cookie,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
access_keys,
|
|
self.server.default_timeline)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# theme designer submit/cancel button
|
|
if users_in_path and \
|
|
self.path.endswith('/changeThemeSettings'):
|
|
nickname = self.path.split('/users/')[1]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
|
|
if not self.server.key_shortcuts.get(nickname):
|
|
access_keys = self.server.access_keys
|
|
self.server.key_shortcuts[nickname] = access_keys.copy()
|
|
access_keys = self.server.key_shortcuts[nickname]
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
|
|
_theme_designer_edit(self, calling_domain, cookie,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.default_timeline,
|
|
self.server.theme_name,
|
|
allow_local_network_access,
|
|
self.server.system_language,
|
|
self.server.dyslexic_font)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# update the shared item federation token for the calling domain
|
|
# if it is within the permitted federation
|
|
if self.headers.get('Origin') and \
|
|
self.headers.get('SharesCatalog'):
|
|
if self.server.debug:
|
|
print('SharesCatalog header: ' + self.headers['SharesCatalog'])
|
|
if not self.server.shared_items_federated_domains:
|
|
si_domains_str = \
|
|
get_config_param(self.server.base_dir,
|
|
'sharedItemsFederatedDomains')
|
|
if si_domains_str:
|
|
if self.server.debug:
|
|
print('Loading shared items federated domains list')
|
|
si_domains_list = si_domains_str.split(',')
|
|
domains_list = self.server.shared_items_federated_domains
|
|
for si_domain in si_domains_list:
|
|
domains_list.append(si_domain.strip())
|
|
origin_domain = self.headers.get('Origin')
|
|
if origin_domain != self.server.domain_full and \
|
|
origin_domain != self.server.onion_domain and \
|
|
origin_domain != self.server.i2p_domain and \
|
|
origin_domain in self.server.shared_items_federated_domains:
|
|
if self.server.debug:
|
|
print('DEBUG: ' +
|
|
'POST updating shared item federation ' +
|
|
'token for ' + origin_domain + ' to ' +
|
|
self.server.domain_full)
|
|
shared_item_tokens = self.server.shared_item_federation_tokens
|
|
shares_token = self.headers['SharesCatalog']
|
|
self.server.shared_item_federation_tokens = \
|
|
update_shared_item_federation_token(self.server.base_dir,
|
|
origin_domain,
|
|
shares_token,
|
|
self.server.debug,
|
|
shared_item_tokens)
|
|
elif self.server.debug:
|
|
fed_domains = self.server.shared_items_federated_domains
|
|
if origin_domain not in fed_domains:
|
|
print('origin_domain is not in federated domains list ' +
|
|
origin_domain)
|
|
else:
|
|
print('origin_domain is not a different instance. ' +
|
|
origin_domain + ' ' + self.server.domain_full + ' ' +
|
|
str(fed_domains))
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'SharesCatalog',
|
|
self.server.debug)
|
|
|
|
# receive different types of post created by html_new_post
|
|
new_post_endpoints = get_new_post_endpoints()
|
|
for curr_post_type in new_post_endpoints:
|
|
if not authorized:
|
|
if self.server.debug:
|
|
print('POST was not authorized')
|
|
break
|
|
|
|
post_redirect = self.server.default_timeline
|
|
if curr_post_type == 'newshare':
|
|
post_redirect = 'tlshares'
|
|
elif curr_post_type == 'newwanted':
|
|
post_redirect = 'tlwanted'
|
|
|
|
page_number = \
|
|
_receive_new_post(self, curr_post_type, self.path,
|
|
calling_domain, cookie,
|
|
self.server.content_license_url,
|
|
curr_session, proxy_type)
|
|
if page_number:
|
|
print(curr_post_type + ' post received')
|
|
nickname = self.path.split('/users/')[1]
|
|
if '?' in nickname:
|
|
nickname = nickname.split('?')[0]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
|
|
if calling_domain.endswith('.onion') and \
|
|
self.server.onion_domain:
|
|
actor_path_str = \
|
|
local_actor_url('http', nickname,
|
|
self.server.onion_domain) + \
|
|
'/' + post_redirect + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
elif (calling_domain.endswith('.i2p') and
|
|
self.server.i2p_domain):
|
|
actor_path_str = \
|
|
local_actor_url('http', nickname,
|
|
self.server.i2p_domain) + \
|
|
'/' + post_redirect + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
else:
|
|
actor_path_str = \
|
|
local_actor_url(self.server.http_prefix, nickname,
|
|
self.server.domain_full) + \
|
|
'/' + post_redirect + '?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'receive post',
|
|
self.server.debug)
|
|
|
|
if self.path.endswith('/outbox') or \
|
|
self.path.endswith('/wanted') or \
|
|
self.path.endswith('/shares'):
|
|
if users_in_path:
|
|
if authorized:
|
|
self.outbox_authenticated = True
|
|
path_users_section = self.path.split('/users/')[1]
|
|
self.post_to_nickname = path_users_section.split('/')[0]
|
|
if not self.outbox_authenticated:
|
|
self.send_response(405)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'authorized',
|
|
self.server.debug)
|
|
|
|
# check that the post is to an expected path
|
|
if not (self.path.endswith('/outbox') or
|
|
self.path.endswith('/inbox') or
|
|
self.path.endswith('/wanted') or
|
|
self.path.endswith('/shares') or
|
|
self.path.endswith('/moderationaction') or
|
|
self.path == '/sharedInbox'):
|
|
print('Attempt to POST to invalid path ' + self.path)
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'check path',
|
|
self.server.debug)
|
|
|
|
if self.headers['Content-length'] is None or \
|
|
self.headers['Content-type'] is None:
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
is_media_content = False
|
|
if self.headers['Content-type'].startswith('image/') or \
|
|
self.headers['Content-type'].startswith('video/') or \
|
|
self.headers['Content-type'].startswith('audio/'):
|
|
is_media_content = True
|
|
|
|
# check that the content length string is not too long
|
|
if isinstance(self.headers['Content-length'], str):
|
|
if not is_media_content:
|
|
max_content_size = len(str(self.server.maxMessageLength))
|
|
else:
|
|
max_content_size = len(str(self.server.maxMediaSize))
|
|
if len(self.headers['Content-length']) > max_content_size:
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# read the message and convert it into a python dictionary
|
|
length = int(self.headers['Content-length'])
|
|
if self.server.debug:
|
|
print('DEBUG: content-length: ' + str(length))
|
|
if not is_media_content:
|
|
if length > self.server.maxMessageLength:
|
|
print('Maximum message length exceeded ' + str(length))
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
if length > self.server.maxMediaSize:
|
|
print('Maximum media size exceeded ' + str(length))
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# receive images to the outbox
|
|
if self.headers['Content-type'].startswith('image/') and \
|
|
users_in_path:
|
|
_receive_image(self, length, self.path,
|
|
self.server.base_dir,
|
|
self.server.domain,
|
|
self.server.debug)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# refuse to receive non-json content
|
|
content_type_str = self.headers['Content-type']
|
|
if not content_type_str.startswith('application/json') and \
|
|
not content_type_str.startswith('application/activity+json') and \
|
|
not content_type_str.startswith('application/ld+json'):
|
|
print("POST is not json: " + self.headers['Content-type'])
|
|
if self.server.debug:
|
|
print(str(self.headers))
|
|
length = int(self.headers['Content-length'])
|
|
if length < self.server.max_post_length:
|
|
try:
|
|
unknown_post = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST unknown_post ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST unknown_post socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST unknown_post rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
print(str(unknown_post))
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if self.server.debug:
|
|
print('DEBUG: Reading message')
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'check content type',
|
|
self.server.debug)
|
|
|
|
# check content length before reading bytes
|
|
if self.path in ('/sharedInbox', '/inbox'):
|
|
length = 0
|
|
if self.headers.get('Content-length'):
|
|
length = int(self.headers['Content-length'])
|
|
elif self.headers.get('Content-Length'):
|
|
length = int(self.headers['Content-Length'])
|
|
elif self.headers.get('content-length'):
|
|
length = int(self.headers['content-length'])
|
|
if length > 10240:
|
|
print('WARN: post to shared inbox is too long ' +
|
|
str(length) + ' bytes')
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
try:
|
|
message_bytes = self.rfile.read(length)
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('WARN: POST message_bytes ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('WARN: POST message_bytes socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST message_bytes rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# check content length after reading bytes
|
|
if self.path in ('/sharedInbox', '/inbox'):
|
|
len_message = len(message_bytes)
|
|
if len_message > 10240:
|
|
print('WARN: post to shared inbox is too long ' +
|
|
str(len_message) + ' bytes')
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
decoded_message_bytes = message_bytes.decode("utf-8")
|
|
if contains_invalid_chars(decoded_message_bytes):
|
|
http_400(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if users_in_path:
|
|
nickname = self.path.split('/users/')[1]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
if self.server.block_military.get(nickname):
|
|
if contains_military_domain(decoded_message_bytes):
|
|
http_400(self)
|
|
print('BLOCK: blocked military domain')
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# convert the raw bytes to json
|
|
try:
|
|
message_json = json.loads(message_bytes)
|
|
except json.decoder.JSONDecodeError as ex:
|
|
http_400(self)
|
|
print('EX: json decode error ' + str(ex) +
|
|
' from POST ' + str(message_bytes))
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'load json',
|
|
self.server.debug)
|
|
|
|
# https://www.w3.org/TR/activitypub/#object-without-create
|
|
if self.outbox_authenticated:
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version, None,
|
|
curr_session, proxy_type):
|
|
if message_json.get('id'):
|
|
locn_str = remove_id_ending(message_json['id'])
|
|
self.headers['Location'] = locn_str
|
|
self.send_response(201)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
if self.server.debug:
|
|
print('Failed to post to outbox')
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'post_to_outbox',
|
|
self.server.debug)
|
|
|
|
# check the necessary properties are available
|
|
if self.server.debug:
|
|
print('DEBUG: Check message has params')
|
|
|
|
if not message_json:
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if self.path.endswith('/inbox') or \
|
|
self.path == '/sharedInbox':
|
|
if not inbox_message_has_params(message_json):
|
|
if self.server.debug:
|
|
print("DEBUG: inbox message doesn't have the " +
|
|
"required parameters")
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'inbox_message_has_params',
|
|
self.server.debug)
|
|
|
|
header_signature = getheader_signature_input(self.headers)
|
|
|
|
if header_signature:
|
|
if 'keyId=' not in header_signature:
|
|
if self.server.debug:
|
|
print('DEBUG: POST to inbox has no keyId in ' +
|
|
'header signature parameter')
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'keyId check',
|
|
self.server.debug)
|
|
|
|
if not self.server.unit_test:
|
|
if not inbox_permitted_message(self.server.domain,
|
|
message_json,
|
|
self.server.federation_list):
|
|
if self.server.debug:
|
|
# https://www.youtube.com/watch?v=K3PrSj9XEu4
|
|
print('DEBUG: Ah Ah Ah')
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fitness_performance(postreq_start_time, self.server.fitness,
|
|
'_POST', 'inbox_permitted_message',
|
|
self.server.debug)
|
|
|
|
if self.server.debug:
|
|
print('INBOX: POST saving to inbox queue')
|
|
if users_in_path:
|
|
path_users_section = self.path.split('/users/')[1]
|
|
if '/' not in path_users_section:
|
|
if self.server.debug:
|
|
print('INBOX: This is not a users endpoint')
|
|
else:
|
|
self.post_to_nickname = path_users_section.split('/')[0]
|
|
if self.post_to_nickname:
|
|
queue_status = \
|
|
update_inbox_queue(self, self.post_to_nickname,
|
|
message_json, message_bytes,
|
|
self.server.debug)
|
|
if queue_status in range(0, 4):
|
|
self.server.postreq_busy = False
|
|
return
|
|
if self.server.debug:
|
|
print('INBOX: update_inbox_queue exited ' +
|
|
'without doing anything')
|
|
else:
|
|
if self.server.debug:
|
|
print('INBOX: self.post_to_nickname is None')
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if self.path in ('/sharedInbox', '/inbox'):
|
|
if self.server.debug:
|
|
print('INBOX: POST to shared inbox')
|
|
queue_status = \
|
|
update_inbox_queue(self, 'inbox', message_json,
|
|
message_bytes,
|
|
self.server.debug)
|
|
if queue_status in range(0, 4):
|
|
self.server.postreq_busy = False
|
|
return
|
|
http_200(self)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _moderator_actions(self, path: str, calling_domain: str, cookie: str,
|
|
base_dir: str, http_prefix: str,
|
|
domain: str, port: int, debug: bool) -> None:
|
|
"""Actions on the moderator screen
|
|
"""
|
|
users_path = path.replace('/moderationaction', '')
|
|
nickname = users_path.replace('/users/', '')
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
users_path
|
|
if not is_moderator(self.server.base_dir, nickname):
|
|
redirect_headers(self, actor_str + '/moderation',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
moderation_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST moderation_params connection was reset')
|
|
else:
|
|
print('EX: POST moderation_params ' +
|
|
'rfile.read socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST moderation_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&' in moderation_params:
|
|
moderation_text = None
|
|
moderation_button = None
|
|
# get the moderation text first
|
|
act_str = 'moderationAction='
|
|
for moderation_str in moderation_params.split('&'):
|
|
if moderation_str.startswith(act_str):
|
|
if act_str in moderation_str:
|
|
moderation_text = \
|
|
moderation_str.split(act_str)[1].strip()
|
|
mod_text = moderation_text.replace('+', ' ')
|
|
moderation_text = \
|
|
urllib.parse.unquote_plus(mod_text.strip())
|
|
# which button was pressed?
|
|
for moderation_str in moderation_params.split('&'):
|
|
if moderation_str.startswith('submitInfo='):
|
|
if not moderation_text and \
|
|
'submitInfo=' in moderation_str:
|
|
moderation_text = \
|
|
moderation_str.split('submitInfo=')[1].strip()
|
|
mod_text = moderation_text.replace('+', ' ')
|
|
moderation_text = \
|
|
urllib.parse.unquote_plus(mod_text.strip())
|
|
search_handle = moderation_text
|
|
if search_handle:
|
|
if '/@' in search_handle and \
|
|
'/@/' not in search_handle:
|
|
search_nickname = \
|
|
get_nickname_from_actor(search_handle)
|
|
if search_nickname:
|
|
search_domain, _ = \
|
|
get_domain_from_actor(search_handle)
|
|
if search_domain:
|
|
search_handle = \
|
|
search_nickname + '@' + search_domain
|
|
else:
|
|
search_handle = ''
|
|
else:
|
|
search_handle = ''
|
|
if '@' not in search_handle or \
|
|
'/@/' in search_handle:
|
|
if search_handle.startswith('http') or \
|
|
search_handle.startswith('ipfs') or \
|
|
search_handle.startswith('ipns'):
|
|
search_nickname = \
|
|
get_nickname_from_actor(search_handle)
|
|
if search_nickname:
|
|
search_domain, _ = \
|
|
get_domain_from_actor(search_handle)
|
|
if search_domain:
|
|
search_handle = \
|
|
search_nickname + '@' + \
|
|
search_domain
|
|
else:
|
|
search_handle = ''
|
|
else:
|
|
search_handle = ''
|
|
if '@' not in search_handle:
|
|
# is this a local nickname on this instance?
|
|
local_handle = \
|
|
search_handle + '@' + self.server.domain
|
|
if os.path.isdir(self.server.base_dir +
|
|
'/accounts/' + local_handle):
|
|
search_handle = local_handle
|
|
else:
|
|
search_handle = ''
|
|
if search_handle is None:
|
|
search_handle = ''
|
|
if '@' in search_handle:
|
|
msg = \
|
|
html_account_info(self.server.translate,
|
|
base_dir, http_prefix,
|
|
nickname,
|
|
self.server.domain,
|
|
search_handle,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.signing_priv_key_pem,
|
|
None,
|
|
self.server.block_federated)
|
|
else:
|
|
msg = \
|
|
html_moderation_info(self.server.translate,
|
|
base_dir, nickname,
|
|
self.server.domain,
|
|
self.server.theme_name,
|
|
self.server.access_keys)
|
|
if msg:
|
|
msg = msg.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
if moderation_str.startswith('submitBlock'):
|
|
moderation_button = 'block'
|
|
elif moderation_str.startswith('submitUnblock'):
|
|
moderation_button = 'unblock'
|
|
elif moderation_str.startswith('submitFilter'):
|
|
moderation_button = 'filter'
|
|
elif moderation_str.startswith('submitUnfilter'):
|
|
moderation_button = 'unfilter'
|
|
elif moderation_str.startswith('submitClearCache'):
|
|
moderation_button = 'clearcache'
|
|
elif moderation_str.startswith('submitSuspend'):
|
|
moderation_button = 'suspend'
|
|
elif moderation_str.startswith('submitUnsuspend'):
|
|
moderation_button = 'unsuspend'
|
|
elif moderation_str.startswith('submitRemove'):
|
|
moderation_button = 'remove'
|
|
if moderation_button and moderation_text:
|
|
if debug:
|
|
print('moderation_button: ' + moderation_button)
|
|
print('moderation_text: ' + moderation_text)
|
|
nickname = moderation_text
|
|
if nickname.startswith('http') or \
|
|
nickname.startswith('ipfs') or \
|
|
nickname.startswith('ipns') or \
|
|
nickname.startswith('hyper'):
|
|
nickname = get_nickname_from_actor(nickname)
|
|
if '@' in nickname:
|
|
nickname = nickname.split('@')[0]
|
|
if moderation_button == 'suspend':
|
|
suspend_account(base_dir, nickname, domain)
|
|
if moderation_button == 'unsuspend':
|
|
reenable_account(base_dir, nickname)
|
|
if moderation_button == 'filter':
|
|
add_global_filter(base_dir, moderation_text)
|
|
if moderation_button == 'unfilter':
|
|
remove_global_filter(base_dir, moderation_text)
|
|
if moderation_button == 'clearcache':
|
|
clear_actor_cache(base_dir,
|
|
self.server.person_cache,
|
|
moderation_text)
|
|
if moderation_button == 'block':
|
|
full_block_domain = None
|
|
moderation_text = moderation_text.strip()
|
|
moderation_reason = None
|
|
if ' ' in moderation_text:
|
|
moderation_domain = moderation_text.split(' ', 1)[0]
|
|
moderation_reason = moderation_text.split(' ', 1)[1]
|
|
else:
|
|
moderation_domain = moderation_text
|
|
if moderation_domain.startswith('http') or \
|
|
moderation_domain.startswith('ipfs') or \
|
|
moderation_domain.startswith('ipns') or \
|
|
moderation_domain.startswith('hyper'):
|
|
# https://domain
|
|
block_domain, block_port = \
|
|
get_domain_from_actor(moderation_domain)
|
|
if block_domain:
|
|
full_block_domain = \
|
|
get_full_domain(block_domain, block_port)
|
|
if '@' in moderation_domain:
|
|
# nick@domain or *@domain
|
|
full_block_domain = \
|
|
moderation_domain.split('@')[1]
|
|
else:
|
|
# assume the text is a domain name
|
|
if not full_block_domain and '.' in moderation_domain:
|
|
nickname = '*'
|
|
full_block_domain = \
|
|
moderation_domain.strip()
|
|
if full_block_domain or nickname.startswith('#'):
|
|
if nickname.startswith('#') and ' ' in nickname:
|
|
nickname = nickname.split(' ')[0]
|
|
add_global_block(base_dir, nickname,
|
|
full_block_domain, moderation_reason)
|
|
blocked_cache_last_updated = \
|
|
self.server.blocked_cache_last_updated
|
|
self.server.blocked_cache_last_updated = \
|
|
update_blocked_cache(self.server.base_dir,
|
|
self.server.blocked_cache,
|
|
blocked_cache_last_updated, 0)
|
|
if moderation_button == 'unblock':
|
|
full_block_domain = None
|
|
if ' ' in moderation_text:
|
|
moderation_domain = moderation_text.split(' ', 1)[0]
|
|
else:
|
|
moderation_domain = moderation_text
|
|
if moderation_domain.startswith('http') or \
|
|
moderation_domain.startswith('ipfs') or \
|
|
moderation_domain.startswith('ipns') or \
|
|
moderation_domain.startswith('hyper'):
|
|
# https://domain
|
|
block_domain, block_port = \
|
|
get_domain_from_actor(moderation_domain)
|
|
if block_domain:
|
|
full_block_domain = \
|
|
get_full_domain(block_domain, block_port)
|
|
if '@' in moderation_domain:
|
|
# nick@domain or *@domain
|
|
full_block_domain = moderation_domain.split('@')[1]
|
|
else:
|
|
# assume the text is a domain name
|
|
if not full_block_domain and '.' in moderation_domain:
|
|
nickname = '*'
|
|
full_block_domain = moderation_domain.strip()
|
|
if full_block_domain or nickname.startswith('#'):
|
|
if nickname.startswith('#') and ' ' in nickname:
|
|
nickname = nickname.split(' ')[0]
|
|
remove_global_block(base_dir, nickname,
|
|
full_block_domain)
|
|
blocked_cache_last_updated = \
|
|
self.server.blocked_cache_last_updated
|
|
self.server.blocked_cache_last_updated = \
|
|
update_blocked_cache(self.server.base_dir,
|
|
self.server.blocked_cache,
|
|
blocked_cache_last_updated, 0)
|
|
if moderation_button == 'remove':
|
|
if '/statuses/' not in moderation_text:
|
|
remove_account(base_dir, nickname, domain, port)
|
|
else:
|
|
# remove a post or thread
|
|
post_filename = \
|
|
locate_post(base_dir, nickname, domain,
|
|
moderation_text)
|
|
if post_filename:
|
|
if can_remove_post(base_dir, domain, port,
|
|
moderation_text):
|
|
delete_post(base_dir,
|
|
http_prefix,
|
|
nickname, domain,
|
|
post_filename,
|
|
debug,
|
|
self.server.recent_posts_cache,
|
|
True)
|
|
if nickname != 'news':
|
|
# if this is a local blog post then also remove it
|
|
# from the news actor
|
|
post_filename = \
|
|
locate_post(base_dir, 'news', domain,
|
|
moderation_text)
|
|
if post_filename:
|
|
if can_remove_post(base_dir, domain, port,
|
|
moderation_text):
|
|
delete_post(base_dir,
|
|
http_prefix,
|
|
'news', domain,
|
|
post_filename,
|
|
debug,
|
|
self.server.recent_posts_cache,
|
|
True)
|
|
|
|
redirect_headers(self, actor_str + '/moderation',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
|
|
def _key_shortcuts(self, calling_domain: str, cookie: str,
|
|
base_dir: str, http_prefix: str, nickname: str,
|
|
domain: str, domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
access_keys: {}, default_timeline: str) -> None:
|
|
"""Receive POST from webapp_accesskeys
|
|
"""
|
|
users_path = '/users/' + nickname
|
|
origin_path_str = \
|
|
http_prefix + '://' + domain_full + users_path + '/' + \
|
|
default_timeline
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
access_keys_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST access_keys_params ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST access_keys_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST access_keys_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
access_keys_params = \
|
|
urllib.parse.unquote_plus(access_keys_params)
|
|
|
|
# key shortcuts screen, back button
|
|
# See html_access_keys
|
|
if 'submitAccessKeysCancel=' in access_keys_params or \
|
|
'submitAccessKeys=' not in access_keys_params:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = \
|
|
'http://' + onion_domain + users_path + '/' + \
|
|
default_timeline
|
|
elif calling_domain.endswith('.i2p') and i2p_domain:
|
|
origin_path_str = \
|
|
'http://' + i2p_domain + users_path + \
|
|
'/' + default_timeline
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
save_keys = False
|
|
access_keys_template = self.server.access_keys
|
|
for variable_name, _ in access_keys_template.items():
|
|
if not access_keys.get(variable_name):
|
|
access_keys[variable_name] = \
|
|
access_keys_template[variable_name]
|
|
|
|
variable_name2 = variable_name.replace(' ', '_')
|
|
if variable_name2 + '=' in access_keys_params:
|
|
new_key = access_keys_params.split(variable_name2 + '=')[1]
|
|
if '&' in new_key:
|
|
new_key = new_key.split('&')[0]
|
|
if new_key:
|
|
if len(new_key) > 1:
|
|
new_key = new_key[0]
|
|
if new_key != access_keys[variable_name]:
|
|
access_keys[variable_name] = new_key
|
|
save_keys = True
|
|
|
|
if save_keys:
|
|
access_keys_filename = \
|
|
acct_dir(base_dir, nickname, domain) + '/access_keys.json'
|
|
save_json(access_keys, access_keys_filename)
|
|
if not self.server.key_shortcuts.get(nickname):
|
|
self.server.key_shortcuts[nickname] = access_keys.copy()
|
|
|
|
# redirect back from key shortcuts screen
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = \
|
|
'http://' + onion_domain + users_path + '/' + default_timeline
|
|
elif calling_domain.endswith('.i2p') and i2p_domain:
|
|
origin_path_str = \
|
|
'http://' + i2p_domain + users_path + '/' + default_timeline
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
|
|
def _theme_designer_edit(self, calling_domain: str, cookie: str,
|
|
base_dir: str, http_prefix: str, nickname: str,
|
|
domain: str, domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
default_timeline: str, theme_name: str,
|
|
allow_local_network_access: bool,
|
|
system_language: str,
|
|
dyslexic_font: bool) -> None:
|
|
"""Receive POST from webapp_theme_designer
|
|
"""
|
|
users_path = '/users/' + nickname
|
|
origin_path_str = \
|
|
http_prefix + '://' + domain_full + users_path + '/' + \
|
|
default_timeline
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
theme_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST theme_params ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST theme_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST theme_params rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
theme_params = \
|
|
urllib.parse.unquote_plus(theme_params)
|
|
|
|
# theme designer screen, reset button
|
|
# See html_theme_designer
|
|
if 'submitThemeDesignerReset=' in theme_params or \
|
|
'submitThemeDesigner=' not in theme_params:
|
|
if 'submitThemeDesignerReset=' in theme_params:
|
|
reset_theme_designer_settings(base_dir)
|
|
self.server.css_cache = {}
|
|
set_theme(base_dir, theme_name, domain,
|
|
allow_local_network_access, system_language,
|
|
dyslexic_font, True)
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = \
|
|
'http://' + onion_domain + users_path + '/' + \
|
|
default_timeline
|
|
elif calling_domain.endswith('.i2p') and i2p_domain:
|
|
origin_path_str = \
|
|
'http://' + i2p_domain + users_path + \
|
|
'/' + default_timeline
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
fields = {}
|
|
fields_list = theme_params.split('&')
|
|
for field_str in fields_list:
|
|
if '=' not in field_str:
|
|
continue
|
|
field_value = field_str.split('=')[1].strip()
|
|
if not field_value:
|
|
continue
|
|
if field_value == 'on':
|
|
field_value = 'True'
|
|
fields_index = field_str.split('=')[0]
|
|
fields[fields_index] = field_value
|
|
|
|
# Check for boolean values which are False.
|
|
# These don't come through via theme_params,
|
|
# so need to be checked separately
|
|
theme_filename = base_dir + '/theme/' + theme_name + '/theme.json'
|
|
theme_json = load_json(theme_filename)
|
|
if theme_json:
|
|
for variable_name, value in theme_json.items():
|
|
variable_name = 'themeSetting_' + variable_name
|
|
if value.lower() == 'false' or value.lower() == 'true':
|
|
if variable_name not in fields:
|
|
fields[variable_name] = 'False'
|
|
|
|
# get the parameters from the theme designer screen
|
|
theme_designer_params = {}
|
|
for variable_name, key in fields.items():
|
|
if variable_name.startswith('themeSetting_'):
|
|
variable_name = variable_name.replace('themeSetting_', '')
|
|
theme_designer_params[variable_name] = key
|
|
|
|
self.server.css_cache = {}
|
|
set_theme_from_designer(base_dir, theme_name, domain,
|
|
theme_designer_params,
|
|
allow_local_network_access,
|
|
system_language, dyslexic_font)
|
|
|
|
# set boolean values
|
|
if 'rss-icon-at-top' in theme_designer_params:
|
|
if theme_designer_params['rss-icon-at-top'].lower() == 'true':
|
|
self.server.rss_icon_at_top = True
|
|
else:
|
|
self.server.rss_icon_at_top = False
|
|
if 'publish-button-at-top' in theme_designer_params:
|
|
publish_button_at_top_str = \
|
|
theme_designer_params['publish-button-at-top'].lower()
|
|
if publish_button_at_top_str == 'true':
|
|
self.server.publish_button_at_top = True
|
|
else:
|
|
self.server.publish_button_at_top = False
|
|
if 'newswire-publish-icon' in theme_designer_params:
|
|
newswire_publish_icon_str = \
|
|
theme_designer_params['newswire-publish-icon'].lower()
|
|
if newswire_publish_icon_str == 'true':
|
|
self.server.show_publish_as_icon = True
|
|
else:
|
|
self.server.show_publish_as_icon = False
|
|
if 'icons-as-buttons' in theme_designer_params:
|
|
if theme_designer_params['icons-as-buttons'].lower() == 'true':
|
|
self.server.icons_as_buttons = True
|
|
else:
|
|
self.server.icons_as_buttons = False
|
|
if 'full-width-timeline-buttons' in theme_designer_params:
|
|
theme_value = theme_designer_params['full-width-timeline-buttons']
|
|
if theme_value.lower() == 'true':
|
|
self.server.full_width_tl_button_header = True
|
|
else:
|
|
self.server.full_width_tl_button_header = False
|
|
|
|
# redirect back from theme designer screen
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = \
|
|
'http://' + onion_domain + users_path + '/' + default_timeline
|
|
elif calling_domain.endswith('.i2p') and i2p_domain:
|
|
origin_path_str = \
|
|
'http://' + i2p_domain + users_path + '/' + default_timeline
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
|
|
def _person_options2(self, path: str,
|
|
calling_domain: str, cookie: str,
|
|
base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool, curr_session,
|
|
authorized: bool) -> None:
|
|
"""Receive POST from person options screen
|
|
"""
|
|
page_number = 1
|
|
users_path = path.split('/personoptions')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
|
|
chooser_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not chooser_nickname:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname in ' + origin_path_str)
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
options_confirm_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST options_confirm_params ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST options_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: ' +
|
|
'POST options_confirm_params rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
options_confirm_params = \
|
|
urllib.parse.unquote_plus(options_confirm_params)
|
|
|
|
# page number to return to
|
|
if 'pageNumber=' in options_confirm_params:
|
|
page_number_str = options_confirm_params.split('pageNumber=')[1]
|
|
if '&' in page_number_str:
|
|
page_number_str = page_number_str.split('&')[0]
|
|
if len(page_number_str) < 5:
|
|
if page_number_str.isdigit():
|
|
page_number = int(page_number_str)
|
|
|
|
# actor for the person
|
|
options_actor = options_confirm_params.split('actor=')[1]
|
|
if '&' in options_actor:
|
|
options_actor = options_actor.split('&')[0]
|
|
|
|
# actor for the movedTo
|
|
options_actor_moved = None
|
|
if 'movedToActor=' in options_confirm_params:
|
|
options_actor_moved = \
|
|
options_confirm_params.split('movedToActor=')[1]
|
|
if '&' in options_actor_moved:
|
|
options_actor_moved = options_actor_moved.split('&')[0]
|
|
|
|
# url of the avatar
|
|
options_avatar_url = options_confirm_params.split('avatarUrl=')[1]
|
|
if '&' in options_avatar_url:
|
|
options_avatar_url = options_avatar_url.split('&')[0]
|
|
|
|
# link to a post, which can then be included in reports
|
|
post_url = None
|
|
if 'postUrl' in options_confirm_params:
|
|
post_url = options_confirm_params.split('postUrl=')[1]
|
|
if '&' in post_url:
|
|
post_url = post_url.split('&')[0]
|
|
|
|
# petname for this person
|
|
petname = None
|
|
if 'optionpetname' in options_confirm_params:
|
|
petname = options_confirm_params.split('optionpetname=')[1]
|
|
if '&' in petname:
|
|
petname = petname.split('&')[0]
|
|
# Limit the length of the petname
|
|
if len(petname) > 20 or \
|
|
' ' in petname or '/' in petname or \
|
|
'?' in petname or '#' in petname:
|
|
petname = None
|
|
|
|
# notes about this person
|
|
person_notes = None
|
|
if 'optionnotes' in options_confirm_params:
|
|
person_notes = options_confirm_params.split('optionnotes=')[1]
|
|
if '&' in person_notes:
|
|
person_notes = person_notes.split('&')[0]
|
|
person_notes = urllib.parse.unquote_plus(person_notes.strip())
|
|
# Limit the length of the notes
|
|
if len(person_notes) > 64000:
|
|
person_notes = None
|
|
|
|
# get the nickname
|
|
options_nickname = get_nickname_from_actor(options_actor)
|
|
if not options_nickname:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname in ' + options_actor)
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
options_domain, options_port = get_domain_from_actor(options_actor)
|
|
if not options_domain:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find domain in ' + options_actor)
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
options_domain_full = get_full_domain(options_domain, options_port)
|
|
if chooser_nickname == options_nickname and \
|
|
options_domain == domain and \
|
|
options_port == port:
|
|
if debug:
|
|
print('You cannot perform an option action on yourself')
|
|
|
|
# person options screen, view button
|
|
# See html_person_options
|
|
if '&submitView=' in options_confirm_params:
|
|
if debug:
|
|
print('Viewing ' + options_actor)
|
|
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
|
|
access_keys = self.server.access_keys
|
|
if self.server.key_shortcuts.get(chooser_nickname):
|
|
access_keys = self.server.key_shortcuts[chooser_nickname]
|
|
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
yt_replace_domain = \
|
|
self.server.yt_replace_domain
|
|
cached_webfingers = \
|
|
self.server.cached_webfingers
|
|
recent_posts_cache = \
|
|
self.server.recent_posts_cache
|
|
timezone = None
|
|
if self.server.account_timezone.get(chooser_nickname):
|
|
timezone = \
|
|
self.server.account_timezone.get(chooser_nickname)
|
|
|
|
profile_handle = remove_eol(options_actor).strip()
|
|
|
|
# establish the session
|
|
curr_proxy_type = self.server.proxy_type
|
|
if '.onion/' in profile_handle or \
|
|
profile_handle.endswith('.onion'):
|
|
curr_proxy_type = 'tor'
|
|
curr_session = self.server.session_onion
|
|
elif ('.i2p/' in profile_handle or
|
|
profile_handle.endswith('.i2p')):
|
|
curr_proxy_type = 'i2p'
|
|
curr_session = self.server.session_i2p
|
|
|
|
curr_session = \
|
|
establish_session("handle search",
|
|
curr_session,
|
|
curr_proxy_type,
|
|
self.server)
|
|
if not curr_session:
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(chooser_nickname):
|
|
bold_reading = True
|
|
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
max_shares_on_profile = \
|
|
self.server.max_shares_on_profile
|
|
profile_str = \
|
|
html_profile_after_search(authorized,
|
|
recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
http_prefix,
|
|
chooser_nickname,
|
|
domain,
|
|
port,
|
|
profile_handle,
|
|
curr_session,
|
|
cached_webfingers,
|
|
self.server.person_cache,
|
|
self.server.debug,
|
|
self.server.project_version,
|
|
yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
self.server.default_timeline,
|
|
peertube_instances,
|
|
allow_local_network_access,
|
|
self.server.theme_name,
|
|
access_keys,
|
|
self.server.system_language,
|
|
self.server.max_like_count,
|
|
signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
timezone,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
bold_reading,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.buy_sites,
|
|
max_shares_on_profile,
|
|
self.server.no_of_books,
|
|
self.server.auto_cw_cache)
|
|
if profile_str:
|
|
msg = profile_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
redirect_headers(self, options_actor,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, petname submit button
|
|
# See html_person_options
|
|
if '&submitPetname=' in options_confirm_params and petname:
|
|
if debug:
|
|
print('Change petname to ' + petname)
|
|
handle = options_nickname + '@' + options_domain_full
|
|
set_pet_name(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
handle, petname)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, person notes submit button
|
|
# See html_person_options
|
|
if '&submitPersonNotes=' in options_confirm_params:
|
|
if debug:
|
|
print('Change person notes')
|
|
handle = options_nickname + '@' + options_domain_full
|
|
if not person_notes:
|
|
person_notes = ''
|
|
set_person_notes(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
handle, person_notes)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, on calendar checkbox
|
|
# See html_person_options
|
|
if '&submitOnCalendar=' in options_confirm_params:
|
|
on_calendar = None
|
|
if 'onCalendar=' in options_confirm_params:
|
|
on_calendar = options_confirm_params.split('onCalendar=')[1]
|
|
if '&' in on_calendar:
|
|
on_calendar = on_calendar.split('&')[0]
|
|
if on_calendar == 'on':
|
|
add_person_to_calendar(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
else:
|
|
remove_person_from_calendar(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, minimize images checkbox
|
|
# See html_person_options
|
|
if '&submitMinimizeImages=' in options_confirm_params:
|
|
minimize_images = None
|
|
if 'minimizeImages=' in options_confirm_params:
|
|
minimize_images = \
|
|
options_confirm_params.split('minimizeImages=')[1]
|
|
if '&' in minimize_images:
|
|
minimize_images = minimize_images.split('&')[0]
|
|
if minimize_images == 'on':
|
|
person_minimize_images(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
else:
|
|
person_undo_minimize_images(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, allow announces checkbox
|
|
# See html_person_options
|
|
if '&submitAllowAnnounce=' in options_confirm_params:
|
|
allow_announce = None
|
|
if 'allowAnnounce=' in options_confirm_params:
|
|
allow_announce = \
|
|
options_confirm_params.split('allowAnnounce=')[1]
|
|
if '&' in allow_announce:
|
|
allow_announce = allow_announce.split('&')[0]
|
|
if allow_announce == 'on':
|
|
allowed_announce_add(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
else:
|
|
allowed_announce_remove(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, on notify checkbox
|
|
# See html_person_options
|
|
if '&submitNotifyOnPost=' in options_confirm_params:
|
|
notify = None
|
|
if 'notifyOnPost=' in options_confirm_params:
|
|
notify = options_confirm_params.split('notifyOnPost=')[1]
|
|
if '&' in notify:
|
|
notify = notify.split('&')[0]
|
|
if notify == 'on':
|
|
add_notify_on_post(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
else:
|
|
remove_notify_on_post(base_dir,
|
|
chooser_nickname,
|
|
domain,
|
|
options_nickname,
|
|
options_domain_full)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, permission to post to newswire
|
|
# See html_person_options
|
|
if '&submitPostToNews=' in options_confirm_params:
|
|
admin_nickname = get_config_param(self.server.base_dir, 'admin')
|
|
if (chooser_nickname != options_nickname and
|
|
(chooser_nickname == admin_nickname or
|
|
(is_moderator(self.server.base_dir, chooser_nickname) and
|
|
not is_moderator(self.server.base_dir, options_nickname)))):
|
|
posts_to_news = None
|
|
if 'postsToNews=' in options_confirm_params:
|
|
posts_to_news = \
|
|
options_confirm_params.split('postsToNews=')[1]
|
|
if '&' in posts_to_news:
|
|
posts_to_news = posts_to_news.split('&')[0]
|
|
account_dir = acct_dir(self.server.base_dir,
|
|
options_nickname, options_domain)
|
|
newswire_blocked_filename = account_dir + '/.nonewswire'
|
|
if posts_to_news == 'on':
|
|
if os.path.isfile(newswire_blocked_filename):
|
|
try:
|
|
os.remove(newswire_blocked_filename)
|
|
except OSError:
|
|
print('EX: _person_options unable to delete ' +
|
|
newswire_blocked_filename)
|
|
refresh_newswire(self.server.base_dir)
|
|
else:
|
|
if os.path.isdir(account_dir):
|
|
nw_filename = newswire_blocked_filename
|
|
nw_written = False
|
|
try:
|
|
with open(nw_filename, 'w+',
|
|
encoding='utf-8') as nofile:
|
|
nofile.write('\n')
|
|
nw_written = True
|
|
except OSError as ex:
|
|
print('EX: unable to write ' + nw_filename +
|
|
' ' + str(ex))
|
|
if nw_written:
|
|
refresh_newswire(self.server.base_dir)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, permission to post to featured articles
|
|
# See html_person_options
|
|
if '&submitPostToFeatures=' in options_confirm_params:
|
|
admin_nickname = get_config_param(self.server.base_dir, 'admin')
|
|
if (chooser_nickname != options_nickname and
|
|
(chooser_nickname == admin_nickname or
|
|
(is_moderator(self.server.base_dir, chooser_nickname) and
|
|
not is_moderator(self.server.base_dir, options_nickname)))):
|
|
posts_to_features = None
|
|
if 'postsToFeatures=' in options_confirm_params:
|
|
posts_to_features = \
|
|
options_confirm_params.split('postsToFeatures=')[1]
|
|
if '&' in posts_to_features:
|
|
posts_to_features = posts_to_features.split('&')[0]
|
|
account_dir = acct_dir(self.server.base_dir,
|
|
options_nickname, options_domain)
|
|
features_blocked_filename = account_dir + '/.nofeatures'
|
|
if posts_to_features == 'on':
|
|
if os.path.isfile(features_blocked_filename):
|
|
try:
|
|
os.remove(features_blocked_filename)
|
|
except OSError:
|
|
print('EX: _person_options unable to delete ' +
|
|
features_blocked_filename)
|
|
refresh_newswire(self.server.base_dir)
|
|
else:
|
|
if os.path.isdir(account_dir):
|
|
feat_filename = features_blocked_filename
|
|
feat_written = False
|
|
try:
|
|
with open(feat_filename, 'w+',
|
|
encoding='utf-8') as nofile:
|
|
nofile.write('\n')
|
|
feat_written = True
|
|
except OSError as ex:
|
|
print('EX: unable to write ' + feat_filename +
|
|
' ' + str(ex))
|
|
if feat_written:
|
|
refresh_newswire(self.server.base_dir)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, permission to post to newswire
|
|
# See html_person_options
|
|
if '&submitModNewsPosts=' in options_confirm_params:
|
|
admin_nickname = get_config_param(self.server.base_dir, 'admin')
|
|
if (chooser_nickname != options_nickname and
|
|
(chooser_nickname == admin_nickname or
|
|
(is_moderator(self.server.base_dir, chooser_nickname) and
|
|
not is_moderator(self.server.base_dir, options_nickname)))):
|
|
mod_posts_to_news = None
|
|
if 'modNewsPosts=' in options_confirm_params:
|
|
mod_posts_to_news = \
|
|
options_confirm_params.split('modNewsPosts=')[1]
|
|
if '&' in mod_posts_to_news:
|
|
mod_posts_to_news = mod_posts_to_news.split('&')[0]
|
|
account_dir = acct_dir(self.server.base_dir,
|
|
options_nickname, options_domain)
|
|
newswire_mod_filename = account_dir + '/.newswiremoderated'
|
|
if mod_posts_to_news != 'on':
|
|
if os.path.isfile(newswire_mod_filename):
|
|
try:
|
|
os.remove(newswire_mod_filename)
|
|
except OSError:
|
|
print('EX: _person_options unable to delete ' +
|
|
newswire_mod_filename)
|
|
else:
|
|
if os.path.isdir(account_dir):
|
|
nw_filename = newswire_mod_filename
|
|
try:
|
|
with open(nw_filename, 'w+',
|
|
encoding='utf-8') as modfile:
|
|
modfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write ' + nw_filename)
|
|
users_path_str = \
|
|
users_path + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, users_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, block button
|
|
# See html_person_options
|
|
if '&submitBlock=' in options_confirm_params:
|
|
if debug:
|
|
print('Blocking ' + options_actor)
|
|
msg = \
|
|
html_confirm_block(self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
options_actor,
|
|
options_avatar_url).encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, unblock button
|
|
# See html_person_options
|
|
if '&submitUnblock=' in options_confirm_params:
|
|
if debug:
|
|
print('Unblocking ' + options_actor)
|
|
msg = \
|
|
html_confirm_unblock(self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
options_actor,
|
|
options_avatar_url).encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, follow button
|
|
# See html_person_options followStr
|
|
if '&submitFollow=' in options_confirm_params or \
|
|
'&submitJoin=' in options_confirm_params:
|
|
if debug:
|
|
print('Following ' + options_actor)
|
|
msg = \
|
|
html_confirm_follow(self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
options_actor,
|
|
options_avatar_url,
|
|
chooser_nickname,
|
|
domain).encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, move button
|
|
# See html_person_options followStr
|
|
if '&submitMove=' in options_confirm_params and options_actor_moved:
|
|
if debug:
|
|
print('Moving ' + options_actor_moved)
|
|
msg = \
|
|
html_confirm_follow(self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
options_actor_moved,
|
|
options_avatar_url,
|
|
chooser_nickname,
|
|
domain).encode('utf-8')
|
|
if msg:
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, unfollow button
|
|
# See html_person_options followStr
|
|
if '&submitUnfollow=' in options_confirm_params or \
|
|
'&submitLeave=' in options_confirm_params:
|
|
print('Unfollowing ' + options_actor)
|
|
msg = \
|
|
html_confirm_unfollow(self.server.translate,
|
|
base_dir,
|
|
users_path,
|
|
options_actor,
|
|
options_avatar_url).encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, DM button
|
|
# See html_person_options
|
|
if '&submitDM=' in options_confirm_params:
|
|
if debug:
|
|
print('Sending DM to ' + options_actor)
|
|
report_path = path.replace('/personoptions', '') + '/newdm'
|
|
|
|
access_keys = self.server.access_keys
|
|
if '/users/' in path:
|
|
nickname = path.split('/users/')[1]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
if self.server.key_shortcuts.get(nickname):
|
|
access_keys = self.server.key_shortcuts[nickname]
|
|
|
|
custom_submit_text = get_config_param(base_dir, 'customSubmitText')
|
|
conversation_id = None
|
|
reply_is_chat = False
|
|
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(chooser_nickname):
|
|
bold_reading = True
|
|
|
|
languages_understood = \
|
|
get_understood_languages(base_dir,
|
|
http_prefix,
|
|
chooser_nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
|
|
default_post_language = self.server.system_language
|
|
if self.server.default_post_language.get(nickname):
|
|
default_post_language = \
|
|
self.server.default_post_language[nickname]
|
|
default_buy_site = ''
|
|
msg = \
|
|
html_new_post({}, False, self.server.translate,
|
|
base_dir,
|
|
http_prefix,
|
|
report_path, None,
|
|
[options_actor], None, None,
|
|
page_number, '',
|
|
chooser_nickname,
|
|
domain,
|
|
domain_full,
|
|
self.server.default_timeline,
|
|
self.server.newswire,
|
|
self.server.theme_name,
|
|
True, access_keys,
|
|
custom_submit_text,
|
|
conversation_id,
|
|
self.server.recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache,
|
|
self.server.port,
|
|
None,
|
|
self.server.project_version,
|
|
self.server.yt_replace_domain,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.show_published_date_only,
|
|
self.server.peertube_instances,
|
|
self.server.allow_local_network_access,
|
|
self.server.system_language,
|
|
languages_understood,
|
|
self.server.max_like_count,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
self.server.default_timeline,
|
|
reply_is_chat,
|
|
bold_reading,
|
|
self.server.dogwhistles,
|
|
self.server.min_images_for_accounts,
|
|
None, None, default_post_language,
|
|
self.server.buy_sites,
|
|
default_buy_site,
|
|
self.server.auto_cw_cache)
|
|
if msg:
|
|
msg = msg.encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, Info button
|
|
# See html_person_options
|
|
if '&submitPersonInfo=' in options_confirm_params:
|
|
if is_moderator(self.server.base_dir, chooser_nickname):
|
|
if debug:
|
|
print('Showing info for ' + options_actor)
|
|
signing_priv_key_pem = self.server.signing_priv_key_pem
|
|
msg = \
|
|
html_account_info(self.server.translate,
|
|
base_dir,
|
|
http_prefix,
|
|
chooser_nickname,
|
|
domain,
|
|
options_actor,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
signing_priv_key_pem,
|
|
None,
|
|
self.server.block_federated)
|
|
if msg:
|
|
msg = msg.encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
http_404(self, 11)
|
|
return
|
|
|
|
# person options screen, snooze button
|
|
# See html_person_options
|
|
if '&submitSnooze=' in options_confirm_params:
|
|
users_path = path.split('/personoptions')[0]
|
|
this_actor = http_prefix + '://' + domain_full + users_path
|
|
if debug:
|
|
print('Snoozing ' + options_actor + ' ' + this_actor)
|
|
if '/users/' in this_actor:
|
|
nickname = this_actor.split('/users/')[1]
|
|
person_snooze(base_dir, nickname,
|
|
domain, options_actor)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
this_actor = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
this_actor = 'http://' + i2p_domain + users_path
|
|
actor_path_str = \
|
|
this_actor + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, unsnooze button
|
|
# See html_person_options
|
|
if '&submitUnsnooze=' in options_confirm_params:
|
|
users_path = path.split('/personoptions')[0]
|
|
this_actor = http_prefix + '://' + domain_full + users_path
|
|
if debug:
|
|
print('Unsnoozing ' + options_actor + ' ' + this_actor)
|
|
if '/users/' in this_actor:
|
|
nickname = this_actor.split('/users/')[1]
|
|
person_unsnooze(base_dir, nickname,
|
|
domain, options_actor)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
this_actor = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
this_actor = 'http://' + i2p_domain + users_path
|
|
actor_path_str = \
|
|
this_actor + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# person options screen, report button
|
|
# See html_person_options
|
|
if '&submitReport=' in options_confirm_params:
|
|
if debug:
|
|
print('Reporting ' + options_actor)
|
|
report_path = \
|
|
path.replace('/personoptions', '') + '/newreport'
|
|
|
|
access_keys = self.server.access_keys
|
|
if '/users/' in path:
|
|
nickname = path.split('/users/')[1]
|
|
if '/' in nickname:
|
|
nickname = nickname.split('/')[0]
|
|
if self.server.key_shortcuts.get(nickname):
|
|
access_keys = self.server.key_shortcuts[nickname]
|
|
|
|
custom_submit_text = get_config_param(base_dir, 'customSubmitText')
|
|
conversation_id = None
|
|
reply_is_chat = False
|
|
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(chooser_nickname):
|
|
bold_reading = True
|
|
|
|
languages_understood = \
|
|
get_understood_languages(base_dir,
|
|
http_prefix,
|
|
chooser_nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
|
|
default_post_language = self.server.system_language
|
|
if self.server.default_post_language.get(nickname):
|
|
default_post_language = \
|
|
self.server.default_post_language[nickname]
|
|
default_buy_site = ''
|
|
msg = \
|
|
html_new_post({}, False, self.server.translate,
|
|
base_dir,
|
|
http_prefix,
|
|
report_path, None, [],
|
|
None, post_url, page_number, '',
|
|
chooser_nickname,
|
|
domain,
|
|
domain_full,
|
|
self.server.default_timeline,
|
|
self.server.newswire,
|
|
self.server.theme_name,
|
|
True, access_keys,
|
|
custom_submit_text,
|
|
conversation_id,
|
|
self.server.recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache,
|
|
self.server.port,
|
|
None,
|
|
self.server.project_version,
|
|
self.server.yt_replace_domain,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.show_published_date_only,
|
|
self.server.peertube_instances,
|
|
self.server.allow_local_network_access,
|
|
self.server.system_language,
|
|
languages_understood,
|
|
self.server.max_like_count,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
self.server.default_timeline,
|
|
reply_is_chat,
|
|
bold_reading,
|
|
self.server.dogwhistles,
|
|
self.server.min_images_for_accounts,
|
|
None, None, default_post_language,
|
|
self.server.buy_sites,
|
|
default_buy_site,
|
|
self.server.auto_cw_cache)
|
|
if msg:
|
|
msg = msg.encode('utf-8')
|
|
msglen = len(msg)
|
|
set_headers(self, 'text/html', msglen,
|
|
cookie, calling_domain, False)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# redirect back from person options screen
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif calling_domain.endswith('.i2p') and i2p_domain:
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
|
|
def _unfollow_confirm(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Confirm to unfollow
|
|
"""
|
|
users_path = path.split('/unfollowconfirm')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
follower_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not follower_nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
follow_confirm_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST follow_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST follow_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST follow_confirm_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in follow_confirm_params:
|
|
following_actor = \
|
|
urllib.parse.unquote_plus(follow_confirm_params)
|
|
following_actor = following_actor.split('actor=')[1]
|
|
if '&' in following_actor:
|
|
following_actor = following_actor.split('&')[0]
|
|
following_nickname = get_nickname_from_actor(following_actor)
|
|
following_domain, following_port = \
|
|
get_domain_from_actor(following_actor)
|
|
if not following_nickname or not following_domain:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
following_domain_full = \
|
|
get_full_domain(following_domain, following_port)
|
|
if follower_nickname == following_nickname and \
|
|
following_domain == domain and \
|
|
following_port == port:
|
|
if debug:
|
|
print('You cannot unfollow yourself!')
|
|
else:
|
|
if debug:
|
|
print(follower_nickname + ' stops following ' +
|
|
following_actor)
|
|
follow_actor = \
|
|
local_actor_url(http_prefix,
|
|
follower_nickname, domain_full)
|
|
status_number, _ = get_status_number()
|
|
follow_id = follow_actor + '/statuses/' + str(status_number)
|
|
unfollow_json = {
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
'id': follow_id + '/undo',
|
|
'type': 'Undo',
|
|
'actor': follow_actor,
|
|
'object': {
|
|
'id': follow_id,
|
|
'type': 'Follow',
|
|
'actor': follow_actor,
|
|
'object': following_actor
|
|
}
|
|
}
|
|
path_users_section = path.split('/users/')[1]
|
|
self.post_to_nickname = path_users_section.split('/')[0]
|
|
group_account = has_group_type(base_dir, following_actor,
|
|
self.server.person_cache)
|
|
unfollow_account(self.server.base_dir, self.post_to_nickname,
|
|
self.server.domain,
|
|
following_nickname, following_domain_full,
|
|
self.server.debug, group_account,
|
|
'following.txt')
|
|
post_to_outbox_thread(self, unfollow_json,
|
|
curr_session, proxy_type)
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _follow_confirm(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Confirm to follow
|
|
"""
|
|
users_path = path.split('/followconfirm')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
follower_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not follower_nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
follow_confirm_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST follow_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST follow_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST follow_confirm_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitView=' in follow_confirm_params:
|
|
following_actor = \
|
|
urllib.parse.unquote_plus(follow_confirm_params)
|
|
following_actor = following_actor.split('actor=')[1]
|
|
if '&' in following_actor:
|
|
following_actor = following_actor.split('&')[0]
|
|
redirect_headers(self, following_actor, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitInfo=' in follow_confirm_params:
|
|
following_actor = \
|
|
urllib.parse.unquote_plus(follow_confirm_params)
|
|
following_actor = following_actor.split('actor=')[1]
|
|
if '&' in following_actor:
|
|
following_actor = following_actor.split('&')[0]
|
|
if is_moderator(base_dir, follower_nickname):
|
|
msg = \
|
|
html_account_info(self.server.translate,
|
|
base_dir, http_prefix,
|
|
follower_nickname,
|
|
self.server.domain,
|
|
following_actor,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.signing_priv_key_pem,
|
|
users_path,
|
|
self.server.block_federated)
|
|
if msg:
|
|
msg = msg.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
redirect_headers(self, following_actor, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in follow_confirm_params:
|
|
following_actor = \
|
|
urllib.parse.unquote_plus(follow_confirm_params)
|
|
following_actor = following_actor.split('actor=')[1]
|
|
if '&' in following_actor:
|
|
following_actor = following_actor.split('&')[0]
|
|
following_nickname = get_nickname_from_actor(following_actor)
|
|
following_domain, following_port = \
|
|
get_domain_from_actor(following_actor)
|
|
if not following_nickname or not following_domain:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if follower_nickname == following_nickname and \
|
|
following_domain == domain and \
|
|
following_port == port:
|
|
if debug:
|
|
print('You cannot follow yourself!')
|
|
elif (following_nickname == 'news' and
|
|
following_domain == domain and
|
|
following_port == port):
|
|
if debug:
|
|
print('You cannot follow the news actor')
|
|
else:
|
|
print('Sending follow request from ' +
|
|
follower_nickname + ' to ' + following_actor)
|
|
if not self.server.signing_priv_key_pem:
|
|
print('Sending follow request with no signing key')
|
|
|
|
curr_domain = domain
|
|
curr_port = port
|
|
curr_http_prefix = http_prefix
|
|
curr_proxy_type = proxy_type
|
|
if onion_domain:
|
|
if not curr_domain.endswith('.onion') and \
|
|
following_domain.endswith('.onion'):
|
|
curr_session = self.server.session_onion
|
|
curr_domain = onion_domain
|
|
curr_port = 80
|
|
following_port = 80
|
|
curr_http_prefix = 'http'
|
|
curr_proxy_type = 'tor'
|
|
if i2p_domain:
|
|
if not curr_domain.endswith('.i2p') and \
|
|
following_domain.endswith('.i2p'):
|
|
curr_session = self.server.session_i2p
|
|
curr_domain = i2p_domain
|
|
curr_port = 80
|
|
following_port = 80
|
|
curr_http_prefix = 'http'
|
|
curr_proxy_type = 'i2p'
|
|
|
|
curr_session = \
|
|
establish_session("follow request",
|
|
curr_session,
|
|
curr_proxy_type,
|
|
self.server)
|
|
|
|
send_follow_request(curr_session,
|
|
base_dir, follower_nickname,
|
|
domain, curr_domain, curr_port,
|
|
curr_http_prefix,
|
|
following_nickname,
|
|
following_domain,
|
|
following_actor,
|
|
following_port, curr_http_prefix,
|
|
False, self.server.federation_list,
|
|
self.server.send_threads,
|
|
self.server.postLog,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache, debug,
|
|
self.server.project_version,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.domain,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
self.server.sites_unavailable,
|
|
self.server.system_language)
|
|
|
|
if '&submitUnblock=' in follow_confirm_params:
|
|
blocking_actor = \
|
|
urllib.parse.unquote_plus(follow_confirm_params)
|
|
blocking_actor = blocking_actor.split('actor=')[1]
|
|
if '&' in blocking_actor:
|
|
blocking_actor = blocking_actor.split('&')[0]
|
|
blocking_nickname = get_nickname_from_actor(blocking_actor)
|
|
blocking_domain, blocking_port = \
|
|
get_domain_from_actor(blocking_actor)
|
|
if not blocking_nickname or not blocking_domain:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find blocked nickname or domain in ' +
|
|
blocking_actor)
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
blocking_domain_full = \
|
|
get_full_domain(blocking_domain, blocking_port)
|
|
if follower_nickname == blocking_nickname and \
|
|
blocking_domain == domain and \
|
|
blocking_port == port:
|
|
if debug:
|
|
print('You cannot unblock yourself!')
|
|
else:
|
|
if debug:
|
|
print(follower_nickname + ' stops blocking ' +
|
|
blocking_actor)
|
|
remove_block(base_dir,
|
|
follower_nickname, domain,
|
|
blocking_nickname, blocking_domain_full)
|
|
if is_moderator(base_dir, follower_nickname):
|
|
remove_global_block(base_dir,
|
|
blocking_nickname,
|
|
blocking_domain_full)
|
|
blocked_cache_last_updated = \
|
|
self.server.blocked_cache_last_updated
|
|
self.server.blocked_cache_last_updated = \
|
|
update_blocked_cache(self.server.base_dir,
|
|
self.server.blocked_cache,
|
|
blocked_cache_last_updated, 0)
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _remove_reading_status(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool,
|
|
books_cache: {}) -> None:
|
|
"""Remove a reading status from the profile screen
|
|
"""
|
|
users_path = path.split('/removereadingstatus')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
reader_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not reader_nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
remove_reading_status_params = \
|
|
self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST remove_reading_status_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST remove_reading_status_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST remove_reading_status_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitRemoveReadingStatus=' in remove_reading_status_params:
|
|
reading_actor = \
|
|
urllib.parse.unquote_plus(remove_reading_status_params)
|
|
reading_actor = reading_actor.split('actor=')[1]
|
|
if '&' in reading_actor:
|
|
reading_actor = reading_actor.split('&')[0]
|
|
|
|
if reading_actor == origin_path_str:
|
|
post_secs_since_epoch = \
|
|
urllib.parse.unquote_plus(remove_reading_status_params)
|
|
post_secs_since_epoch = \
|
|
post_secs_since_epoch.split('publishedtimesec=')[1]
|
|
if '&' in post_secs_since_epoch:
|
|
post_secs_since_epoch = post_secs_since_epoch.split('&')[0]
|
|
|
|
book_event_type = \
|
|
urllib.parse.unquote_plus(remove_reading_status_params)
|
|
book_event_type = \
|
|
book_event_type.split('bookeventtype=')[1]
|
|
if '&' in book_event_type:
|
|
book_event_type = book_event_type.split('&')[0]
|
|
|
|
remove_reading_event(base_dir,
|
|
reading_actor, post_secs_since_epoch,
|
|
book_event_type, books_cache, debug)
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _block_confirm(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool) -> None:
|
|
"""Confirms a block from the person options screen
|
|
"""
|
|
users_path = path.split('/blockconfirm')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
blocker_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not blocker_nickname:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname in ' + origin_path_str)
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
block_confirm_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST block_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST block_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST block_confirm_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in block_confirm_params:
|
|
blocking_confirm_str = \
|
|
urllib.parse.unquote_plus(block_confirm_params)
|
|
block_reason = blocking_confirm_str.split('blockReason=')[1]
|
|
if '&' in block_reason:
|
|
block_reason = block_reason.split('&')[0]
|
|
blocking_actor = blocking_confirm_str.split('actor=')[1]
|
|
if '&' in blocking_actor:
|
|
blocking_actor = blocking_actor.split('&')[0]
|
|
blocking_nickname = get_nickname_from_actor(blocking_actor)
|
|
blocking_domain, blocking_port = \
|
|
get_domain_from_actor(blocking_actor)
|
|
if not blocking_nickname or not blocking_domain:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname or domain in ' +
|
|
blocking_actor)
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
blocking_domain_full = \
|
|
get_full_domain(blocking_domain, blocking_port)
|
|
if blocker_nickname == blocking_nickname and \
|
|
blocking_domain == domain and \
|
|
blocking_port == port:
|
|
if debug:
|
|
print('You cannot block yourself!')
|
|
else:
|
|
print('Adding block by ' + blocker_nickname +
|
|
' of ' + blocking_actor)
|
|
add_block(base_dir, blocker_nickname,
|
|
domain, blocking_nickname,
|
|
blocking_domain_full, block_reason)
|
|
remove_follower(base_dir, blocker_nickname,
|
|
domain,
|
|
blocking_nickname,
|
|
blocking_domain_full)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str, cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _unblock_confirm(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool) -> None:
|
|
"""Confirms a unblock
|
|
"""
|
|
users_path = path.split('/unblockconfirm')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
blocker_nickname = get_nickname_from_actor(origin_path_str)
|
|
if not blocker_nickname:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname in ' + origin_path_str)
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
block_confirm_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST block_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST block_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST block_confirm_params rfile.read failed, ' +
|
|
str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in block_confirm_params:
|
|
blocking_actor = \
|
|
urllib.parse.unquote_plus(block_confirm_params)
|
|
blocking_actor = blocking_actor.split('actor=')[1]
|
|
if '&' in blocking_actor:
|
|
blocking_actor = blocking_actor.split('&')[0]
|
|
blocking_nickname = get_nickname_from_actor(blocking_actor)
|
|
blocking_domain, blocking_port = \
|
|
get_domain_from_actor(blocking_actor)
|
|
if not blocking_nickname or not blocking_domain:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
print('WARN: unable to find nickname in ' + blocking_actor)
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
blocking_domain_full = \
|
|
get_full_domain(blocking_domain, blocking_port)
|
|
if blocker_nickname == blocking_nickname and \
|
|
blocking_domain == domain and \
|
|
blocking_port == port:
|
|
if debug:
|
|
print('You cannot unblock yourself!')
|
|
else:
|
|
if debug:
|
|
print(blocker_nickname + ' stops blocking ' +
|
|
blocking_actor)
|
|
remove_block(base_dir,
|
|
blocker_nickname, domain,
|
|
blocking_nickname, blocking_domain_full)
|
|
if is_moderator(base_dir, blocker_nickname):
|
|
remove_global_block(base_dir,
|
|
blocking_nickname,
|
|
blocking_domain_full)
|
|
blocked_cache_last_updated = \
|
|
self.server.blocked_cache_last_updated
|
|
self.server.blocked_cache_last_updated = \
|
|
update_blocked_cache(self.server.base_dir,
|
|
self.server.blocked_cache,
|
|
blocked_cache_last_updated, 0)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _receive_search_query(self, calling_domain: str, cookie: str,
|
|
authorized: bool, path: str,
|
|
base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str,
|
|
port: int, search_for_emoji: bool,
|
|
onion_domain: str, i2p_domain: str,
|
|
getreq_start_time, debug: bool,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Receive a search query
|
|
"""
|
|
# get the page number
|
|
page_number = 1
|
|
if '/searchhandle?page=' in path:
|
|
page_number_str = path.split('/searchhandle?page=')[1]
|
|
if '#' in page_number_str:
|
|
page_number_str = page_number_str.split('#')[0]
|
|
if len(page_number_str) > 5:
|
|
page_number_str = "1"
|
|
if page_number_str.isdigit():
|
|
page_number = int(page_number_str)
|
|
path = path.split('?page=')[0]
|
|
|
|
users_path = path.replace('/searchhandle', '')
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
http_prefix,
|
|
domain_full,
|
|
onion_domain,
|
|
i2p_domain) + \
|
|
users_path
|
|
length = int(self.headers['Content-length'])
|
|
try:
|
|
search_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST search_params connection was reset')
|
|
else:
|
|
print('EX: POST search_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST search_params rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if 'submitBack=' in search_params:
|
|
# go back on search screen
|
|
redirect_headers(self, actor_str + '/' +
|
|
self.server.default_timeline,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
if 'searchtext=' in search_params:
|
|
search_str = search_params.split('searchtext=')[1]
|
|
if '&' in search_str:
|
|
search_str = search_str.split('&')[0]
|
|
search_str = \
|
|
urllib.parse.unquote_plus(search_str.strip())
|
|
search_str = search_str.strip()
|
|
print('search_str: ' + search_str)
|
|
if search_for_emoji:
|
|
search_str = ':' + search_str + ':'
|
|
if search_str.startswith('#'):
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
if not nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# hashtag search
|
|
timezone = None
|
|
if self.server.account_timezone.get(nickname):
|
|
timezone = \
|
|
self.server.account_timezone.get(nickname)
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(nickname):
|
|
bold_reading = True
|
|
hashtag_str = \
|
|
html_hashtag_search(nickname, domain, port,
|
|
self.server.recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
base_dir,
|
|
search_str[1:], 1,
|
|
MAX_POSTS_IN_HASHTAG_FEED,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache,
|
|
http_prefix,
|
|
self.server.project_version,
|
|
self.server.yt_replace_domain,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.show_published_date_only,
|
|
self.server.peertube_instances,
|
|
self.server.allow_local_network_access,
|
|
self.server.theme_name,
|
|
self.server.system_language,
|
|
self.server.max_like_count,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
timezone, bold_reading,
|
|
self.server.dogwhistles,
|
|
self.server.map_format,
|
|
self.server.access_keys,
|
|
'search',
|
|
self.server.min_images_for_accounts,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
if hashtag_str:
|
|
msg = hashtag_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif (search_str.startswith('*') or
|
|
search_str.endswith(' skill')):
|
|
possible_endings = (
|
|
' skill'
|
|
)
|
|
for poss_ending in possible_endings:
|
|
if search_str.endswith(poss_ending):
|
|
search_str = search_str.replace(poss_ending, '')
|
|
break
|
|
# skill search
|
|
search_str = search_str.replace('*', '').strip()
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
skill_str = \
|
|
html_skills_search(actor_str,
|
|
self.server.translate,
|
|
base_dir,
|
|
search_str,
|
|
self.server.instance_only_skills_search,
|
|
64, nickname, domain,
|
|
self.server.theme_name,
|
|
self.server.access_keys)
|
|
if skill_str:
|
|
msg = skill_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif (search_str.startswith("'") or
|
|
search_str.endswith(' history') or
|
|
search_str.endswith(' in sent') or
|
|
search_str.endswith(' in outbox') or
|
|
search_str.endswith(' in outgoing') or
|
|
search_str.endswith(' in sent items') or
|
|
search_str.endswith(' in sent posts') or
|
|
search_str.endswith(' in outgoing posts') or
|
|
search_str.endswith(' in my history') or
|
|
search_str.endswith(' in my outbox') or
|
|
search_str.endswith(' in my posts')):
|
|
possible_endings = (
|
|
' in my posts',
|
|
' in my history',
|
|
' in my outbox',
|
|
' in sent posts',
|
|
' in outgoing posts',
|
|
' in sent items',
|
|
' in history',
|
|
' in outbox',
|
|
' in outgoing',
|
|
' in sent',
|
|
' history'
|
|
)
|
|
for poss_ending in possible_endings:
|
|
if search_str.endswith(poss_ending):
|
|
search_str = search_str.replace(poss_ending, '')
|
|
break
|
|
# your post history search
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
if not nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
search_str = search_str.replace("'", '', 1).strip()
|
|
timezone = None
|
|
if self.server.account_timezone.get(nickname):
|
|
timezone = \
|
|
self.server.account_timezone.get(nickname)
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(nickname):
|
|
bold_reading = True
|
|
history_str = \
|
|
html_history_search(self.server.translate,
|
|
base_dir,
|
|
http_prefix,
|
|
nickname,
|
|
domain,
|
|
search_str,
|
|
MAX_POSTS_IN_FEED,
|
|
page_number,
|
|
self.server.project_version,
|
|
self.server.recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache,
|
|
port,
|
|
self.server.yt_replace_domain,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.show_published_date_only,
|
|
self.server.peertube_instances,
|
|
self.server.allow_local_network_access,
|
|
self.server.theme_name, 'outbox',
|
|
self.server.system_language,
|
|
self.server.max_like_count,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
timezone, bold_reading,
|
|
self.server.dogwhistles,
|
|
self.server.access_keys,
|
|
self.server.min_images_for_accounts,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
if history_str:
|
|
msg = history_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif (search_str.startswith('-') or
|
|
search_str.endswith(' in my saved items') or
|
|
search_str.endswith(' in my saved posts') or
|
|
search_str.endswith(' in my bookmarks') or
|
|
search_str.endswith(' in my saved') or
|
|
search_str.endswith(' in my saves') or
|
|
search_str.endswith(' in saved posts') or
|
|
search_str.endswith(' in saved items') or
|
|
search_str.endswith(' in bookmarks') or
|
|
search_str.endswith(' in saved') or
|
|
search_str.endswith(' in saves') or
|
|
search_str.endswith(' bookmark')):
|
|
possible_endings = (
|
|
' in my bookmarks'
|
|
' in my saved posts'
|
|
' in my saved items'
|
|
' in my saved'
|
|
' in my saves'
|
|
' in saved posts'
|
|
' in saved items'
|
|
' in saved'
|
|
' in saves'
|
|
' in bookmarks'
|
|
' bookmark'
|
|
)
|
|
for poss_ending in possible_endings:
|
|
if search_str.endswith(poss_ending):
|
|
search_str = search_str.replace(poss_ending, '')
|
|
break
|
|
# bookmark search
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
if not nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
search_str = search_str.replace('-', '', 1).strip()
|
|
timezone = None
|
|
if self.server.account_timezone.get(nickname):
|
|
timezone = \
|
|
self.server.account_timezone.get(nickname)
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(nickname):
|
|
bold_reading = True
|
|
bookmarks_str = \
|
|
html_history_search(self.server.translate,
|
|
base_dir,
|
|
http_prefix,
|
|
nickname,
|
|
domain,
|
|
search_str,
|
|
MAX_POSTS_IN_FEED,
|
|
page_number,
|
|
self.server.project_version,
|
|
self.server.recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.person_cache,
|
|
port,
|
|
self.server.yt_replace_domain,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.show_published_date_only,
|
|
self.server.peertube_instances,
|
|
self.server.allow_local_network_access,
|
|
self.server.theme_name, 'bookmarks',
|
|
self.server.system_language,
|
|
self.server.max_like_count,
|
|
self.server.signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
timezone, bold_reading,
|
|
self.server.dogwhistles,
|
|
self.server.access_keys,
|
|
self.server.min_images_for_accounts,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
if bookmarks_str:
|
|
msg = bookmarks_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif ('@' in search_str or
|
|
('://' in search_str and
|
|
has_users_path(search_str))):
|
|
remote_only = False
|
|
if search_str.endswith(';remote'):
|
|
search_str = search_str.replace(';remote', '')
|
|
remote_only = True
|
|
if search_str.endswith(':') or \
|
|
search_str.endswith(';') or \
|
|
search_str.endswith('.'):
|
|
actor_str = \
|
|
get_instance_url(calling_domain, http_prefix,
|
|
domain_full, onion_domain,
|
|
i2p_domain) + \
|
|
users_path
|
|
redirect_headers(self, actor_str + '/search',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
# profile search
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
if not nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
profile_path_str = path.replace('/searchhandle', '')
|
|
|
|
# are we already following or followed by the searched
|
|
# for handle?
|
|
search_nickname = get_nickname_from_actor(search_str)
|
|
search_domain, search_port = \
|
|
get_domain_from_actor(search_str)
|
|
search_follower = \
|
|
is_follower_of_person(base_dir, nickname, domain,
|
|
search_nickname, search_domain)
|
|
search_following = \
|
|
is_following_actor(base_dir, nickname, domain, search_str)
|
|
if not remote_only and (search_follower or search_following):
|
|
# get the actor
|
|
if not has_users_path(search_str):
|
|
if not search_nickname or not search_domain:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
search_domain_full = \
|
|
get_full_domain(search_domain, search_port)
|
|
actor = \
|
|
local_actor_url(http_prefix, search_nickname,
|
|
search_domain_full)
|
|
else:
|
|
actor = search_str
|
|
|
|
# establish the session
|
|
curr_proxy_type = proxy_type
|
|
if '.onion/' in actor:
|
|
curr_proxy_type = 'tor'
|
|
curr_session = self.server.session_onion
|
|
elif '.i2p/' in actor:
|
|
curr_proxy_type = 'i2p'
|
|
curr_session = self.server.session_i2p
|
|
|
|
curr_session = \
|
|
establish_session("handle search",
|
|
curr_session,
|
|
curr_proxy_type,
|
|
self.server)
|
|
if not curr_session:
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# get the avatar url for the actor
|
|
avatar_url = \
|
|
get_avatar_image_url(curr_session,
|
|
base_dir, http_prefix,
|
|
actor,
|
|
self.server.person_cache,
|
|
None, True,
|
|
self.server.signing_priv_key_pem)
|
|
profile_path_str += \
|
|
'?options=' + actor + ';1;' + avatar_url
|
|
|
|
show_person_options(self, calling_domain, profile_path_str,
|
|
base_dir,
|
|
domain, domain_full,
|
|
getreq_start_time,
|
|
cookie, debug, authorized,
|
|
curr_session)
|
|
return
|
|
else:
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
|
|
access_keys = self.server.access_keys
|
|
if self.server.key_shortcuts.get(nickname):
|
|
access_keys = self.server.key_shortcuts[nickname]
|
|
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
yt_replace_domain = \
|
|
self.server.yt_replace_domain
|
|
cached_webfingers = \
|
|
self.server.cached_webfingers
|
|
recent_posts_cache = \
|
|
self.server.recent_posts_cache
|
|
timezone = None
|
|
if self.server.account_timezone.get(nickname):
|
|
timezone = \
|
|
self.server.account_timezone.get(nickname)
|
|
|
|
profile_handle = remove_eol(search_str).strip()
|
|
|
|
# establish the session
|
|
curr_proxy_type = proxy_type
|
|
if '.onion/' in profile_handle or \
|
|
profile_handle.endswith('.onion'):
|
|
curr_proxy_type = 'tor'
|
|
curr_session = self.server.session_onion
|
|
elif ('.i2p/' in profile_handle or
|
|
profile_handle.endswith('.i2p')):
|
|
curr_proxy_type = 'i2p'
|
|
curr_session = self.server.session_i2p
|
|
|
|
curr_session = \
|
|
establish_session("handle search",
|
|
curr_session,
|
|
curr_proxy_type,
|
|
self.server)
|
|
if not curr_session:
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
bold_reading = False
|
|
if self.server.bold_reading.get(nickname):
|
|
bold_reading = True
|
|
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
max_shares_on_profile = \
|
|
self.server.max_shares_on_profile
|
|
profile_str = \
|
|
html_profile_after_search(authorized,
|
|
recent_posts_cache,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
base_dir,
|
|
profile_path_str,
|
|
http_prefix,
|
|
nickname,
|
|
domain,
|
|
port,
|
|
profile_handle,
|
|
curr_session,
|
|
cached_webfingers,
|
|
self.server.person_cache,
|
|
self.server.debug,
|
|
self.server.project_version,
|
|
yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
self.server.default_timeline,
|
|
peertube_instances,
|
|
allow_local_network_access,
|
|
self.server.theme_name,
|
|
access_keys,
|
|
self.server.system_language,
|
|
self.server.max_like_count,
|
|
signing_priv_key_pem,
|
|
self.server.cw_lists,
|
|
self.server.lists_enabled,
|
|
timezone,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain,
|
|
bold_reading,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.buy_sites,
|
|
max_shares_on_profile,
|
|
self.server.no_of_books,
|
|
self.server.auto_cw_cache)
|
|
if profile_str:
|
|
msg = profile_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
http_prefix, domain_full,
|
|
onion_domain, i2p_domain) + \
|
|
users_path
|
|
redirect_headers(self, actor_str + '/search',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif (search_str.startswith(':') or
|
|
search_str.endswith(' emoji')):
|
|
# eg. "cat emoji"
|
|
if search_str.endswith(' emoji'):
|
|
search_str = \
|
|
search_str.replace(' emoji', '')
|
|
# emoji search
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
emoji_str = \
|
|
html_search_emoji(self.server.translate,
|
|
base_dir, search_str,
|
|
nickname, domain,
|
|
self.server.theme_name,
|
|
self.server.access_keys)
|
|
if emoji_str:
|
|
msg = emoji_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif search_str.startswith('.'):
|
|
# wanted items search
|
|
shared_items_federated_domains = \
|
|
self.server.shared_items_federated_domains
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
wanted_items_str = \
|
|
html_search_shared_items(self.server.translate,
|
|
base_dir,
|
|
search_str[1:], page_number,
|
|
MAX_POSTS_IN_FEED,
|
|
http_prefix,
|
|
domain_full,
|
|
actor_str, calling_domain,
|
|
shared_items_federated_domains,
|
|
'wanted', nickname, domain,
|
|
self.server.theme_name,
|
|
self.server.access_keys)
|
|
if wanted_items_str:
|
|
msg = wanted_items_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
# shared items search
|
|
shared_items_federated_domains = \
|
|
self.server.shared_items_federated_domains
|
|
nickname = get_nickname_from_actor(actor_str)
|
|
shared_items_str = \
|
|
html_search_shared_items(self.server.translate,
|
|
base_dir,
|
|
search_str, page_number,
|
|
MAX_POSTS_IN_FEED,
|
|
http_prefix,
|
|
domain_full,
|
|
actor_str, calling_domain,
|
|
shared_items_federated_domains,
|
|
'shares', nickname, domain,
|
|
self.server.theme_name,
|
|
self.server.access_keys)
|
|
if shared_items_str:
|
|
msg = shared_items_str.encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
actor_str = \
|
|
get_instance_url(calling_domain, http_prefix,
|
|
domain_full, onion_domain, i2p_domain) + \
|
|
users_path
|
|
redirect_headers(self, actor_str + '/' +
|
|
self.server.default_timeline,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _receive_vote(self, calling_domain: str, cookie: str,
|
|
path: str, http_prefix: str,
|
|
domain: str, domain_full: str, port: int,
|
|
onion_domain: str, i2p_domain: str,
|
|
curr_session, proxy_type: str,
|
|
base_dir: str, city: str,
|
|
person_cache: {}, debug: bool,
|
|
system_language: str,
|
|
low_bandwidth: bool,
|
|
dm_license_url: str,
|
|
content_license_url: str,
|
|
translate: {}, max_replies: int,
|
|
project_version: str,
|
|
recent_posts_cache: {}) -> None:
|
|
"""Receive a vote via POST
|
|
"""
|
|
first_post_id = ''
|
|
if '?firstpost=' in path:
|
|
first_post_id = path.split('?firstpost=')[1]
|
|
path = path.split('?firstpost=')[0]
|
|
if ';firstpost=' in path:
|
|
first_post_id = path.split(';firstpost=')[1]
|
|
path = path.split(';firstpost=')[0]
|
|
if first_post_id:
|
|
if '?' in first_post_id:
|
|
first_post_id = first_post_id.split('?')[0]
|
|
if ';' in first_post_id:
|
|
first_post_id = first_post_id.split(';')[0]
|
|
first_post_id = first_post_id.replace('/', '--')
|
|
first_post_id = ';firstpost=' + first_post_id.replace('#', '--')
|
|
|
|
last_post_id = ''
|
|
if '?lastpost=' in path:
|
|
last_post_id = path.split('?lastpost=')[1]
|
|
path = path.split('?lastpost=')[0]
|
|
if ';lastpost=' in path:
|
|
last_post_id = path.split(';lastpost=')[1]
|
|
path = path.split(';lastpost=')[0]
|
|
if last_post_id:
|
|
if '?' in last_post_id:
|
|
last_post_id = last_post_id.split('?')[0]
|
|
if ';' in last_post_id:
|
|
last_post_id = last_post_id.split(';')[0]
|
|
last_post_id = last_post_id.replace('/', '--')
|
|
last_post_id = ';lastpost=' + last_post_id.replace('#', '--')
|
|
|
|
page_number = 1
|
|
if '?page=' in path:
|
|
page_number_str = path.split('?page=')[1]
|
|
if '#' in page_number_str:
|
|
page_number_str = page_number_str.split('#')[0]
|
|
if len(page_number_str) > 5:
|
|
page_number_str = "1"
|
|
if page_number_str.isdigit():
|
|
page_number = int(page_number_str)
|
|
|
|
# the actor who votes
|
|
users_path = path.replace('/question', '')
|
|
actor = http_prefix + '://' + domain_full + users_path
|
|
nickname = get_nickname_from_actor(actor)
|
|
if not nickname:
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
actor = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
actor = 'http://' + i2p_domain + users_path
|
|
actor_path_str = \
|
|
actor + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number)
|
|
redirect_headers(self, actor_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# get the parameters
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
question_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST question_params connection was reset')
|
|
else:
|
|
print('EX: POST question_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST question_params rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
question_params = question_params.replace('+', ' ')
|
|
question_params = question_params.replace('%3F', '')
|
|
question_params = \
|
|
urllib.parse.unquote_plus(question_params.strip())
|
|
|
|
# post being voted on
|
|
message_id = None
|
|
if 'messageId=' in question_params:
|
|
message_id = question_params.split('messageId=')[1]
|
|
if '&' in message_id:
|
|
message_id = message_id.split('&')[0]
|
|
|
|
answer = None
|
|
if 'answer=' in question_params:
|
|
answer = question_params.split('answer=')[1]
|
|
if '&' in answer:
|
|
answer = answer.split('&')[0]
|
|
|
|
_send_reply_to_question(self, base_dir, http_prefix,
|
|
nickname, domain, domain_full, port,
|
|
message_id, answer,
|
|
curr_session, proxy_type, city,
|
|
person_cache, debug,
|
|
system_language,
|
|
low_bandwidth,
|
|
dm_license_url,
|
|
content_license_url,
|
|
translate, max_replies,
|
|
project_version,
|
|
recent_posts_cache)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
actor = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
actor = 'http://' + i2p_domain + users_path
|
|
actor_path_str = \
|
|
actor + '/' + self.server.default_timeline + \
|
|
'?page=' + str(page_number) + first_post_id + last_post_id
|
|
redirect_headers(self, actor_path_str, cookie,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
|
|
def _send_reply_to_question(self, base_dir: str,
|
|
http_prefix: str,
|
|
nickname: str, domain: str,
|
|
domain_full: str,
|
|
port: int,
|
|
message_id: str,
|
|
answer: str,
|
|
curr_session, proxy_type: str,
|
|
city_name: str,
|
|
person_cache: {},
|
|
debug: bool,
|
|
system_language: str,
|
|
low_bandwidth: bool,
|
|
dm_license_url: str,
|
|
content_license_url: str,
|
|
translate: {},
|
|
max_replies: int,
|
|
project_version: str,
|
|
recent_posts_cache: {}) -> None:
|
|
"""Sends a reply to a question
|
|
"""
|
|
votes_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/questions.txt'
|
|
|
|
if os.path.isfile(votes_filename):
|
|
# have we already voted on this?
|
|
if text_in_file(message_id, votes_filename):
|
|
print('Already voted on message ' + message_id)
|
|
return
|
|
|
|
print('Voting on message ' + message_id)
|
|
print('Vote for: ' + answer)
|
|
comments_enabled = True
|
|
attach_image_filename = None
|
|
media_type = None
|
|
image_description = None
|
|
video_transcript = None
|
|
in_reply_to = message_id
|
|
in_reply_to_atom_uri = message_id
|
|
subject = None
|
|
schedule_post = False
|
|
event_date = None
|
|
event_time = None
|
|
event_end_time = None
|
|
location = None
|
|
conversation_id = None
|
|
buy_url = ''
|
|
chat_url = ''
|
|
city = get_spoofed_city(city_name, base_dir, nickname, domain)
|
|
languages_understood = \
|
|
get_understood_languages(base_dir, http_prefix,
|
|
nickname, domain_full,
|
|
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)
|
|
message_json = None
|
|
if reply_to_nickname and reply_to_domain:
|
|
reply_to_domain_full = \
|
|
get_full_domain(reply_to_domain, reply_to_port)
|
|
mentions_str = '@' + reply_to_nickname + '@' + reply_to_domain_full
|
|
|
|
message_json = \
|
|
create_direct_message_post(base_dir, nickname, domain,
|
|
port, http_prefix,
|
|
mentions_str + ' ' + answer,
|
|
False, False,
|
|
comments_enabled,
|
|
attach_image_filename,
|
|
media_type, image_description,
|
|
video_transcript, city,
|
|
in_reply_to, in_reply_to_atom_uri,
|
|
subject, debug,
|
|
schedule_post,
|
|
event_date, event_time,
|
|
event_end_time,
|
|
location,
|
|
system_language,
|
|
conversation_id,
|
|
low_bandwidth,
|
|
dm_license_url,
|
|
content_license_url, '',
|
|
languages_understood, False,
|
|
translate, buy_url,
|
|
chat_url,
|
|
self.server.auto_cw_cache)
|
|
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
|
|
message_json['object']['name'] = answer
|
|
if post_to_outbox(self, message_json,
|
|
project_version, nickname,
|
|
curr_session, proxy_type):
|
|
post_filename = \
|
|
locate_post(base_dir, nickname, domain, message_id)
|
|
if post_filename:
|
|
post_json_object = load_json(post_filename)
|
|
if post_json_object:
|
|
populate_replies(base_dir,
|
|
http_prefix,
|
|
domain_full,
|
|
post_json_object,
|
|
max_replies,
|
|
debug)
|
|
# record the vote
|
|
try:
|
|
with open(votes_filename, 'a+',
|
|
encoding='utf-8') as votes_file:
|
|
votes_file.write(message_id + '\n')
|
|
except OSError:
|
|
print('EX: unable to write vote ' +
|
|
votes_filename)
|
|
|
|
# ensure that the cached post is removed if it exists,
|
|
# so that it then will be recreated
|
|
cached_post_filename = \
|
|
get_cached_post_filename(base_dir,
|
|
nickname, domain,
|
|
post_json_object)
|
|
if cached_post_filename:
|
|
if os.path.isfile(cached_post_filename):
|
|
try:
|
|
os.remove(cached_post_filename)
|
|
except OSError:
|
|
print('EX: _send_reply_to_question ' +
|
|
'unable to delete ' +
|
|
cached_post_filename)
|
|
# remove from memory cache
|
|
remove_post_from_cache(post_json_object,
|
|
recent_posts_cache)
|
|
else:
|
|
print('ERROR: unable to post vote to outbox')
|
|
else:
|
|
print('ERROR: unable to create vote')
|
|
|
|
|
|
def _receive_image(self, length: int, path: str, base_dir: str,
|
|
domain: str, debug: bool) -> None:
|
|
"""Receives an image via POST
|
|
"""
|
|
if not self.outbox_authenticated:
|
|
if debug:
|
|
print('DEBUG: unauthenticated attempt to ' +
|
|
'post image to outbox')
|
|
self.send_response(403)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
path_users_section = path.split('/users/')[1]
|
|
if '/' not in path_users_section:
|
|
http_404(self, 12)
|
|
self.server.postreq_busy = False
|
|
return
|
|
self.post_from_nickname = path_users_section.split('/')[0]
|
|
accounts_dir = acct_dir(base_dir, self.post_from_nickname, domain)
|
|
if not os.path.isdir(accounts_dir):
|
|
http_404(self, 13)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
try:
|
|
media_bytes = self.rfile.read(length)
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST media_bytes ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST media_bytes socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST media_bytes rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
media_filename_base = accounts_dir + '/upload'
|
|
media_filename = \
|
|
media_filename_base + '.' + \
|
|
get_image_extension_from_mime_type(self.headers['Content-type'])
|
|
if not binary_is_image(media_filename, media_bytes):
|
|
print('WARN: _receive_image image binary is not recognized ' +
|
|
media_filename)
|
|
try:
|
|
with open(media_filename, 'wb') as av_file:
|
|
av_file.write(media_bytes)
|
|
except OSError:
|
|
print('EX: unable to write ' + media_filename)
|
|
if debug:
|
|
print('DEBUG: image saved to ' + media_filename)
|
|
self.send_response(201)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _remove_share(self, calling_domain: str, cookie: str,
|
|
authorized: bool, path: str,
|
|
base_dir: str, http_prefix: str, domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Removes a shared item
|
|
"""
|
|
users_path = path.split('/rmshare')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
remove_share_confirm_params = \
|
|
self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST remove_share_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST remove_share_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST remove_share_confirm_params ' +
|
|
'rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in remove_share_confirm_params and authorized:
|
|
remove_share_confirm_params = \
|
|
remove_share_confirm_params.replace('+', ' ').strip()
|
|
remove_share_confirm_params = \
|
|
urllib.parse.unquote_plus(remove_share_confirm_params)
|
|
share_actor = remove_share_confirm_params.split('actor=')[1]
|
|
if '&' in share_actor:
|
|
share_actor = share_actor.split('&')[0]
|
|
admin_nickname = get_config_param(base_dir, 'admin')
|
|
admin_actor = \
|
|
local_actor_url(http_prefix, admin_nickname, domain_full)
|
|
actor = origin_path_str
|
|
actor_nickname = get_nickname_from_actor(actor)
|
|
if not actor_nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if actor == share_actor or actor == admin_actor or \
|
|
is_moderator(base_dir, actor_nickname):
|
|
item_id = remove_share_confirm_params.split('itemID=')[1]
|
|
if '&' in item_id:
|
|
item_id = item_id.split('&')[0]
|
|
share_nickname = get_nickname_from_actor(share_actor)
|
|
share_domain, _ = \
|
|
get_domain_from_actor(share_actor)
|
|
if share_nickname and share_domain:
|
|
remove_shared_item2(base_dir,
|
|
share_nickname, share_domain, item_id,
|
|
'shares')
|
|
# remove shared items from the actor attachments
|
|
# https://codeberg.org/fediverse/fep/
|
|
# src/branch/main/fep/0837/fep-0837.md
|
|
actor = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
'/users/' + share_nickname
|
|
person_cache = self.server.person_cache
|
|
actor_json = get_person_from_cache(base_dir,
|
|
actor, person_cache)
|
|
if not actor_json:
|
|
actor_filename = \
|
|
acct_dir(base_dir, share_nickname,
|
|
share_domain) + '.json'
|
|
if os.path.isfile(actor_filename):
|
|
actor_json = load_json(actor_filename, 1, 1)
|
|
if actor_json:
|
|
max_shares_on_profile = \
|
|
self.server.max_shares_on_profile
|
|
if add_shares_to_actor(base_dir,
|
|
share_nickname, share_domain,
|
|
actor_json,
|
|
max_shares_on_profile):
|
|
remove_person_from_cache(base_dir, actor,
|
|
person_cache)
|
|
store_person_in_cache(base_dir, actor,
|
|
actor_json,
|
|
person_cache, True)
|
|
actor_filename = acct_dir(base_dir, share_nickname,
|
|
share_domain) + '.json'
|
|
save_json(actor_json, actor_filename)
|
|
# send profile update to followers
|
|
|
|
update_actor_json = \
|
|
get_actor_update_json(actor_json)
|
|
print('Sending actor update ' +
|
|
'after change to attached shares 2: ' +
|
|
str(update_actor_json))
|
|
post_to_outbox(self, update_actor_json,
|
|
self.server.project_version,
|
|
share_nickname,
|
|
curr_session,
|
|
proxy_type)
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str + '/tlshares',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _remove_wanted(self, calling_domain: str, cookie: str,
|
|
authorized: bool, path: str,
|
|
base_dir: str, http_prefix: str,
|
|
domain_full: str,
|
|
onion_domain: str, i2p_domain: str) -> None:
|
|
"""Removes a wanted item
|
|
"""
|
|
users_path = path.split('/rmwanted')[0]
|
|
origin_path_str = http_prefix + '://' + domain_full + users_path
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
remove_share_confirm_params = \
|
|
self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST remove_share_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST remove_share_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST remove_share_confirm_params ' +
|
|
'rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
if '&submitYes=' in remove_share_confirm_params and authorized:
|
|
remove_share_confirm_params = \
|
|
remove_share_confirm_params.replace('+', ' ').strip()
|
|
remove_share_confirm_params = \
|
|
urllib.parse.unquote_plus(remove_share_confirm_params)
|
|
share_actor = remove_share_confirm_params.split('actor=')[1]
|
|
if '&' in share_actor:
|
|
share_actor = share_actor.split('&')[0]
|
|
admin_nickname = get_config_param(base_dir, 'admin')
|
|
admin_actor = \
|
|
local_actor_url(http_prefix, admin_nickname, domain_full)
|
|
actor = origin_path_str
|
|
actor_nickname = get_nickname_from_actor(actor)
|
|
if not actor_nickname:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if actor == share_actor or actor == admin_actor or \
|
|
is_moderator(base_dir, actor_nickname):
|
|
item_id = remove_share_confirm_params.split('itemID=')[1]
|
|
if '&' in item_id:
|
|
item_id = item_id.split('&')[0]
|
|
share_nickname = get_nickname_from_actor(share_actor)
|
|
share_domain, _ = \
|
|
get_domain_from_actor(share_actor)
|
|
if share_nickname and share_domain:
|
|
remove_shared_item2(base_dir,
|
|
share_nickname, share_domain, item_id,
|
|
'wanted')
|
|
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
redirect_headers(self, origin_path_str + '/tlwanted',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _receive_remove_post(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Endpoint for removing posts after confirmation
|
|
"""
|
|
page_number = 1
|
|
users_path = path.split('/rmpost')[0]
|
|
origin_path_str = \
|
|
http_prefix + '://' + \
|
|
domain_full + users_path
|
|
|
|
length = int(self.headers['Content-length'])
|
|
|
|
try:
|
|
remove_post_confirm_params = \
|
|
self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST remove_post_confirm_params ' +
|
|
'connection was reset')
|
|
else:
|
|
print('EX: POST remove_post_confirm_params socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST remove_post_confirm_params ' +
|
|
'rfile.read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if '&submitYes=' in remove_post_confirm_params:
|
|
remove_post_confirm_params = \
|
|
urllib.parse.unquote_plus(remove_post_confirm_params)
|
|
if 'messageId=' in remove_post_confirm_params:
|
|
remove_message_id = \
|
|
remove_post_confirm_params.split('messageId=')[1]
|
|
elif 'eventid=' in remove_post_confirm_params:
|
|
remove_message_id = \
|
|
remove_post_confirm_params.split('eventid=')[1]
|
|
else:
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
if '&' in remove_message_id:
|
|
remove_message_id = remove_message_id.split('&')[0]
|
|
print('remove_message_id: ' + remove_message_id)
|
|
if 'pageNumber=' in remove_post_confirm_params:
|
|
page_number_str = \
|
|
remove_post_confirm_params.split('pageNumber=')[1]
|
|
if '&' in page_number_str:
|
|
page_number_str = page_number_str.split('&')[0]
|
|
if len(page_number_str) > 5:
|
|
page_number_str = "1"
|
|
if page_number_str.isdigit():
|
|
page_number = int(page_number_str)
|
|
year_str = None
|
|
if 'year=' in remove_post_confirm_params:
|
|
year_str = remove_post_confirm_params.split('year=')[1]
|
|
if '&' in year_str:
|
|
year_str = year_str.split('&')[0]
|
|
month_str = None
|
|
if 'month=' in remove_post_confirm_params:
|
|
month_str = remove_post_confirm_params.split('month=')[1]
|
|
if '&' in month_str:
|
|
month_str = month_str.split('&')[0]
|
|
if '/statuses/' in remove_message_id:
|
|
remove_post_actor = remove_message_id.split('/statuses/')[0]
|
|
print('origin_path_str: ' + origin_path_str)
|
|
print('remove_post_actor: ' + remove_post_actor)
|
|
if origin_path_str in remove_post_actor:
|
|
to_list = [
|
|
'https://www.w3.org/ns/activitystreams#Public',
|
|
remove_post_actor
|
|
]
|
|
delete_json = {
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
'actor': remove_post_actor,
|
|
'object': remove_message_id,
|
|
'to': to_list,
|
|
'cc': [remove_post_actor + '/followers'],
|
|
'type': 'Delete'
|
|
}
|
|
self.post_to_nickname = \
|
|
get_nickname_from_actor(remove_post_actor)
|
|
if self.post_to_nickname:
|
|
if month_str and year_str:
|
|
if len(month_str) <= 3 and \
|
|
len(year_str) <= 3 and \
|
|
month_str.isdigit() and \
|
|
year_str.isdigit():
|
|
year_int = int(year_str)
|
|
month_int = int(month_str)
|
|
remove_calendar_event(base_dir,
|
|
self.post_to_nickname,
|
|
domain, year_int,
|
|
month_int,
|
|
remove_message_id)
|
|
post_to_outbox_thread(self, delete_json,
|
|
curr_session, proxy_type)
|
|
if calling_domain.endswith('.onion') and onion_domain:
|
|
origin_path_str = 'http://' + onion_domain + users_path
|
|
elif (calling_domain.endswith('.i2p') and i2p_domain):
|
|
origin_path_str = 'http://' + i2p_domain + users_path
|
|
if page_number == 1:
|
|
redirect_headers(self, origin_path_str + '/outbox', cookie,
|
|
calling_domain)
|
|
else:
|
|
page_number_str = str(page_number)
|
|
actor_path_str = \
|
|
origin_path_str + '/outbox?page=' + page_number_str
|
|
redirect_headers(self, actor_path_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _links_update(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, debug: bool,
|
|
default_timeline: str,
|
|
allow_local_network_access: bool) -> None:
|
|
"""Updates the left links column of the timeline
|
|
"""
|
|
users_path = path.replace('/linksdata', '')
|
|
users_path = users_path.replace('/editlinks', '')
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.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 = None
|
|
if nickname:
|
|
editor = is_editor(base_dir, nickname)
|
|
if not nickname or not editor:
|
|
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)
|
|
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 > self.server.max_post_length:
|
|
print('Maximum links data length exceeded ' + str(length))
|
|
redirect_headers(self, actor_str, cookie, calling_domain)
|
|
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
|
|
|
|
links_filename = base_dir + '/accounts/links.txt'
|
|
about_filename = base_dir + '/accounts/about.md'
|
|
tos_filename = base_dir + '/accounts/tos.md'
|
|
specification_filename = base_dir + '/accounts/activitypub.md'
|
|
|
|
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('editedLinks'):
|
|
links_str = fields['editedLinks']
|
|
if fields.get('newColLink'):
|
|
if links_str:
|
|
if not links_str.endswith('\n'):
|
|
links_str += '\n'
|
|
links_str += fields['newColLink'] + '\n'
|
|
try:
|
|
with open(links_filename, 'w+',
|
|
encoding='utf-8') as linksfile:
|
|
linksfile.write(links_str)
|
|
except OSError:
|
|
print('EX: _links_update unable to write ' +
|
|
links_filename)
|
|
else:
|
|
if fields.get('newColLink'):
|
|
# the text area is empty but there is a new link added
|
|
links_str = fields['newColLink'] + '\n'
|
|
try:
|
|
with open(links_filename, 'w+',
|
|
encoding='utf-8') as linksfile:
|
|
linksfile.write(links_str)
|
|
except OSError:
|
|
print('EX: _links_update unable to write ' +
|
|
links_filename)
|
|
else:
|
|
if os.path.isfile(links_filename):
|
|
try:
|
|
os.remove(links_filename)
|
|
except OSError:
|
|
print('EX: _links_update unable to delete ' +
|
|
links_filename)
|
|
|
|
admin_nickname = \
|
|
get_config_param(base_dir, 'admin')
|
|
if nickname == admin_nickname:
|
|
if fields.get('editedAbout'):
|
|
about_str = fields['editedAbout']
|
|
if not dangerous_markup(about_str,
|
|
allow_local_network_access, []):
|
|
try:
|
|
with open(about_filename, 'w+',
|
|
encoding='utf-8') as aboutfile:
|
|
aboutfile.write(about_str)
|
|
except OSError:
|
|
print('EX: unable to write about ' +
|
|
about_filename)
|
|
else:
|
|
if os.path.isfile(about_filename):
|
|
try:
|
|
os.remove(about_filename)
|
|
except OSError:
|
|
print('EX: _links_update unable to delete ' +
|
|
about_filename)
|
|
|
|
if fields.get('editedTOS'):
|
|
tos_str = fields['editedTOS']
|
|
if not dangerous_markup(tos_str,
|
|
allow_local_network_access, []):
|
|
try:
|
|
with open(tos_filename, 'w+',
|
|
encoding='utf-8') as tosfile:
|
|
tosfile.write(tos_str)
|
|
except OSError:
|
|
print('EX: unable to write TOS ' + tos_filename)
|
|
else:
|
|
if os.path.isfile(tos_filename):
|
|
try:
|
|
os.remove(tos_filename)
|
|
except OSError:
|
|
print('EX: _links_update unable to delete ' +
|
|
tos_filename)
|
|
|
|
if fields.get('editedSpecification'):
|
|
specification_str = fields['editedSpecification']
|
|
try:
|
|
with open(specification_filename, 'w+',
|
|
encoding='utf-8') as specificationfile:
|
|
specificationfile.write(specification_str)
|
|
except OSError:
|
|
print('EX: unable to write specification ' +
|
|
specification_filename)
|
|
else:
|
|
if os.path.isfile(specification_filename):
|
|
try:
|
|
os.remove(specification_filename)
|
|
except OSError:
|
|
print('EX: _links_update unable to delete ' +
|
|
specification_filename)
|
|
|
|
# redirect back to the default timeline
|
|
redirect_headers(self, actor_str + '/' + default_timeline,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _newswire_update(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str,
|
|
domain: str, debug: bool,
|
|
default_timeline: str) -> 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,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.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)
|
|
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 > self.server.max_post_length:
|
|
print('Maximum newswire data length exceeded ' + str(length))
|
|
redirect_headers(self, actor_str, cookie, calling_domain)
|
|
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)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _citations_update(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str,
|
|
domain: str, debug: bool,
|
|
newswire: {}) -> 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,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.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 > self.server.max_post_length:
|
|
print('Maximum citations data length exceeded ' + str(length))
|
|
redirect_headers(self, actor_str, cookie, calling_domain)
|
|
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)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _news_post_edit(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str,
|
|
domain: str, debug: bool) -> 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,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.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 self.server.news_instance:
|
|
redirect_headers(self, actor_str + '/tlfeatures',
|
|
cookie, calling_domain)
|
|
else:
|
|
redirect_headers(self, actor_str + '/tlnews',
|
|
cookie, calling_domain)
|
|
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 > self.server.max_post_length:
|
|
print('Maximum news data length exceeded ' + str(length))
|
|
if self.server.news_instance:
|
|
redirect_headers(self, actor_str + '/tlfeatures',
|
|
cookie, calling_domain)
|
|
else:
|
|
redirect_headers(self, actor_str + '/tlnews',
|
|
cookie, calling_domain)
|
|
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[self.server.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 self.server.newswire.get(str(published_date)):
|
|
self.server.newswire[published_date][0] = \
|
|
news_post_title
|
|
self.server.newswire[published_date][4] = \
|
|
first_paragraph_from_string(news_post_content)
|
|
# save newswire
|
|
newswire_state_filename = \
|
|
base_dir + '/accounts/.newswirestate.json'
|
|
try:
|
|
save_json(self.server.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,
|
|
self.server.recent_posts_cache,
|
|
news_id)
|
|
|
|
# save the news post
|
|
save_json(post_json_object, post_filename)
|
|
|
|
# redirect back to the default timeline
|
|
if self.server.news_instance:
|
|
redirect_headers(self, actor_str + '/tlfeatures',
|
|
cookie, calling_domain)
|
|
else:
|
|
redirect_headers(self, actor_str + '/tlnews',
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _profile_edit(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str, http_prefix: str,
|
|
domain: str, domain_full: str,
|
|
onion_domain: str, i2p_domain: str,
|
|
debug: bool, allow_local_network_access: bool,
|
|
system_language: str,
|
|
content_license_url: str,
|
|
curr_session, proxy_type: str) -> None:
|
|
"""Updates your user profile after editing via the Edit button
|
|
on the profile screen
|
|
"""
|
|
users_path = path.replace('/profiledata', '')
|
|
users_path = users_path.replace('/editprofile', '')
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.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)
|
|
if not nickname:
|
|
print('WARN: nickname not found in ' + actor_str)
|
|
redirect_headers(self, actor_str, cookie, calling_domain)
|
|
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 > self.server.max_post_length:
|
|
print('Maximum profile data length exceeded ' +
|
|
str(length))
|
|
redirect_headers(self, actor_str, cookie, calling_domain)
|
|
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
|
|
|
|
admin_nickname = get_config_param(self.server.base_dir, 'admin')
|
|
|
|
if not boundary:
|
|
if b'--LYNX' in post_bytes:
|
|
boundary = '--LYNX'
|
|
|
|
if debug:
|
|
print('post_bytes: ' + str(post_bytes))
|
|
|
|
if boundary:
|
|
# get the various avatar, banner and background images
|
|
actor_changed = True
|
|
send_move_activity = False
|
|
profile_media_types = (
|
|
'avatar', 'image',
|
|
'banner', 'search_banner',
|
|
'instanceLogo',
|
|
'left_col_image', 'right_col_image',
|
|
'importFollows',
|
|
'importTheme'
|
|
)
|
|
profile_media_types_uploaded = {}
|
|
for m_type in profile_media_types:
|
|
# some images can only be changed by the admin
|
|
if m_type == 'instanceLogo':
|
|
if nickname != admin_nickname:
|
|
print('WARN: only the admin can change ' +
|
|
'instance logo')
|
|
continue
|
|
|
|
if debug:
|
|
print('DEBUG: profile update extracting ' + m_type +
|
|
' image, zip, csv or font from POST')
|
|
media_bytes, post_bytes = \
|
|
extract_media_in_form_post(post_bytes, boundary, m_type)
|
|
if media_bytes:
|
|
if debug:
|
|
print('DEBUG: profile update ' + m_type +
|
|
' image, zip, csv or font was found. ' +
|
|
str(len(media_bytes)) + ' bytes')
|
|
else:
|
|
if debug:
|
|
print('DEBUG: profile update, no ' + m_type +
|
|
' image, zip, csv or font was found in POST')
|
|
continue
|
|
|
|
# Note: a .temp extension is used here so that at no
|
|
# time is an image with metadata publicly exposed,
|
|
# even for a few mS
|
|
if m_type == 'instanceLogo':
|
|
filename_base = \
|
|
base_dir + '/accounts/login.temp'
|
|
elif m_type == 'importTheme':
|
|
if not os.path.isdir(base_dir + '/imports'):
|
|
os.mkdir(base_dir + '/imports')
|
|
filename_base = \
|
|
base_dir + '/imports/newtheme.zip'
|
|
if os.path.isfile(filename_base):
|
|
try:
|
|
os.remove(filename_base)
|
|
except OSError:
|
|
print('EX: _profile_edit unable to delete ' +
|
|
filename_base)
|
|
elif m_type == 'importFollows':
|
|
filename_base = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/import_following.csv'
|
|
else:
|
|
filename_base = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/' + m_type + '.temp'
|
|
|
|
filename, _ = \
|
|
save_media_in_form_post(media_bytes, debug,
|
|
filename_base)
|
|
if filename:
|
|
print('Profile update POST ' + m_type +
|
|
' media, zip, csv or font filename is ' + filename)
|
|
else:
|
|
print('Profile update, no ' + m_type +
|
|
' media, zip, csv or font filename in POST')
|
|
continue
|
|
|
|
if m_type == 'importFollows':
|
|
if os.path.isfile(filename_base):
|
|
print(nickname + ' imported follows csv')
|
|
else:
|
|
print('WARN: failed to import follows from csv for ' +
|
|
nickname)
|
|
continue
|
|
|
|
if m_type == 'importTheme':
|
|
if nickname == admin_nickname or \
|
|
is_artist(base_dir, nickname):
|
|
if import_theme(base_dir, filename):
|
|
print(nickname + ' uploaded a theme')
|
|
else:
|
|
print('Only admin or artist can import a theme')
|
|
continue
|
|
|
|
post_image_filename = filename.replace('.temp', '')
|
|
if debug:
|
|
print('DEBUG: POST ' + m_type +
|
|
' media removing metadata')
|
|
# remove existing etag
|
|
if os.path.isfile(post_image_filename + '.etag'):
|
|
try:
|
|
os.remove(post_image_filename + '.etag')
|
|
except OSError:
|
|
print('EX: _profile_edit unable to delete ' +
|
|
post_image_filename + '.etag')
|
|
|
|
city = get_spoofed_city(self.server.city,
|
|
base_dir, nickname, domain)
|
|
|
|
if self.server.low_bandwidth:
|
|
convert_image_to_low_bandwidth(filename)
|
|
process_meta_data(base_dir, nickname, domain,
|
|
filename, post_image_filename, city,
|
|
content_license_url)
|
|
if os.path.isfile(post_image_filename):
|
|
print('profile update POST ' + m_type +
|
|
' image, zip or font saved to ' +
|
|
post_image_filename)
|
|
if m_type != 'instanceLogo':
|
|
last_part_of_image_filename = \
|
|
post_image_filename.split('/')[-1]
|
|
profile_media_types_uploaded[m_type] = \
|
|
last_part_of_image_filename
|
|
actor_changed = True
|
|
else:
|
|
print('ERROR: profile update POST ' + m_type +
|
|
' image or font could not be saved to ' +
|
|
post_image_filename)
|
|
|
|
post_bytes_str = post_bytes.decode('utf-8')
|
|
redirect_path = ''
|
|
check_name_and_bio = False
|
|
on_final_welcome_screen = False
|
|
if 'name="previewAvatar"' in post_bytes_str:
|
|
redirect_path = '/welcome_profile'
|
|
elif 'name="initialWelcomeScreen"' in post_bytes_str:
|
|
redirect_path = '/welcome'
|
|
elif 'name="finalWelcomeScreen"' in post_bytes_str:
|
|
check_name_and_bio = True
|
|
redirect_path = '/welcome_final'
|
|
elif 'name="welcomeCompleteButton"' in post_bytes_str:
|
|
redirect_path = '/' + self.server.default_timeline
|
|
welcome_screen_is_complete(self.server.base_dir, nickname,
|
|
self.server.domain)
|
|
on_final_welcome_screen = True
|
|
elif 'name="submitExportTheme"' in post_bytes_str:
|
|
print('submitExportTheme')
|
|
theme_download_path = actor_str
|
|
if export_theme(self.server.base_dir,
|
|
self.server.theme_name):
|
|
theme_download_path += \
|
|
'/exports/' + self.server.theme_name + '.zip'
|
|
print('submitExportTheme path=' + theme_download_path)
|
|
redirect_headers(self, theme_download_path,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
elif 'name="submitExportBlocks"' in post_bytes_str:
|
|
print('submitExportBlocks')
|
|
blocks_download_path = actor_str + '/exports/blocks.csv'
|
|
print('submitExportBlocks path=' + blocks_download_path)
|
|
redirect_headers(self, blocks_download_path,
|
|
cookie, calling_domain)
|
|
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)
|
|
if debug:
|
|
if fields:
|
|
print('DEBUG: profile update text ' +
|
|
'field extracted from POST ' + str(fields))
|
|
else:
|
|
print('WARN: profile update, no text ' +
|
|
'fields could be extracted from POST')
|
|
|
|
# load the json for the actor for this user
|
|
actor_filename = \
|
|
acct_dir(base_dir, nickname, domain) + '.json'
|
|
if os.path.isfile(actor_filename):
|
|
actor_json = load_json(actor_filename)
|
|
if actor_json:
|
|
if not actor_json.get('discoverable'):
|
|
# discoverable in profile directory
|
|
# which isn't implemented in Epicyon
|
|
actor_json['discoverable'] = True
|
|
actor_changed = True
|
|
if actor_json.get('capabilityAcquisitionEndpoint'):
|
|
del actor_json['capabilityAcquisitionEndpoint']
|
|
actor_changed = True
|
|
# update the avatar/image url file extension
|
|
uploads = profile_media_types_uploaded.items()
|
|
for m_type, last_part in uploads:
|
|
rep_str = '/' + last_part
|
|
if m_type == 'avatar':
|
|
url_str = \
|
|
get_url_from_post(actor_json['icon']['url'])
|
|
actor_url = remove_html(url_str)
|
|
last_part_of_url = actor_url.split('/')[-1]
|
|
srch_str = '/' + last_part_of_url
|
|
actor_url = actor_url.replace(srch_str, rep_str)
|
|
actor_json['icon']['url'] = actor_url
|
|
print('actor_url: ' + actor_url)
|
|
if '.' in actor_url:
|
|
img_ext = actor_url.split('.')[-1]
|
|
if img_ext == 'jpg':
|
|
img_ext = 'jpeg'
|
|
actor_json['icon']['mediaType'] = \
|
|
'image/' + img_ext
|
|
elif m_type == 'image':
|
|
url_str = \
|
|
get_url_from_post(actor_json['image']['url'])
|
|
im_url = \
|
|
remove_html(url_str)
|
|
last_part_of_url = im_url.split('/')[-1]
|
|
srch_str = '/' + last_part_of_url
|
|
actor_json['image']['url'] = \
|
|
im_url.replace(srch_str, rep_str)
|
|
if '.' in im_url:
|
|
img_ext = im_url.split('.')[-1]
|
|
if img_ext == 'jpg':
|
|
img_ext = 'jpeg'
|
|
actor_json['image']['mediaType'] = \
|
|
'image/' + img_ext
|
|
|
|
# set skill levels
|
|
skill_ctr = 1
|
|
actor_skills_ctr = no_of_actor_skills(actor_json)
|
|
while skill_ctr < 10:
|
|
skill_name = \
|
|
fields.get('skillName' + str(skill_ctr))
|
|
if not skill_name:
|
|
skill_ctr += 1
|
|
continue
|
|
if is_filtered(base_dir, nickname, domain, skill_name,
|
|
system_language):
|
|
skill_ctr += 1
|
|
continue
|
|
skill_value = \
|
|
fields.get('skillValue' + str(skill_ctr))
|
|
if not skill_value:
|
|
skill_ctr += 1
|
|
continue
|
|
if not actor_has_skill(actor_json, skill_name):
|
|
actor_changed = True
|
|
else:
|
|
if actor_skill_value(actor_json, skill_name) != \
|
|
int(skill_value):
|
|
actor_changed = True
|
|
set_actor_skill_level(actor_json,
|
|
skill_name, int(skill_value))
|
|
skills_str = self.server.translate['Skills']
|
|
skills_str = skills_str.lower()
|
|
set_hashtag_category(base_dir, skill_name,
|
|
skills_str, False)
|
|
skill_ctr += 1
|
|
if no_of_actor_skills(actor_json) != \
|
|
actor_skills_ctr:
|
|
actor_changed = True
|
|
|
|
# change password
|
|
if fields.get('password') and \
|
|
fields.get('passwordconfirm'):
|
|
fields['password'] = \
|
|
remove_eol(fields['password']).strip()
|
|
fields['passwordconfirm'] = \
|
|
remove_eol(fields['passwordconfirm']).strip()
|
|
if valid_password(fields['password']) and \
|
|
fields['password'] == fields['passwordconfirm']:
|
|
# set password
|
|
store_basic_credentials(base_dir, nickname,
|
|
fields['password'])
|
|
|
|
# reply interval in hours
|
|
if fields.get('replyhours'):
|
|
if fields['replyhours'].isdigit():
|
|
set_reply_interval_hours(base_dir,
|
|
nickname, domain,
|
|
fields['replyhours'])
|
|
|
|
# change city
|
|
if fields.get('cityDropdown'):
|
|
city_filename = \
|
|
acct_dir(base_dir, nickname, domain) + '/city.txt'
|
|
try:
|
|
with open(city_filename, 'w+',
|
|
encoding='utf-8') as fp_city:
|
|
fp_city.write(fields['cityDropdown'])
|
|
except OSError:
|
|
print('EX: unable to write city ' + city_filename)
|
|
|
|
# change displayed name
|
|
if fields.get('displayNickname'):
|
|
if fields['displayNickname'] != actor_json['name']:
|
|
display_name = \
|
|
remove_html(fields['displayNickname'])
|
|
if not is_filtered(base_dir,
|
|
nickname, domain,
|
|
display_name,
|
|
system_language):
|
|
actor_json['name'] = display_name
|
|
else:
|
|
actor_json['name'] = nickname
|
|
if check_name_and_bio:
|
|
redirect_path = '/welcome_profile'
|
|
actor_changed = True
|
|
else:
|
|
if check_name_and_bio:
|
|
redirect_path = '/welcome_profile'
|
|
|
|
# change the theme from edit profile screen
|
|
if nickname == admin_nickname or \
|
|
is_artist(base_dir, nickname):
|
|
if fields.get('themeDropdown'):
|
|
if self.server.theme_name != \
|
|
fields['themeDropdown']:
|
|
self.server.theme_name = \
|
|
fields['themeDropdown']
|
|
set_theme(base_dir, self.server.theme_name,
|
|
domain, allow_local_network_access,
|
|
system_language,
|
|
self.server.dyslexic_font, True)
|
|
self.server.text_mode_banner = \
|
|
get_text_mode_banner(self.server.base_dir)
|
|
self.server.iconsCache = {}
|
|
self.server.fontsCache = {}
|
|
self.server.css_cache = {}
|
|
self.server.show_publish_as_icon = \
|
|
get_config_param(self.server.base_dir,
|
|
'showPublishAsIcon')
|
|
self.server.full_width_tl_button_header = \
|
|
get_config_param(self.server.base_dir,
|
|
'fullWidthTlButtonHeader')
|
|
self.server.icons_as_buttons = \
|
|
get_config_param(self.server.base_dir,
|
|
'iconsAsButtons')
|
|
self.server.rss_icon_at_top = \
|
|
get_config_param(self.server.base_dir,
|
|
'rssIconAtTop')
|
|
self.server.publish_button_at_top = \
|
|
get_config_param(self.server.base_dir,
|
|
'publishButtonAtTop')
|
|
set_news_avatar(base_dir,
|
|
fields['themeDropdown'],
|
|
http_prefix,
|
|
domain, domain_full)
|
|
|
|
if nickname == admin_nickname:
|
|
# change media instance status
|
|
if fields.get('mediaInstance'):
|
|
self.server.media_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
if fields['mediaInstance'] == 'on':
|
|
self.server.media_instance = True
|
|
self.server.blogs_instance = False
|
|
self.server.news_instance = False
|
|
self.server.default_timeline = 'tlmedia'
|
|
set_config_param(base_dir, "mediaInstance",
|
|
self.server.media_instance)
|
|
set_config_param(base_dir, "blogsInstance",
|
|
self.server.blogs_instance)
|
|
set_config_param(base_dir, "newsInstance",
|
|
self.server.news_instance)
|
|
else:
|
|
if self.server.media_instance:
|
|
self.server.media_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
set_config_param(base_dir, "mediaInstance",
|
|
self.server.media_instance)
|
|
|
|
# is this a news theme?
|
|
if is_news_theme_name(self.server.base_dir,
|
|
self.server.theme_name):
|
|
fields['newsInstance'] = 'on'
|
|
|
|
# change news instance status
|
|
if fields.get('newsInstance'):
|
|
self.server.news_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
if fields['newsInstance'] == 'on':
|
|
self.server.news_instance = True
|
|
self.server.blogs_instance = False
|
|
self.server.media_instance = False
|
|
self.server.default_timeline = 'tlfeatures'
|
|
set_config_param(base_dir, "mediaInstance",
|
|
self.server.media_instance)
|
|
set_config_param(base_dir, "blogsInstance",
|
|
self.server.blogs_instance)
|
|
set_config_param(base_dir, "newsInstance",
|
|
self.server.news_instance)
|
|
else:
|
|
if self.server.news_instance:
|
|
self.server.news_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
set_config_param(base_dir, "newsInstance",
|
|
self.server.media_instance)
|
|
|
|
# change blog instance status
|
|
if fields.get('blogsInstance'):
|
|
self.server.blogs_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
if fields['blogsInstance'] == 'on':
|
|
self.server.blogs_instance = True
|
|
self.server.media_instance = False
|
|
self.server.news_instance = False
|
|
self.server.default_timeline = 'tlblogs'
|
|
set_config_param(base_dir, "blogsInstance",
|
|
self.server.blogs_instance)
|
|
set_config_param(base_dir, "mediaInstance",
|
|
self.server.media_instance)
|
|
set_config_param(base_dir, "newsInstance",
|
|
self.server.news_instance)
|
|
else:
|
|
if self.server.blogs_instance:
|
|
self.server.blogs_instance = False
|
|
self.server.default_timeline = 'inbox'
|
|
set_config_param(base_dir, "blogsInstance",
|
|
self.server.blogs_instance)
|
|
|
|
# change instance title
|
|
if fields.get('instanceTitle'):
|
|
curr_instance_title = \
|
|
get_config_param(base_dir, 'instanceTitle')
|
|
if fields['instanceTitle'] != curr_instance_title:
|
|
set_config_param(base_dir, 'instanceTitle',
|
|
fields['instanceTitle'])
|
|
|
|
# change YouTube alternate domain
|
|
if fields.get('ytdomain'):
|
|
curr_yt_domain = self.server.yt_replace_domain
|
|
if fields['ytdomain'] != curr_yt_domain:
|
|
new_yt_domain = fields['ytdomain']
|
|
if '://' in new_yt_domain:
|
|
new_yt_domain = \
|
|
new_yt_domain.split('://')[1]
|
|
if '/' in new_yt_domain:
|
|
new_yt_domain = new_yt_domain.split('/')[0]
|
|
if '.' in new_yt_domain:
|
|
set_config_param(base_dir, 'youtubedomain',
|
|
new_yt_domain)
|
|
self.server.yt_replace_domain = \
|
|
new_yt_domain
|
|
else:
|
|
set_config_param(base_dir, 'youtubedomain', '')
|
|
self.server.yt_replace_domain = None
|
|
|
|
# change twitter alternate domain
|
|
if fields.get('twitterdomain'):
|
|
curr_twitter_domain = \
|
|
self.server.twitter_replacement_domain
|
|
if fields['twitterdomain'] != curr_twitter_domain:
|
|
new_twitter_domain = fields['twitterdomain']
|
|
if '://' in new_twitter_domain:
|
|
new_twitter_domain = \
|
|
new_twitter_domain.split('://')[1]
|
|
if '/' in new_twitter_domain:
|
|
new_twitter_domain = \
|
|
new_twitter_domain.split('/')[0]
|
|
if '.' in new_twitter_domain:
|
|
set_config_param(base_dir, 'twitterdomain',
|
|
new_twitter_domain)
|
|
self.server.twitter_replacement_domain = \
|
|
new_twitter_domain
|
|
else:
|
|
set_config_param(base_dir, 'twitterdomain', '')
|
|
self.server.twitter_replacement_domain = None
|
|
|
|
# change custom post submit button text
|
|
curr_custom_submit_text = \
|
|
get_config_param(base_dir, 'customSubmitText')
|
|
if fields.get('customSubmitText'):
|
|
if fields['customSubmitText'] != \
|
|
curr_custom_submit_text:
|
|
custom_text = fields['customSubmitText']
|
|
set_config_param(base_dir, 'customSubmitText',
|
|
custom_text)
|
|
else:
|
|
if curr_custom_submit_text:
|
|
set_config_param(base_dir, 'customSubmitText',
|
|
'')
|
|
|
|
# change registrations open status
|
|
registrations_open = False
|
|
if self.server.registration or \
|
|
get_config_param(base_dir,
|
|
"registration") == 'open':
|
|
registrations_open = True
|
|
if fields.get('regOpen'):
|
|
if fields['regOpen'] != registrations_open:
|
|
registrations_open = fields['regOpen']
|
|
set_config_param(base_dir, 'registration',
|
|
'open')
|
|
remaining = \
|
|
get_config_param(base_dir,
|
|
'registrationsRemaining')
|
|
if not remaining:
|
|
set_config_param(base_dir,
|
|
'registrationsRemaining',
|
|
10)
|
|
self.server.registration = True
|
|
else:
|
|
if registrations_open:
|
|
set_config_param(base_dir, 'registration',
|
|
'closed')
|
|
self.server.registration = False
|
|
|
|
# change public replies unlisted
|
|
pub_replies_unlisted = False
|
|
if self.server.public_replies_unlisted or \
|
|
get_config_param(base_dir,
|
|
"publicRepliesUnlisted") is True:
|
|
pub_replies_unlisted = True
|
|
if fields.get('publicRepliesUnlisted'):
|
|
if fields['publicRepliesUnlisted'] != \
|
|
pub_replies_unlisted:
|
|
pub_replies_unlisted = \
|
|
fields['publicRepliesUnlisted']
|
|
set_config_param(base_dir,
|
|
'publicRepliesUnlisted',
|
|
True)
|
|
self.server.public_replies_unlisted = \
|
|
pub_replies_unlisted
|
|
else:
|
|
if pub_replies_unlisted:
|
|
set_config_param(base_dir,
|
|
'publicRepliesUnlisted',
|
|
False)
|
|
self.server.public_replies_unlisted = False
|
|
|
|
# change registrations remaining
|
|
reg_str = "registrationsRemaining"
|
|
remaining = get_config_param(base_dir, reg_str)
|
|
if fields.get('regRemaining'):
|
|
if fields['regRemaining'] != remaining:
|
|
remaining = int(fields['regRemaining'])
|
|
if remaining < 0:
|
|
remaining = 0
|
|
elif remaining > 10:
|
|
remaining = 10
|
|
set_config_param(base_dir, reg_str,
|
|
remaining)
|
|
|
|
# libretranslate URL
|
|
curr_libretranslate_url = \
|
|
get_config_param(base_dir,
|
|
'libretranslateUrl')
|
|
if fields.get('libretranslateUrl'):
|
|
if fields['libretranslateUrl'] != \
|
|
curr_libretranslate_url:
|
|
lt_url = fields['libretranslateUrl']
|
|
if resembles_url(lt_url):
|
|
set_config_param(base_dir,
|
|
'libretranslateUrl',
|
|
lt_url)
|
|
else:
|
|
if curr_libretranslate_url:
|
|
set_config_param(base_dir,
|
|
'libretranslateUrl', '')
|
|
|
|
# libretranslate API Key
|
|
curr_libretranslate_api_key = \
|
|
get_config_param(base_dir,
|
|
'libretranslateApiKey')
|
|
if fields.get('libretranslateApiKey'):
|
|
if fields['libretranslateApiKey'] != \
|
|
curr_libretranslate_api_key:
|
|
lt_api_key = fields['libretranslateApiKey']
|
|
set_config_param(base_dir,
|
|
'libretranslateApiKey',
|
|
lt_api_key)
|
|
else:
|
|
if curr_libretranslate_api_key:
|
|
set_config_param(base_dir,
|
|
'libretranslateApiKey', '')
|
|
|
|
# change instance content license
|
|
if fields.get('contentLicenseUrl'):
|
|
if fields['contentLicenseUrl'] != \
|
|
self.server.content_license_url:
|
|
license_str = fields['contentLicenseUrl']
|
|
if '://' not in license_str:
|
|
license_str = \
|
|
license_link_from_name(license_str)
|
|
set_config_param(base_dir,
|
|
'contentLicenseUrl',
|
|
license_str)
|
|
self.server.content_license_url = \
|
|
license_str
|
|
else:
|
|
license_str = \
|
|
'https://creativecommons.org/' + \
|
|
'licenses/by-nc/4.0'
|
|
set_config_param(base_dir,
|
|
'contentLicenseUrl',
|
|
license_str)
|
|
self.server.content_license_url = license_str
|
|
|
|
# change instance short description
|
|
curr_instance_description_short = \
|
|
get_config_param(base_dir,
|
|
'instanceDescriptionShort')
|
|
if fields.get('instanceDescriptionShort'):
|
|
if fields['instanceDescriptionShort'] != \
|
|
curr_instance_description_short:
|
|
idesc = fields['instanceDescriptionShort']
|
|
set_config_param(base_dir,
|
|
'instanceDescriptionShort',
|
|
idesc)
|
|
else:
|
|
if curr_instance_description_short:
|
|
set_config_param(base_dir,
|
|
'instanceDescriptionShort',
|
|
'')
|
|
|
|
# change instance description
|
|
curr_instance_description = \
|
|
get_config_param(base_dir, 'instanceDescription')
|
|
if fields.get('instanceDescription'):
|
|
if fields['instanceDescription'] != \
|
|
curr_instance_description:
|
|
set_config_param(base_dir,
|
|
'instanceDescription',
|
|
fields['instanceDescription'])
|
|
else:
|
|
if curr_instance_description:
|
|
set_config_param(base_dir,
|
|
'instanceDescription', '')
|
|
|
|
# change memorial accounts
|
|
curr_memorial = get_memorials(base_dir)
|
|
if fields.get('memorialAccounts'):
|
|
if fields['memorialAccounts'] != \
|
|
curr_memorial:
|
|
set_memorials(base_dir, self.server.domain,
|
|
fields['memorialAccounts'])
|
|
update_memorial_flags(base_dir,
|
|
self.server.person_cache)
|
|
else:
|
|
if curr_memorial:
|
|
set_memorials(base_dir,
|
|
self.server.domain, '')
|
|
update_memorial_flags(base_dir,
|
|
self.server.person_cache)
|
|
|
|
# change email address
|
|
current_email_address = get_email_address(actor_json)
|
|
if fields.get('email'):
|
|
if fields['email'] != current_email_address:
|
|
set_email_address(actor_json, fields['email'])
|
|
actor_changed = True
|
|
else:
|
|
if current_email_address:
|
|
set_email_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change xmpp address
|
|
current_xmpp_address = get_xmpp_address(actor_json)
|
|
if fields.get('xmppAddress'):
|
|
if fields['xmppAddress'] != current_xmpp_address:
|
|
set_xmpp_address(actor_json,
|
|
fields['xmppAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_xmpp_address:
|
|
set_xmpp_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change matrix address
|
|
current_matrix_address = get_matrix_address(actor_json)
|
|
if fields.get('matrixAddress'):
|
|
if fields['matrixAddress'] != current_matrix_address:
|
|
set_matrix_address(actor_json,
|
|
fields['matrixAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_matrix_address:
|
|
set_matrix_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change SSB address
|
|
current_ssb_address = get_ssb_address(actor_json)
|
|
if fields.get('ssbAddress'):
|
|
if fields['ssbAddress'] != current_ssb_address:
|
|
set_ssb_address(actor_json,
|
|
fields['ssbAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_ssb_address:
|
|
set_ssb_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change blog address
|
|
current_blog_address = get_blog_address(actor_json)
|
|
if fields.get('blogAddress'):
|
|
if fields['blogAddress'] != current_blog_address:
|
|
set_blog_address(actor_json,
|
|
fields['blogAddress'])
|
|
actor_changed = True
|
|
site_is_verified(curr_session,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname, domain,
|
|
fields['blogAddress'],
|
|
True,
|
|
self.server.debug)
|
|
else:
|
|
if current_blog_address:
|
|
set_blog_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change Languages address
|
|
current_show_languages = get_actor_languages(actor_json)
|
|
if fields.get('showLanguages'):
|
|
if fields['showLanguages'] != current_show_languages:
|
|
set_actor_languages(actor_json,
|
|
fields['showLanguages'])
|
|
actor_changed = True
|
|
else:
|
|
if current_show_languages:
|
|
set_actor_languages(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change time zone
|
|
timezone = \
|
|
get_account_timezone(base_dir, nickname, domain)
|
|
if fields.get('timeZone'):
|
|
if fields['timeZone'] != timezone:
|
|
set_account_timezone(base_dir,
|
|
nickname, domain,
|
|
fields['timeZone'])
|
|
self.server.account_timezone[nickname] = \
|
|
fields['timeZone']
|
|
actor_changed = True
|
|
else:
|
|
if timezone:
|
|
set_account_timezone(base_dir,
|
|
nickname, domain, '')
|
|
del self.server.account_timezone[nickname]
|
|
actor_changed = True
|
|
|
|
# set post expiry period in days
|
|
post_expiry_period_days = \
|
|
get_post_expiry_days(base_dir, nickname, domain)
|
|
if fields.get('postExpiryPeriod'):
|
|
if fields['postExpiryPeriod'] != \
|
|
str(post_expiry_period_days):
|
|
post_expiry_period_days = \
|
|
fields['postExpiryPeriod']
|
|
set_post_expiry_days(base_dir, nickname, domain,
|
|
post_expiry_period_days)
|
|
actor_changed = True
|
|
else:
|
|
if post_expiry_period_days > 0:
|
|
set_post_expiry_days(base_dir, nickname, domain, 0)
|
|
actor_changed = True
|
|
|
|
# set maximum preview posts on profile screen
|
|
max_profile_posts = \
|
|
get_max_profile_posts(base_dir, nickname, domain,
|
|
20)
|
|
if fields.get('maxRecentProfilePosts'):
|
|
if fields['maxRecentProfilePosts'] != \
|
|
str(max_profile_posts):
|
|
max_profile_posts = \
|
|
fields['maxRecentProfilePosts']
|
|
set_max_profile_posts(base_dir, nickname, domain,
|
|
max_profile_posts)
|
|
else:
|
|
set_max_profile_posts(base_dir, nickname, domain,
|
|
20)
|
|
|
|
# birthday on edit profile screen
|
|
birth_date = ''
|
|
if actor_json.get('vcard:bday'):
|
|
birth_date = actor_json['vcard:bday']
|
|
if fields.get('birthDate'):
|
|
if fields['birthDate'] != birth_date:
|
|
new_birth_date = fields['birthDate']
|
|
if '-' in new_birth_date and \
|
|
len(new_birth_date.split('-')) == 3:
|
|
# set birth date
|
|
actor_json['vcard:bday'] = new_birth_date
|
|
actor_changed = True
|
|
else:
|
|
# set birth date
|
|
if birth_date:
|
|
actor_json['vcard:bday'] = ''
|
|
actor_changed = True
|
|
|
|
# change tox address
|
|
current_tox_address = get_tox_address(actor_json)
|
|
if fields.get('toxAddress'):
|
|
if fields['toxAddress'] != current_tox_address:
|
|
set_tox_address(actor_json,
|
|
fields['toxAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_tox_address:
|
|
set_tox_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change briar address
|
|
current_briar_address = get_briar_address(actor_json)
|
|
if fields.get('briarAddress'):
|
|
if fields['briarAddress'] != current_briar_address:
|
|
set_briar_address(actor_json,
|
|
fields['briarAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_briar_address:
|
|
set_briar_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change cwtch address
|
|
current_cwtch_address = get_cwtch_address(actor_json)
|
|
if fields.get('cwtchAddress'):
|
|
if fields['cwtchAddress'] != current_cwtch_address:
|
|
set_cwtch_address(actor_json,
|
|
fields['cwtchAddress'])
|
|
actor_changed = True
|
|
else:
|
|
if current_cwtch_address:
|
|
set_cwtch_address(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change ntfy url
|
|
if fields.get('ntfyUrl'):
|
|
ntfy_url_file = \
|
|
base_dir + '/accounts/' + \
|
|
nickname + '@' + domain + '/.ntfy_url'
|
|
try:
|
|
with open(ntfy_url_file, 'w+',
|
|
encoding='utf-8') as fp_ntfy:
|
|
fp_ntfy.write(fields['ntfyUrl'])
|
|
except OSError:
|
|
print('EX: unable to save ntfy url ' +
|
|
ntfy_url_file)
|
|
|
|
# change ntfy topic
|
|
if fields.get('ntfyTopic'):
|
|
ntfy_topic_file = \
|
|
base_dir + '/accounts/' + \
|
|
nickname + '@' + domain + '/.ntfy_topic'
|
|
try:
|
|
with open(ntfy_topic_file, 'w+',
|
|
encoding='utf-8') as fp_ntfy:
|
|
fp_ntfy.write(fields['ntfyTopic'])
|
|
except OSError:
|
|
print('EX: unable to save ntfy topic ' +
|
|
ntfy_topic_file)
|
|
|
|
# change Enigma public key
|
|
currentenigma_pub_key = get_enigma_pub_key(actor_json)
|
|
if fields.get('enigmapubkey'):
|
|
if fields['enigmapubkey'] != currentenigma_pub_key:
|
|
set_enigma_pub_key(actor_json,
|
|
fields['enigmapubkey'])
|
|
actor_changed = True
|
|
else:
|
|
if currentenigma_pub_key:
|
|
set_enigma_pub_key(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change PGP public key
|
|
currentpgp_pub_key = get_pgp_pub_key(actor_json)
|
|
if fields.get('pgp'):
|
|
if fields['pgp'] != currentpgp_pub_key:
|
|
set_pgp_pub_key(actor_json,
|
|
fields['pgp'])
|
|
actor_changed = True
|
|
else:
|
|
if currentpgp_pub_key:
|
|
set_pgp_pub_key(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change PGP fingerprint
|
|
currentpgp_fingerprint = get_pgp_fingerprint(actor_json)
|
|
if fields.get('openpgp'):
|
|
if fields['openpgp'] != currentpgp_fingerprint:
|
|
set_pgp_fingerprint(actor_json,
|
|
fields['openpgp'])
|
|
actor_changed = True
|
|
else:
|
|
if currentpgp_fingerprint:
|
|
set_pgp_fingerprint(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change donation link
|
|
current_donate_url = get_donation_url(actor_json)
|
|
if fields.get('donateUrl'):
|
|
if fields['donateUrl'] != current_donate_url:
|
|
set_donation_url(actor_json,
|
|
fields['donateUrl'])
|
|
actor_changed = True
|
|
else:
|
|
if current_donate_url:
|
|
set_donation_url(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# change website
|
|
current_website = \
|
|
get_website(actor_json, self.server.translate)
|
|
if fields.get('websiteUrl'):
|
|
if fields['websiteUrl'] != current_website:
|
|
set_website(actor_json,
|
|
fields['websiteUrl'],
|
|
self.server.translate)
|
|
actor_changed = True
|
|
site_is_verified(curr_session,
|
|
self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname, domain,
|
|
fields['websiteUrl'],
|
|
True,
|
|
self.server.debug)
|
|
else:
|
|
if current_website:
|
|
set_website(actor_json, '', self.server.translate)
|
|
actor_changed = True
|
|
|
|
# change gemini link
|
|
current_gemini_link = \
|
|
get_gemini_link(actor_json)
|
|
if fields.get('geminiLink'):
|
|
if fields['geminiLink'] != current_gemini_link:
|
|
set_gemini_link(actor_json,
|
|
fields['geminiLink'])
|
|
actor_changed = True
|
|
else:
|
|
if current_gemini_link:
|
|
set_gemini_link(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# account moved to new address
|
|
moved_to = ''
|
|
if actor_json.get('movedTo'):
|
|
moved_to = actor_json['movedTo']
|
|
if fields.get('movedTo'):
|
|
if fields['movedTo'] != moved_to and \
|
|
resembles_url(fields['movedTo']):
|
|
actor_json['movedTo'] = fields['movedTo']
|
|
send_move_activity = True
|
|
actor_changed = True
|
|
else:
|
|
if moved_to:
|
|
del actor_json['movedTo']
|
|
actor_changed = True
|
|
|
|
# occupation on edit profile screen
|
|
occupation_name = get_occupation_name(actor_json)
|
|
if fields.get('occupationName'):
|
|
fields['occupationName'] = \
|
|
remove_html(fields['occupationName'])
|
|
if occupation_name != \
|
|
fields['occupationName']:
|
|
set_occupation_name(actor_json,
|
|
fields['occupationName'])
|
|
actor_changed = True
|
|
else:
|
|
if occupation_name:
|
|
set_occupation_name(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# featured hashtags on edit profile screen
|
|
featured_hashtags = get_featured_hashtags(actor_json)
|
|
if fields.get('featuredHashtags'):
|
|
fields['featuredHashtags'] = \
|
|
remove_html(fields['featuredHashtags'])
|
|
if featured_hashtags != \
|
|
fields['featuredHashtags']:
|
|
set_featured_hashtags(actor_json,
|
|
fields['featuredHashtags'])
|
|
actor_changed = True
|
|
else:
|
|
if featured_hashtags:
|
|
set_featured_hashtags(actor_json, '')
|
|
actor_changed = True
|
|
|
|
# Other accounts (alsoKnownAs)
|
|
also_known_as = []
|
|
if actor_json.get('alsoKnownAs'):
|
|
also_known_as = actor_json['alsoKnownAs']
|
|
if fields.get('alsoKnownAs'):
|
|
also_known_as_str = ''
|
|
also_known_as_ctr = 0
|
|
for alt_actor in also_known_as:
|
|
if also_known_as_ctr > 0:
|
|
also_known_as_str += ', '
|
|
also_known_as_str += alt_actor
|
|
also_known_as_ctr += 1
|
|
if fields['alsoKnownAs'] != also_known_as_str and \
|
|
'://' in fields['alsoKnownAs'] and \
|
|
'@' not in fields['alsoKnownAs'] and \
|
|
'.' in fields['alsoKnownAs']:
|
|
if ';' in fields['alsoKnownAs']:
|
|
fields['alsoKnownAs'] = \
|
|
fields['alsoKnownAs'].replace(';', ',')
|
|
new_also_known_as = \
|
|
fields['alsoKnownAs'].split(',')
|
|
also_known_as = []
|
|
for alt_actor in new_also_known_as:
|
|
alt_actor = alt_actor.strip()
|
|
if resembles_url(alt_actor):
|
|
if alt_actor not in also_known_as:
|
|
also_known_as.append(alt_actor)
|
|
actor_json['alsoKnownAs'] = also_known_as
|
|
actor_changed = True
|
|
else:
|
|
if also_known_as:
|
|
del actor_json['alsoKnownAs']
|
|
actor_changed = True
|
|
|
|
# change user bio
|
|
featured_tags = get_featured_hashtags(actor_json) + ' '
|
|
actor_json['tag'] = []
|
|
if fields.get('bio'):
|
|
if fields['bio'] != actor_json['summary']:
|
|
bio_str = remove_html(fields['bio'])
|
|
if not is_filtered(base_dir,
|
|
nickname, domain, bio_str,
|
|
system_language):
|
|
actor_tags = {}
|
|
actor_json['summary'] = \
|
|
add_html_tags(base_dir,
|
|
http_prefix,
|
|
nickname,
|
|
domain_full,
|
|
bio_str, [], actor_tags,
|
|
self.server.translate)
|
|
if actor_tags:
|
|
for _, tag in actor_tags.items():
|
|
if tag['name'] + ' ' in featured_tags:
|
|
continue
|
|
actor_json['tag'].append(tag)
|
|
actor_changed = True
|
|
else:
|
|
if check_name_and_bio:
|
|
redirect_path = '/welcome_profile'
|
|
else:
|
|
if check_name_and_bio:
|
|
redirect_path = '/welcome_profile'
|
|
set_featured_hashtags(actor_json, featured_tags, True)
|
|
|
|
admin_nickname = \
|
|
get_config_param(base_dir, 'admin')
|
|
|
|
if admin_nickname:
|
|
# whether to require jsonld signatures
|
|
# on all incoming posts
|
|
if path.startswith('/users/' +
|
|
admin_nickname + '/'):
|
|
show_node_info_accounts = False
|
|
if fields.get('showNodeInfoAccounts'):
|
|
if fields['showNodeInfoAccounts'] == 'on':
|
|
show_node_info_accounts = True
|
|
self.server.show_node_info_accounts = \
|
|
show_node_info_accounts
|
|
set_config_param(base_dir,
|
|
"showNodeInfoAccounts",
|
|
show_node_info_accounts)
|
|
|
|
show_node_info_version = False
|
|
if fields.get('showNodeInfoVersion'):
|
|
if fields['showNodeInfoVersion'] == 'on':
|
|
show_node_info_version = True
|
|
self.server.show_node_info_version = \
|
|
show_node_info_version
|
|
set_config_param(base_dir,
|
|
"showNodeInfoVersion",
|
|
show_node_info_version)
|
|
|
|
verify_all_signatures = False
|
|
if fields.get('verifyallsignatures'):
|
|
if fields['verifyallsignatures'] == 'on':
|
|
verify_all_signatures = True
|
|
self.server.verify_all_signatures = \
|
|
verify_all_signatures
|
|
set_config_param(base_dir, "verifyAllSignatures",
|
|
verify_all_signatures)
|
|
|
|
broch_mode = False
|
|
if fields.get('brochMode'):
|
|
if fields['brochMode'] == 'on':
|
|
broch_mode = True
|
|
curr_broch_mode = \
|
|
get_config_param(base_dir, "brochMode")
|
|
if broch_mode != curr_broch_mode:
|
|
set_broch_mode(self.server.base_dir,
|
|
self.server.domain_full,
|
|
broch_mode)
|
|
set_config_param(base_dir, 'brochMode',
|
|
broch_mode)
|
|
|
|
# shared item federation domains
|
|
si_domain_updated = False
|
|
fed_domains_variable = \
|
|
"sharedItemsFederatedDomains"
|
|
fed_domains_str = \
|
|
get_config_param(base_dir,
|
|
fed_domains_variable)
|
|
if not fed_domains_str:
|
|
fed_domains_str = ''
|
|
shared_items_form_str = ''
|
|
if fields.get('shareDomainList'):
|
|
shared_it_list = \
|
|
fed_domains_str.split(',')
|
|
for shared_federated_domain in shared_it_list:
|
|
shared_items_form_str += \
|
|
shared_federated_domain.strip() + '\n'
|
|
|
|
share_domain_list = fields['shareDomainList']
|
|
if share_domain_list != \
|
|
shared_items_form_str:
|
|
shared_items_form_str2 = \
|
|
share_domain_list.replace('\n', ',')
|
|
shared_items_field = \
|
|
"sharedItemsFederatedDomains"
|
|
set_config_param(base_dir,
|
|
shared_items_field,
|
|
shared_items_form_str2)
|
|
si_domain_updated = True
|
|
else:
|
|
if fed_domains_str:
|
|
shared_items_field = \
|
|
"sharedItemsFederatedDomains"
|
|
set_config_param(base_dir,
|
|
shared_items_field,
|
|
'')
|
|
si_domain_updated = True
|
|
if si_domain_updated:
|
|
si_domains = shared_items_form_str.split('\n')
|
|
si_tokens = \
|
|
self.server.shared_item_federation_tokens
|
|
self.server.shared_items_federated_domains = \
|
|
si_domains
|
|
domain_full = self.server.domain_full
|
|
base_dir = \
|
|
self.server.base_dir
|
|
self.server.shared_item_federation_tokens = \
|
|
merge_shared_item_tokens(base_dir,
|
|
domain_full,
|
|
si_domains,
|
|
si_tokens)
|
|
|
|
# change moderators list
|
|
set_roles_from_list(base_dir, domain, admin_nickname,
|
|
'moderators', 'moderator', fields,
|
|
path, 'moderators.txt')
|
|
|
|
# change site editors list
|
|
set_roles_from_list(base_dir, domain, admin_nickname,
|
|
'editors', 'editor', fields,
|
|
path, 'editors.txt')
|
|
|
|
# change site devops list
|
|
set_roles_from_list(base_dir, domain, admin_nickname,
|
|
'devopslist', 'devops', fields,
|
|
path, 'devops.txt')
|
|
|
|
# change site counselors list
|
|
set_roles_from_list(base_dir, domain, admin_nickname,
|
|
'counselors', 'counselor', fields,
|
|
path, 'counselors.txt')
|
|
|
|
# change site artists list
|
|
set_roles_from_list(base_dir, domain, admin_nickname,
|
|
'artists', 'artist', fields,
|
|
path, 'artists.txt')
|
|
|
|
# remove scheduled posts
|
|
if fields.get('removeScheduledPosts'):
|
|
if fields['removeScheduledPosts'] == 'on':
|
|
remove_scheduled_posts(base_dir,
|
|
nickname, domain)
|
|
|
|
# approve followers
|
|
if on_final_welcome_screen:
|
|
# Default setting created via the welcome screen
|
|
actor_json['manuallyApprovesFollowers'] = True
|
|
actor_changed = True
|
|
else:
|
|
approve_followers = False
|
|
if fields.get('approveFollowers'):
|
|
if fields['approveFollowers'] == 'on':
|
|
approve_followers = True
|
|
if approve_followers != \
|
|
actor_json['manuallyApprovesFollowers']:
|
|
actor_json['manuallyApprovesFollowers'] = \
|
|
approve_followers
|
|
actor_changed = True
|
|
|
|
# reject spam actors
|
|
reject_spam_actors = False
|
|
if fields.get('rejectSpamActors'):
|
|
if fields['rejectSpamActors'] == 'on':
|
|
reject_spam_actors = True
|
|
curr_reject_spam_actors = False
|
|
actor_spam_filter_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.reject_spam_actors'
|
|
if os.path.isfile(actor_spam_filter_filename):
|
|
curr_reject_spam_actors = True
|
|
if reject_spam_actors != curr_reject_spam_actors:
|
|
if reject_spam_actors:
|
|
try:
|
|
with open(actor_spam_filter_filename, 'w+',
|
|
encoding='utf-8') as fp_spam:
|
|
fp_spam.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write reject spam actors')
|
|
else:
|
|
try:
|
|
os.remove(actor_spam_filter_filename)
|
|
except OSError:
|
|
print('EX: ' +
|
|
'unable to remove reject spam actors')
|
|
|
|
# keep DMs during post expiry
|
|
expire_keep_dms = False
|
|
if fields.get('expiryKeepDMs'):
|
|
if fields['expiryKeepDMs'] == 'on':
|
|
expire_keep_dms = True
|
|
curr_keep_dms = \
|
|
get_post_expiry_keep_dms(base_dir, nickname, domain)
|
|
if curr_keep_dms != expire_keep_dms:
|
|
set_post_expiry_keep_dms(base_dir, nickname, domain,
|
|
expire_keep_dms)
|
|
actor_changed = True
|
|
|
|
# remove a custom font
|
|
if fields.get('removeCustomFont'):
|
|
if (fields['removeCustomFont'] == 'on' and
|
|
(is_artist(base_dir, nickname) or
|
|
path.startswith('/users/' +
|
|
admin_nickname + '/'))):
|
|
font_ext = ('woff', 'woff2', 'otf', 'ttf')
|
|
for ext in font_ext:
|
|
if os.path.isfile(base_dir +
|
|
'/fonts/custom.' + ext):
|
|
try:
|
|
os.remove(base_dir +
|
|
'/fonts/custom.' + ext)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
base_dir +
|
|
'/fonts/custom.' + ext)
|
|
if os.path.isfile(base_dir +
|
|
'/fonts/custom.' + ext +
|
|
'.etag'):
|
|
try:
|
|
os.remove(base_dir +
|
|
'/fonts/custom.' + ext +
|
|
'.etag')
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
base_dir + '/fonts/custom.' +
|
|
ext + '.etag')
|
|
curr_theme = get_theme(base_dir)
|
|
if curr_theme:
|
|
self.server.theme_name = curr_theme
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
set_theme(base_dir, curr_theme, domain,
|
|
allow_local_network_access,
|
|
system_language,
|
|
self.server.dyslexic_font, False)
|
|
self.server.text_mode_banner = \
|
|
get_text_mode_banner(base_dir)
|
|
self.server.iconsCache = {}
|
|
self.server.fontsCache = {}
|
|
self.server.show_publish_as_icon = \
|
|
get_config_param(base_dir,
|
|
'showPublishAsIcon')
|
|
self.server.full_width_tl_button_header = \
|
|
get_config_param(base_dir,
|
|
'fullWidthTimeline' +
|
|
'ButtonHeader')
|
|
self.server.icons_as_buttons = \
|
|
get_config_param(base_dir,
|
|
'iconsAsButtons')
|
|
self.server.rss_icon_at_top = \
|
|
get_config_param(base_dir,
|
|
'rssIconAtTop')
|
|
self.server.publish_button_at_top = \
|
|
get_config_param(base_dir,
|
|
'publishButtonAtTop')
|
|
|
|
# only receive DMs from accounts you follow
|
|
follow_dms_filename = \
|
|
acct_dir(base_dir, nickname, domain) + '/.followDMs'
|
|
if on_final_welcome_screen:
|
|
# initial default setting created via
|
|
# the welcome screen
|
|
try:
|
|
with open(follow_dms_filename, 'w+',
|
|
encoding='utf-8') as ffile:
|
|
ffile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write follow DMs ' +
|
|
follow_dms_filename)
|
|
actor_changed = True
|
|
else:
|
|
follow_dms_active = False
|
|
if fields.get('followDMs'):
|
|
if fields['followDMs'] == 'on':
|
|
follow_dms_active = True
|
|
try:
|
|
with open(follow_dms_filename, 'w+',
|
|
encoding='utf-8') as ffile:
|
|
ffile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write follow DMs 2 ' +
|
|
follow_dms_filename)
|
|
if not follow_dms_active:
|
|
if os.path.isfile(follow_dms_filename):
|
|
try:
|
|
os.remove(follow_dms_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
follow_dms_filename)
|
|
|
|
# remove Twitter retweets
|
|
remove_twitter_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.removeTwitter'
|
|
remove_twitter_active = False
|
|
if fields.get('removeTwitter'):
|
|
if fields['removeTwitter'] == 'on':
|
|
remove_twitter_active = True
|
|
try:
|
|
with open(remove_twitter_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write remove twitter ' +
|
|
remove_twitter_filename)
|
|
if not remove_twitter_active:
|
|
if os.path.isfile(remove_twitter_filename):
|
|
try:
|
|
os.remove(remove_twitter_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
remove_twitter_filename)
|
|
|
|
# hide Like button
|
|
hide_like_button_file = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.hideLikeButton'
|
|
notify_likes_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.notifyLikes'
|
|
hide_like_button_active = False
|
|
if fields.get('hideLikeButton'):
|
|
if fields['hideLikeButton'] == 'on':
|
|
hide_like_button_active = True
|
|
try:
|
|
with open(hide_like_button_file, 'w+',
|
|
encoding='utf-8') as rfil:
|
|
rfil.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write hide like ' +
|
|
hide_like_button_file)
|
|
# remove notify likes selection
|
|
if os.path.isfile(notify_likes_filename):
|
|
try:
|
|
os.remove(notify_likes_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
notify_likes_filename)
|
|
if not hide_like_button_active:
|
|
if os.path.isfile(hide_like_button_file):
|
|
try:
|
|
os.remove(hide_like_button_file)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
hide_like_button_file)
|
|
|
|
# Minimize all images from edit profile screen
|
|
minimize_all_images = False
|
|
if fields.get('minimizeAllImages'):
|
|
if fields['minimizeAllImages'] == 'on':
|
|
minimize_all_images = True
|
|
min_img_acct = self.server.min_images_for_accounts
|
|
set_minimize_all_images(base_dir,
|
|
nickname, domain,
|
|
True, min_img_acct)
|
|
print('min_images_for_accounts: ' +
|
|
str(min_img_acct))
|
|
if not minimize_all_images:
|
|
min_img_acct = self.server.min_images_for_accounts
|
|
set_minimize_all_images(base_dir,
|
|
nickname, domain,
|
|
False, min_img_acct)
|
|
print('min_images_for_accounts: ' +
|
|
str(min_img_acct))
|
|
|
|
# hide Reaction button
|
|
hide_reaction_button_file = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.hideReactionButton'
|
|
notify_reactions_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.notifyReactions'
|
|
hide_reaction_button_active = False
|
|
if fields.get('hideReactionButton'):
|
|
if fields['hideReactionButton'] == 'on':
|
|
hide_reaction_button_active = True
|
|
try:
|
|
with open(hide_reaction_button_file, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write hide reaction ' +
|
|
hide_reaction_button_file)
|
|
# remove notify Reaction selection
|
|
if os.path.isfile(notify_reactions_filename):
|
|
try:
|
|
os.remove(notify_reactions_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
notify_reactions_filename)
|
|
if not hide_reaction_button_active:
|
|
if os.path.isfile(hide_reaction_button_file):
|
|
try:
|
|
os.remove(hide_reaction_button_file)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
hide_reaction_button_file)
|
|
|
|
# bold reading checkbox
|
|
bold_reading_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.boldReading'
|
|
bold_reading = False
|
|
if fields.get('boldReading'):
|
|
if fields['boldReading'] == 'on':
|
|
bold_reading = True
|
|
self.server.bold_reading[nickname] = True
|
|
try:
|
|
with open(bold_reading_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write bold reading ' +
|
|
bold_reading_filename)
|
|
if not bold_reading:
|
|
if self.server.bold_reading.get(nickname):
|
|
del self.server.bold_reading[nickname]
|
|
if os.path.isfile(bold_reading_filename):
|
|
try:
|
|
os.remove(bold_reading_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
bold_reading_filename)
|
|
|
|
# reverse timelines checkbox
|
|
reverse = False
|
|
if fields.get('reverseTimelines'):
|
|
if fields['reverseTimelines'] == 'on':
|
|
reverse = True
|
|
if nickname not in self.server.reverse_sequence:
|
|
self.server.reverse_sequence.append(nickname)
|
|
save_reverse_timeline(base_dir,
|
|
self.server.reverse_sequence)
|
|
if not reverse:
|
|
if nickname in self.server.reverse_sequence:
|
|
self.server.reverse_sequence.remove(nickname)
|
|
save_reverse_timeline(base_dir,
|
|
self.server.reverse_sequence)
|
|
|
|
# show poll/vote/question posts checkbox
|
|
show_vote_posts = False
|
|
if fields.get('showVotes'):
|
|
if fields['showVotes'] == 'on':
|
|
show_vote_posts = True
|
|
account_dir = acct_dir(self.server.base_dir,
|
|
nickname, self.server.domain)
|
|
show_vote_file = account_dir + '/.noVotes'
|
|
if os.path.isfile(show_vote_file):
|
|
if show_vote_posts:
|
|
try:
|
|
os.remove(show_vote_file)
|
|
except OSError:
|
|
print('EX: unable to remove noVotes file ' +
|
|
show_vote_file)
|
|
else:
|
|
if not show_vote_posts:
|
|
try:
|
|
with open(show_vote_file, 'w+',
|
|
encoding='utf-8') as fp_votes:
|
|
fp_votes.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write noVotes file ' +
|
|
show_vote_file)
|
|
|
|
# show replies only from followers checkbox
|
|
show_replies_followers = False
|
|
if fields.get('repliesFromFollowersOnly'):
|
|
if fields['repliesFromFollowersOnly'] == 'on':
|
|
show_replies_followers = True
|
|
show_replies_followers_file = \
|
|
account_dir + '/.repliesFromFollowersOnly'
|
|
if os.path.isfile(show_replies_followers_file):
|
|
if not show_replies_followers:
|
|
try:
|
|
os.remove(show_replies_followers_file)
|
|
except OSError:
|
|
print('EX: unable to remove ' +
|
|
'repliesFromFollowersOnly file ' +
|
|
show_replies_followers_file)
|
|
else:
|
|
if show_replies_followers:
|
|
try:
|
|
with open(show_replies_followers_file, 'w+',
|
|
encoding='utf-8') as fp_replies:
|
|
fp_replies.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write ' +
|
|
'repliesFromFollowersOnly file ' +
|
|
show_replies_followers_file)
|
|
|
|
# show replies only from mutuals checkbox
|
|
show_replies_mutuals = False
|
|
if fields.get('repliesFromMutualsOnly'):
|
|
if fields['repliesFromMutualsOnly'] == 'on':
|
|
show_replies_mutuals = True
|
|
show_replies_mutuals_file = \
|
|
account_dir + '/.repliesFromMutualsOnly'
|
|
if os.path.isfile(show_replies_mutuals_file):
|
|
if not show_replies_mutuals:
|
|
try:
|
|
os.remove(show_replies_mutuals_file)
|
|
except OSError:
|
|
print('EX: unable to remove ' +
|
|
'repliesFromMutualsOnly file ' +
|
|
show_replies_mutuals_file)
|
|
else:
|
|
if show_replies_mutuals:
|
|
try:
|
|
with open(show_replies_mutuals_file, 'w+',
|
|
encoding='utf-8') as fp_replies:
|
|
fp_replies.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write ' +
|
|
'repliesFromMutualsOnly file ' +
|
|
show_replies_mutuals_file)
|
|
|
|
# hide follows checkbox
|
|
hide_follows_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.hideFollows'
|
|
hide_follows = False
|
|
if fields.get('hideFollows'):
|
|
if fields['hideFollows'] == 'on':
|
|
hide_follows = True
|
|
self.server.hide_follows[nickname] = True
|
|
actor_json['hideFollows'] = True
|
|
actor_changed = True
|
|
try:
|
|
with open(hide_follows_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write hideFollows ' +
|
|
hide_follows_filename)
|
|
if not hide_follows:
|
|
actor_json['hideFollows'] = False
|
|
if self.server.hide_follows.get(nickname):
|
|
del self.server.hide_follows[nickname]
|
|
actor_changed = True
|
|
if os.path.isfile(hide_follows_filename):
|
|
try:
|
|
os.remove(hide_follows_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
hide_follows_filename)
|
|
|
|
# block military instances
|
|
block_mil_instances = False
|
|
if fields.get('blockMilitary'):
|
|
if fields['blockMilitary'] == 'on':
|
|
block_mil_instances = True
|
|
if block_mil_instances:
|
|
if not self.server.block_military.get(nickname):
|
|
self.server.block_military[nickname] = True
|
|
save_blocked_military(self.server.base_dir,
|
|
self.server.block_military)
|
|
else:
|
|
if self.server.block_military.get(nickname):
|
|
del self.server.block_military[nickname]
|
|
save_blocked_military(self.server.base_dir,
|
|
self.server.block_military)
|
|
|
|
# notify about new Likes
|
|
if on_final_welcome_screen:
|
|
# default setting from welcome screen
|
|
try:
|
|
with open(notify_likes_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write notify likes ' +
|
|
notify_likes_filename)
|
|
actor_changed = True
|
|
else:
|
|
notify_likes_active = False
|
|
if fields.get('notifyLikes'):
|
|
if fields['notifyLikes'] == 'on' and \
|
|
not hide_like_button_active:
|
|
notify_likes_active = True
|
|
try:
|
|
with open(notify_likes_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write notify likes ' +
|
|
notify_likes_filename)
|
|
if not notify_likes_active:
|
|
if os.path.isfile(notify_likes_filename):
|
|
try:
|
|
os.remove(notify_likes_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
notify_likes_filename)
|
|
|
|
notify_reactions_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/.notifyReactions'
|
|
if on_final_welcome_screen:
|
|
# default setting from welcome screen
|
|
notify_react_filename = notify_reactions_filename
|
|
try:
|
|
with open(notify_react_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write notify reactions ' +
|
|
notify_reactions_filename)
|
|
actor_changed = True
|
|
else:
|
|
notify_reactions_active = False
|
|
if fields.get('notifyReactions'):
|
|
if fields['notifyReactions'] == 'on' and \
|
|
not hide_reaction_button_active:
|
|
notify_reactions_active = True
|
|
try:
|
|
with open(notify_reactions_filename, 'w+',
|
|
encoding='utf-8') as rfile:
|
|
rfile.write('\n')
|
|
except OSError:
|
|
print('EX: unable to write ' +
|
|
'notify reactions ' +
|
|
notify_reactions_filename)
|
|
if not notify_reactions_active:
|
|
if os.path.isfile(notify_reactions_filename):
|
|
try:
|
|
os.remove(notify_reactions_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
notify_reactions_filename)
|
|
|
|
# this account is a bot
|
|
if fields.get('isBot'):
|
|
if fields['isBot'] == 'on' and \
|
|
actor_json.get('type'):
|
|
if actor_json['type'] != 'Service':
|
|
actor_json['type'] = 'Service'
|
|
actor_changed = True
|
|
else:
|
|
# this account is a group
|
|
if fields.get('isGroup'):
|
|
if fields['isGroup'] == 'on' and \
|
|
actor_json.get('type'):
|
|
if actor_json['type'] != 'Group':
|
|
# only allow admin to create groups
|
|
if path.startswith('/users/' +
|
|
admin_nickname + '/'):
|
|
actor_json['type'] = 'Group'
|
|
actor_changed = True
|
|
else:
|
|
# this account is a person (default)
|
|
if actor_json.get('type'):
|
|
if actor_json['type'] != 'Person':
|
|
actor_json['type'] = 'Person'
|
|
actor_changed = True
|
|
|
|
# grayscale theme
|
|
if path.startswith('/users/' + admin_nickname + '/') or \
|
|
is_artist(base_dir, nickname):
|
|
grayscale = False
|
|
if fields.get('grayscale'):
|
|
if fields['grayscale'] == 'on':
|
|
grayscale = True
|
|
if grayscale:
|
|
enable_grayscale(base_dir)
|
|
else:
|
|
disable_grayscale(base_dir)
|
|
|
|
# dyslexic font
|
|
if path.startswith('/users/' + admin_nickname + '/') or \
|
|
is_artist(base_dir, nickname):
|
|
dyslexic_font = False
|
|
if fields.get('dyslexicFont'):
|
|
if fields['dyslexicFont'] == 'on':
|
|
dyslexic_font = True
|
|
if dyslexic_font != self.server.dyslexic_font:
|
|
self.server.dyslexic_font = dyslexic_font
|
|
set_config_param(base_dir, 'dyslexicFont',
|
|
self.server.dyslexic_font)
|
|
set_theme(base_dir,
|
|
self.server.theme_name,
|
|
self.server.domain,
|
|
self.server.allow_local_network_access,
|
|
self.server.system_language,
|
|
self.server.dyslexic_font, False)
|
|
|
|
# low bandwidth images checkbox
|
|
if path.startswith('/users/' + admin_nickname + '/') or \
|
|
is_artist(base_dir, nickname):
|
|
curr_low_bandwidth = \
|
|
get_config_param(base_dir, 'lowBandwidth')
|
|
low_bandwidth = False
|
|
if fields.get('lowBandwidth'):
|
|
if fields['lowBandwidth'] == 'on':
|
|
low_bandwidth = True
|
|
if curr_low_bandwidth != low_bandwidth:
|
|
set_config_param(base_dir, 'lowBandwidth',
|
|
low_bandwidth)
|
|
self.server.low_bandwidth = low_bandwidth
|
|
|
|
# save filtered words list
|
|
filter_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/filters.txt'
|
|
if fields.get('filteredWords'):
|
|
try:
|
|
with open(filter_filename, 'w+',
|
|
encoding='utf-8') as filterfile:
|
|
filterfile.write(fields['filteredWords'])
|
|
except OSError:
|
|
print('EX: unable to write filter ' +
|
|
filter_filename)
|
|
else:
|
|
if os.path.isfile(filter_filename):
|
|
try:
|
|
os.remove(filter_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete filter ' +
|
|
filter_filename)
|
|
|
|
# save filtered words within bio list
|
|
filter_bio_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/filters_bio.txt'
|
|
if fields.get('filteredWordsBio'):
|
|
try:
|
|
with open(filter_bio_filename, 'w+',
|
|
encoding='utf-8') as filterfile:
|
|
filterfile.write(fields['filteredWordsBio'])
|
|
except OSError:
|
|
print('EX: unable to write bio filter ' +
|
|
filter_bio_filename)
|
|
else:
|
|
if os.path.isfile(filter_bio_filename):
|
|
try:
|
|
os.remove(filter_bio_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete bio filter ' +
|
|
filter_bio_filename)
|
|
|
|
# word replacements
|
|
switch_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/replacewords.txt'
|
|
if fields.get('switchwords'):
|
|
try:
|
|
with open(switch_filename, 'w+',
|
|
encoding='utf-8') as switchfile:
|
|
switchfile.write(fields['switchwords'])
|
|
except OSError:
|
|
print('EX: unable to write switches ' +
|
|
switch_filename)
|
|
else:
|
|
if os.path.isfile(switch_filename):
|
|
try:
|
|
os.remove(switch_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
switch_filename)
|
|
|
|
# autogenerated tags
|
|
auto_tags_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/autotags.txt'
|
|
if fields.get('autoTags'):
|
|
try:
|
|
with open(auto_tags_filename, 'w+',
|
|
encoding='utf-8') as autofile:
|
|
autofile.write(fields['autoTags'])
|
|
except OSError:
|
|
print('EX: unable to write auto tags ' +
|
|
auto_tags_filename)
|
|
else:
|
|
if os.path.isfile(auto_tags_filename):
|
|
try:
|
|
os.remove(auto_tags_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
auto_tags_filename)
|
|
|
|
# autogenerated content warnings
|
|
auto_cw_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/autocw.txt'
|
|
if fields.get('autoCW'):
|
|
try:
|
|
with open(auto_cw_filename, 'w+',
|
|
encoding='utf-8') as auto_cw_file:
|
|
auto_cw_file.write(fields['autoCW'])
|
|
except OSError:
|
|
print('EX: unable to write auto CW ' +
|
|
auto_cw_filename)
|
|
self.server.auto_cw_cache[nickname] = \
|
|
fields['autoCW'].split('\n')
|
|
else:
|
|
if os.path.isfile(auto_cw_filename):
|
|
try:
|
|
os.remove(auto_cw_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
auto_cw_filename)
|
|
self.server.auto_cw_cache[nickname] = []
|
|
|
|
# save blocked accounts list
|
|
if fields.get('blocked'):
|
|
add_account_blocks(base_dir,
|
|
nickname, domain,
|
|
fields['blocked'])
|
|
else:
|
|
add_account_blocks(base_dir,
|
|
nickname, domain, '')
|
|
# import blocks from csv file
|
|
if fields.get('importBlocks'):
|
|
blocks_str = fields['importBlocks']
|
|
while blocks_str.startswith('\n'):
|
|
blocks_str = blocks_str[1:]
|
|
blocks_lines = blocks_str.split('\n')
|
|
if import_blocking_file(base_dir, nickname, domain,
|
|
blocks_lines):
|
|
print('blocks imported for ' + nickname)
|
|
else:
|
|
print('blocks not imported for ' + nickname)
|
|
|
|
if fields.get('importFollows'):
|
|
filename_base = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/import_following.csv'
|
|
follows_str = fields['importFollows']
|
|
while follows_str.startswith('\n'):
|
|
follows_str = follows_str[1:]
|
|
try:
|
|
with open(filename_base, 'w+',
|
|
encoding='utf-8') as fp_foll:
|
|
fp_foll.write(follows_str)
|
|
except OSError:
|
|
print('EX: unable to write imported follows ' +
|
|
filename_base)
|
|
|
|
if fields.get('importTheme'):
|
|
if not os.path.isdir(base_dir + '/imports'):
|
|
os.mkdir(base_dir + '/imports')
|
|
filename_base = \
|
|
base_dir + '/imports/newtheme.zip'
|
|
if os.path.isfile(filename_base):
|
|
try:
|
|
os.remove(filename_base)
|
|
except OSError:
|
|
print('EX: _profile_edit unable to delete ' +
|
|
filename_base)
|
|
if nickname == admin_nickname or \
|
|
is_artist(base_dir, nickname):
|
|
if import_theme(base_dir, filename_base):
|
|
print(nickname + ' uploaded a theme')
|
|
else:
|
|
print('Only admin or artist can import a theme')
|
|
|
|
# Save DM allowed instances list.
|
|
# The allow list for incoming DMs,
|
|
# if the .followDMs flag file exists
|
|
dm_allowed_instances_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/dmAllowedInstances.txt'
|
|
if fields.get('dmAllowedInstances'):
|
|
try:
|
|
with open(dm_allowed_instances_filename, 'w+',
|
|
encoding='utf-8') as afile:
|
|
afile.write(fields['dmAllowedInstances'])
|
|
except OSError:
|
|
print('EX: unable to write allowed DM instances ' +
|
|
dm_allowed_instances_filename)
|
|
else:
|
|
if os.path.isfile(dm_allowed_instances_filename):
|
|
try:
|
|
os.remove(dm_allowed_instances_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
dm_allowed_instances_filename)
|
|
|
|
# save allowed instances list
|
|
# This is the account level allow list
|
|
allowed_instances_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/allowedinstances.txt'
|
|
if fields.get('allowedInstances'):
|
|
inst_filename = allowed_instances_filename
|
|
try:
|
|
with open(inst_filename, 'w+',
|
|
encoding='utf-8') as afile:
|
|
afile.write(fields['allowedInstances'])
|
|
except OSError:
|
|
print('EX: unable to write allowed instances ' +
|
|
allowed_instances_filename)
|
|
else:
|
|
if os.path.isfile(allowed_instances_filename):
|
|
try:
|
|
os.remove(allowed_instances_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
allowed_instances_filename)
|
|
|
|
if is_moderator(self.server.base_dir, nickname):
|
|
# set selected content warning lists
|
|
new_lists_enabled = ''
|
|
for name, _ in self.server.cw_lists.items():
|
|
list_var_name = get_cw_list_variable(name)
|
|
if fields.get(list_var_name):
|
|
if fields[list_var_name] == 'on':
|
|
if new_lists_enabled:
|
|
new_lists_enabled += ', ' + name
|
|
else:
|
|
new_lists_enabled += name
|
|
if new_lists_enabled != self.server.lists_enabled:
|
|
self.server.lists_enabled = new_lists_enabled
|
|
set_config_param(self.server.base_dir,
|
|
"listsEnabled",
|
|
new_lists_enabled)
|
|
|
|
# save blocked user agents
|
|
user_agents_blocked = []
|
|
if fields.get('userAgentsBlockedStr'):
|
|
user_agents_blocked_str = \
|
|
fields['userAgentsBlockedStr']
|
|
user_agents_blocked_list = \
|
|
user_agents_blocked_str.split('\n')
|
|
for uagent in user_agents_blocked_list:
|
|
if uagent in user_agents_blocked:
|
|
continue
|
|
user_agents_blocked.append(uagent.strip())
|
|
if str(self.server.user_agents_blocked) != \
|
|
str(user_agents_blocked):
|
|
self.server.user_agents_blocked = \
|
|
user_agents_blocked
|
|
user_agents_blocked_str = ''
|
|
for uagent in user_agents_blocked:
|
|
if user_agents_blocked_str:
|
|
user_agents_blocked_str += ','
|
|
user_agents_blocked_str += uagent
|
|
set_config_param(base_dir, 'userAgentsBlocked',
|
|
user_agents_blocked_str)
|
|
|
|
# save allowed web crawlers
|
|
crawlers_allowed = []
|
|
if fields.get('crawlersAllowedStr'):
|
|
crawlers_allowed_str = \
|
|
fields['crawlersAllowedStr']
|
|
crawlers_allowed_list = \
|
|
crawlers_allowed_str.split('\n')
|
|
for uagent in crawlers_allowed_list:
|
|
if uagent in crawlers_allowed:
|
|
continue
|
|
crawlers_allowed.append(uagent.strip())
|
|
if str(self.server.crawlers_allowed) != \
|
|
str(crawlers_allowed):
|
|
self.server.crawlers_allowed = \
|
|
crawlers_allowed
|
|
crawlers_allowed_str = ''
|
|
for uagent in crawlers_allowed:
|
|
if crawlers_allowed_str:
|
|
crawlers_allowed_str += ','
|
|
crawlers_allowed_str += uagent
|
|
set_config_param(base_dir, 'crawlersAllowed',
|
|
crawlers_allowed_str)
|
|
|
|
# save allowed buy domains
|
|
buy_sites = {}
|
|
if fields.get('buySitesStr'):
|
|
buy_sites_str = \
|
|
fields['buySitesStr']
|
|
buy_sites_list = \
|
|
buy_sites_str.split('\n')
|
|
for site_url in buy_sites_list:
|
|
if ' ' in site_url:
|
|
site_url = site_url.split(' ')[-1]
|
|
buy_icon_text = \
|
|
site_url.replace(site_url, '').strip()
|
|
if not buy_icon_text:
|
|
buy_icon_text = site_url
|
|
else:
|
|
buy_icon_text = site_url
|
|
if buy_sites.get(buy_icon_text):
|
|
continue
|
|
if '<' in site_url:
|
|
continue
|
|
if not site_url.strip():
|
|
continue
|
|
buy_sites[buy_icon_text] = site_url.strip()
|
|
if str(self.server.buy_sites) != \
|
|
str(buy_sites):
|
|
self.server.buy_sites = buy_sites
|
|
buy_sites_filename = \
|
|
base_dir + '/accounts/buy_sites.json'
|
|
if buy_sites:
|
|
save_json(buy_sites, buy_sites_filename)
|
|
else:
|
|
if os.path.isfile(buy_sites_filename):
|
|
try:
|
|
os.remove(buy_sites_filename)
|
|
except OSError:
|
|
print('EX: unable to delete ' +
|
|
buy_sites_filename)
|
|
|
|
# save blocking API endpoints
|
|
block_ep_new = []
|
|
if fields.get('blockFederated'):
|
|
block_federated_str = \
|
|
fields['blockFederated']
|
|
block_ep_new = \
|
|
block_federated_str.split('\n')
|
|
if str(self.server.block_federated_endpoints) != \
|
|
str(block_ep_new):
|
|
base_dir = self.server.base_dir
|
|
self.server.block_federated_endpoints = \
|
|
save_block_federated_endpoints(base_dir,
|
|
block_ep_new)
|
|
if not block_ep_new:
|
|
self.server.block_federated = []
|
|
|
|
# save peertube instances list
|
|
peertube_instances_file = \
|
|
base_dir + '/accounts/peertube.txt'
|
|
if fields.get('ptInstances'):
|
|
self.server.peertube_instances.clear()
|
|
try:
|
|
with open(peertube_instances_file, 'w+',
|
|
encoding='utf-8') as afile:
|
|
afile.write(fields['ptInstances'])
|
|
except OSError:
|
|
print('EX: unable to write peertube ' +
|
|
peertube_instances_file)
|
|
pt_instances_list = \
|
|
fields['ptInstances'].split('\n')
|
|
if pt_instances_list:
|
|
for url in pt_instances_list:
|
|
url = url.strip()
|
|
if not url:
|
|
continue
|
|
if url in self.server.peertube_instances:
|
|
continue
|
|
self.server.peertube_instances.append(url)
|
|
else:
|
|
if os.path.isfile(peertube_instances_file):
|
|
try:
|
|
os.remove(peertube_instances_file)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
peertube_instances_file)
|
|
self.server.peertube_instances.clear()
|
|
|
|
# save git project names list
|
|
git_projects_filename = \
|
|
acct_dir(base_dir, nickname, domain) + \
|
|
'/gitprojects.txt'
|
|
if fields.get('gitProjects'):
|
|
try:
|
|
with open(git_projects_filename, 'w+',
|
|
encoding='utf-8') as afile:
|
|
afile.write(fields['gitProjects'].lower())
|
|
except OSError:
|
|
print('EX: unable to write git ' +
|
|
git_projects_filename)
|
|
else:
|
|
if os.path.isfile(git_projects_filename):
|
|
try:
|
|
os.remove(git_projects_filename)
|
|
except OSError:
|
|
print('EX: _profile_edit ' +
|
|
'unable to delete ' +
|
|
git_projects_filename)
|
|
|
|
# change memorial status
|
|
if is_memorial_account(base_dir, nickname):
|
|
if not actor_json.get('memorial'):
|
|
actor_json['memorial'] = True
|
|
actor_changed = True
|
|
elif actor_json.get('memorial'):
|
|
actor_json['memorial'] = False
|
|
actor_changed = True
|
|
|
|
# save actor json file within accounts
|
|
if actor_changed:
|
|
add_name_emojis_to_tags(base_dir, http_prefix,
|
|
domain, self.server.port,
|
|
actor_json)
|
|
# update the context for the actor
|
|
actor_json['@context'] = [
|
|
'https://www.w3.org/ns/activitystreams',
|
|
'https://w3id.org/security/v1',
|
|
get_default_person_context()
|
|
]
|
|
if actor_json.get('nomadicLocations'):
|
|
del actor_json['nomadicLocations']
|
|
if not actor_json.get('featured'):
|
|
actor_json['featured'] = \
|
|
actor_json['id'] + '/collections/featured'
|
|
if not actor_json.get('featuredTags'):
|
|
actor_json['featuredTags'] = \
|
|
actor_json['id'] + '/collections/tags'
|
|
randomize_actor_images(actor_json)
|
|
add_actor_update_timestamp(actor_json)
|
|
# save the actor
|
|
save_json(actor_json, actor_filename)
|
|
webfinger_update(base_dir,
|
|
nickname, domain,
|
|
onion_domain, i2p_domain,
|
|
self.server.cached_webfingers)
|
|
# also copy to the actors cache and
|
|
# person_cache in memory
|
|
store_person_in_cache(base_dir,
|
|
actor_json['id'], actor_json,
|
|
self.server.person_cache,
|
|
True)
|
|
# clear any cached images for this actor
|
|
id_str = actor_json['id'].replace('/', '-')
|
|
remove_avatar_from_cache(base_dir, id_str)
|
|
# save the actor to the cache
|
|
actor_cache_filename = \
|
|
base_dir + '/cache/actors/' + \
|
|
actor_json['id'].replace('/', '#') + '.json'
|
|
save_json(actor_json, actor_cache_filename)
|
|
# send profile update to followers
|
|
update_actor_json = get_actor_update_json(actor_json)
|
|
print('Sending actor update: ' +
|
|
str(update_actor_json))
|
|
post_to_outbox(self, update_actor_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type)
|
|
# send move activity if necessary
|
|
if send_move_activity:
|
|
move_actor_json = get_actor_move_json(actor_json)
|
|
print('Sending Move activity: ' +
|
|
str(move_actor_json))
|
|
post_to_outbox(self, move_actor_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type)
|
|
|
|
# deactivate the account
|
|
if fields.get('deactivateThisAccount'):
|
|
if fields['deactivateThisAccount'] == 'on':
|
|
deactivate_account(base_dir,
|
|
nickname, domain)
|
|
clear_login_details(self, nickname,
|
|
calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# redirect back to the profile screen
|
|
redirect_headers(self, actor_str + redirect_path,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _receive_new_post(self, post_type: str, path: str,
|
|
calling_domain: str, cookie: str,
|
|
content_license_url: str,
|
|
curr_session, proxy_type: str) -> int:
|
|
"""A new post has been created
|
|
This creates a thread to send the new post
|
|
"""
|
|
page_number = 1
|
|
original_path = path
|
|
|
|
if '/users/' not in path:
|
|
print('Not receiving new post for ' + path +
|
|
' because /users/ not in path')
|
|
return None
|
|
|
|
if '?' + post_type + '?' not in path:
|
|
print('Not receiving new post for ' + path +
|
|
' because ?' + post_type + '? not in path')
|
|
return None
|
|
|
|
print('New post begins: ' + post_type + ' ' + path)
|
|
|
|
if '?page=' in path:
|
|
page_number_str = path.split('?page=')[1]
|
|
if '?' in page_number_str:
|
|
page_number_str = page_number_str.split('?')[0]
|
|
if '#' in page_number_str:
|
|
page_number_str = page_number_str.split('#')[0]
|
|
if len(page_number_str) > 5:
|
|
page_number_str = "1"
|
|
if page_number_str.isdigit():
|
|
page_number = int(page_number_str)
|
|
path = path.split('?page=')[0]
|
|
|
|
# get the username who posted
|
|
new_post_thread_name = None
|
|
if '/users/' in path:
|
|
new_post_thread_name = path.split('/users/')[1]
|
|
if '/' in new_post_thread_name:
|
|
new_post_thread_name = new_post_thread_name.split('/')[0]
|
|
if not new_post_thread_name:
|
|
new_post_thread_name = '*'
|
|
|
|
if self.server.new_post_thread.get(new_post_thread_name):
|
|
print('Waiting for previous new post thread to end')
|
|
wait_ctr = 0
|
|
np_thread = self.server.new_post_thread[new_post_thread_name]
|
|
while np_thread.is_alive() and wait_ctr < 8:
|
|
time.sleep(1)
|
|
wait_ctr += 1
|
|
if wait_ctr >= 8:
|
|
print('Killing previous new post thread for ' +
|
|
new_post_thread_name)
|
|
np_thread.kill()
|
|
|
|
# make a copy of self.headers
|
|
headers = copy.deepcopy(self.headers)
|
|
headers_without_cookie = copy.deepcopy(headers)
|
|
if 'cookie' in headers_without_cookie:
|
|
del headers_without_cookie['cookie']
|
|
if 'Cookie' in headers_without_cookie:
|
|
del headers_without_cookie['Cookie']
|
|
print('New post headers: ' + str(headers_without_cookie))
|
|
|
|
length = int(headers['Content-Length'])
|
|
if length > self.server.max_post_length:
|
|
print('POST size too large')
|
|
return None
|
|
|
|
if not headers.get('Content-Type'):
|
|
if headers.get('Content-type'):
|
|
headers['Content-Type'] = headers['Content-type']
|
|
elif headers.get('content-type'):
|
|
headers['Content-Type'] = headers['content-type']
|
|
if headers.get('Content-Type'):
|
|
if ' boundary=' in headers['Content-Type']:
|
|
boundary = headers['Content-Type'].split('boundary=')[1]
|
|
if ';' in boundary:
|
|
boundary = boundary.split(';')[0]
|
|
|
|
try:
|
|
post_bytes = self.rfile.read(length)
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('WARN: POST post_bytes ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('WARN: POST post_bytes socket error')
|
|
return None
|
|
except ValueError as ex:
|
|
print('EX: POST post_bytes rfile.read failed, ' +
|
|
str(ex))
|
|
return None
|
|
|
|
# second length check from the bytes received
|
|
# since Content-Length could be untruthful
|
|
length = len(post_bytes)
|
|
if length > self.server.max_post_length:
|
|
print('POST size too large')
|
|
return None
|
|
|
|
# Note sending new posts needs to be synchronous,
|
|
# otherwise any attachments can get mangled if
|
|
# other events happen during their decoding
|
|
print('Creating new post from: ' + new_post_thread_name)
|
|
_receive_new_post_process(self, post_type,
|
|
original_path,
|
|
headers, length,
|
|
post_bytes, boundary,
|
|
calling_domain, cookie,
|
|
content_license_url,
|
|
curr_session, proxy_type)
|
|
return page_number
|
|
|
|
|
|
def _receive_new_post_process(self, post_type: str, path: str, headers: {},
|
|
length: int, post_bytes, boundary: str,
|
|
calling_domain: str, cookie: str,
|
|
content_license_url: str,
|
|
curr_session, proxy_type: str) -> int:
|
|
# Note: this needs to happen synchronously
|
|
# 0=this is not a new post
|
|
# 1=new post success
|
|
# -1=new post failed
|
|
# 2=new post canceled
|
|
if self.server.debug:
|
|
print('DEBUG: receiving POST')
|
|
|
|
if ' boundary=' in headers['Content-Type']:
|
|
if self.server.debug:
|
|
print('DEBUG: receiving POST headers ' +
|
|
headers['Content-Type'] +
|
|
' path ' + path)
|
|
nickname = None
|
|
nickname_str = path.split('/users/')[1]
|
|
if '?' in nickname_str:
|
|
nickname_str = nickname_str.split('?')[0]
|
|
if '/' in nickname_str:
|
|
nickname = nickname_str.split('/')[0]
|
|
else:
|
|
nickname = nickname_str
|
|
if self.server.debug:
|
|
print('DEBUG: POST nickname ' + str(nickname))
|
|
if not nickname:
|
|
print('WARN: no nickname found when receiving ' + post_type +
|
|
' path ' + path)
|
|
return -1
|
|
|
|
# get the message id of an edited post
|
|
edited_postid = None
|
|
print('DEBUG: edited_postid path ' + path)
|
|
if '?editid=' in path:
|
|
edited_postid = path.split('?editid=')[1]
|
|
if '?' in edited_postid:
|
|
edited_postid = edited_postid.split('?')[0]
|
|
print('DEBUG: edited_postid ' + edited_postid)
|
|
|
|
# get the published date of an edited post
|
|
edited_published = None
|
|
if '?editpub=' in path:
|
|
edited_published = path.split('?editpub=')[1]
|
|
if '?' in edited_published:
|
|
edited_published = \
|
|
edited_published.split('?')[0]
|
|
print('DEBUG: edited_published ' +
|
|
edited_published)
|
|
|
|
length = int(headers['Content-Length'])
|
|
if length > self.server.max_post_length:
|
|
print('POST size too large')
|
|
return -1
|
|
|
|
boundary = headers['Content-Type'].split('boundary=')[1]
|
|
if ';' in boundary:
|
|
boundary = boundary.split(';')[0]
|
|
|
|
# Note: we don't use cgi here because it's due to be deprecated
|
|
# in Python 3.8/3.10
|
|
# Instead we use the multipart mime parser from the email module
|
|
if self.server.debug:
|
|
print('DEBUG: extracting media from POST')
|
|
media_bytes, post_bytes = \
|
|
extract_media_in_form_post(post_bytes, boundary, 'attachpic')
|
|
if self.server.debug:
|
|
if media_bytes:
|
|
print('DEBUG: media was found. ' +
|
|
str(len(media_bytes)) + ' bytes')
|
|
else:
|
|
print('DEBUG: no media was found in POST')
|
|
|
|
# Note: a .temp extension is used here so that at no time is
|
|
# an image with metadata publicly exposed, even for a few mS
|
|
filename_base = \
|
|
acct_dir(self.server.base_dir,
|
|
nickname, self.server.domain) + '/upload.temp'
|
|
|
|
filename, attachment_media_type = \
|
|
save_media_in_form_post(media_bytes, self.server.debug,
|
|
filename_base)
|
|
if self.server.debug:
|
|
if filename:
|
|
print('DEBUG: POST media filename is ' + filename)
|
|
else:
|
|
print('DEBUG: no media filename in POST')
|
|
|
|
if filename:
|
|
if is_image_file(filename):
|
|
post_image_filename = filename.replace('.temp', '')
|
|
print('Removing metadata from ' + post_image_filename)
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname, self.server.domain)
|
|
if self.server.low_bandwidth:
|
|
convert_image_to_low_bandwidth(filename)
|
|
process_meta_data(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
filename, post_image_filename, city,
|
|
content_license_url)
|
|
if os.path.isfile(post_image_filename):
|
|
print('POST media saved to ' + post_image_filename)
|
|
else:
|
|
print('ERROR: POST media could not be saved to ' +
|
|
post_image_filename)
|
|
else:
|
|
if os.path.isfile(filename):
|
|
new_filename = filename.replace('.temp', '')
|
|
os.rename(filename, new_filename)
|
|
filename = new_filename
|
|
|
|
fields = \
|
|
extract_text_fields_in_post(post_bytes, boundary,
|
|
self.server.debug, None)
|
|
if self.server.debug:
|
|
if fields:
|
|
print('DEBUG: text field extracted from POST ' +
|
|
str(fields))
|
|
else:
|
|
print('WARN: no text fields could be extracted from POST')
|
|
|
|
# was the citations button pressed on the newblog screen?
|
|
citations_button_press = False
|
|
if post_type == 'newblog' and fields.get('submitCitations'):
|
|
if fields['submitCitations'] == \
|
|
self.server.translate['Citations']:
|
|
citations_button_press = True
|
|
|
|
if not citations_button_press:
|
|
# process the received text fields from the POST
|
|
if not fields.get('message') and \
|
|
not fields.get('imageDescription') and \
|
|
not fields.get('pinToProfile'):
|
|
print('WARN: no message, image description or pin')
|
|
return -1
|
|
submit_text1 = self.server.translate['Publish']
|
|
submit_text2 = self.server.translate['Send']
|
|
submit_text3 = submit_text2
|
|
custom_submit_text = \
|
|
get_config_param(self.server.base_dir, 'customSubmitText')
|
|
if custom_submit_text:
|
|
submit_text3 = custom_submit_text
|
|
if fields.get('submitPost'):
|
|
if fields['submitPost'] != submit_text1 and \
|
|
fields['submitPost'] != submit_text2 and \
|
|
fields['submitPost'] != submit_text3:
|
|
print('WARN: no submit field ' + fields['submitPost'])
|
|
return -1
|
|
else:
|
|
print('WARN: no submitPost')
|
|
return 2
|
|
|
|
if not fields.get('imageDescription'):
|
|
fields['imageDescription'] = None
|
|
if not fields.get('videoTranscript'):
|
|
fields['videoTranscript'] = None
|
|
if not fields.get('subject'):
|
|
fields['subject'] = None
|
|
if not fields.get('replyTo'):
|
|
fields['replyTo'] = None
|
|
|
|
if not fields.get('schedulePost'):
|
|
fields['schedulePost'] = False
|
|
else:
|
|
fields['schedulePost'] = True
|
|
print('DEBUG: shedulePost ' + str(fields['schedulePost']))
|
|
|
|
if not fields.get('eventDate'):
|
|
fields['eventDate'] = None
|
|
if not fields.get('eventTime'):
|
|
fields['eventTime'] = None
|
|
if not fields.get('eventEndTime'):
|
|
fields['eventEndTime'] = None
|
|
if not fields.get('location'):
|
|
fields['location'] = None
|
|
if not fields.get('languagesDropdown'):
|
|
fields['languagesDropdown'] = self.server.system_language
|
|
set_default_post_language(self.server.base_dir, nickname,
|
|
self.server.domain,
|
|
fields['languagesDropdown'])
|
|
self.server.default_post_language[nickname] = \
|
|
fields['languagesDropdown']
|
|
|
|
if not citations_button_press:
|
|
# Store a file which contains the time in seconds
|
|
# since epoch when an attempt to post something was made.
|
|
# This is then used for active monthly users counts
|
|
last_used_filename = \
|
|
acct_dir(self.server.base_dir,
|
|
nickname, self.server.domain) + '/.lastUsed'
|
|
try:
|
|
with open(last_used_filename, 'w+',
|
|
encoding='utf-8') as lastfile:
|
|
lastfile.write(str(int(time.time())))
|
|
except OSError:
|
|
print('EX: _receive_new_post_process unable to write ' +
|
|
last_used_filename)
|
|
|
|
mentions_str = ''
|
|
if fields.get('mentions'):
|
|
mentions_str = fields['mentions'].strip() + ' '
|
|
if not fields.get('commentsEnabled'):
|
|
comments_enabled = False
|
|
else:
|
|
comments_enabled = True
|
|
|
|
buy_url = ''
|
|
if fields.get('buyUrl'):
|
|
buy_url = fields['buyUrl']
|
|
|
|
chat_url = ''
|
|
if fields.get('chatUrl'):
|
|
chat_url = fields['chatUrl']
|
|
|
|
if post_type == 'newpost':
|
|
if not fields.get('pinToProfile'):
|
|
pin_to_profile = False
|
|
else:
|
|
pin_to_profile = True
|
|
# is the post message empty?
|
|
if not fields['message']:
|
|
# remove the pinned content from profile screen
|
|
undo_pinned_post(self.server.base_dir,
|
|
nickname, self.server.domain)
|
|
return 1
|
|
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname, self.server.domain)
|
|
|
|
conversation_id = None
|
|
if fields.get('conversationId'):
|
|
conversation_id = fields['conversationId']
|
|
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_public_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_str + fields['message'],
|
|
False, False, comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['replyTo'], fields['replyTo'],
|
|
fields['subject'],
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'], False,
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate, buy_url,
|
|
chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'outbox',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited public post ' +
|
|
str(message_json))
|
|
if fields['schedulePost']:
|
|
return 1
|
|
if pin_to_profile:
|
|
sys_language = self.server.system_language
|
|
content_str = \
|
|
get_base_content_from_post(message_json,
|
|
sys_language)
|
|
pin_post2(self.server.base_dir,
|
|
nickname, self.server.domain, content_str)
|
|
return 1
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newblog':
|
|
# citations button on newblog screen
|
|
if citations_button_press:
|
|
message_json = \
|
|
html_citations(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.translate,
|
|
self.server.newswire,
|
|
fields['subject'],
|
|
fields['message'],
|
|
self.server.theme_name)
|
|
if message_json:
|
|
message_json = message_json.encode('utf-8')
|
|
message_json_len = len(message_json)
|
|
set_headers(self, 'text/html',
|
|
message_json_len,
|
|
cookie, calling_domain, False)
|
|
write2(self, message_json)
|
|
return 1
|
|
else:
|
|
return -1
|
|
if not fields['subject']:
|
|
print('WARN: blog posts must have a title')
|
|
return -1
|
|
if not fields['message']:
|
|
print('WARN: blog posts must have content')
|
|
return -1
|
|
# submit button on newblog screen
|
|
save_to_file = False
|
|
client_to_server = False
|
|
city = None
|
|
conversation_id = None
|
|
if fields.get('conversationId'):
|
|
conversation_id = fields['conversationId']
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_blog_post(self.server.base_dir, nickname,
|
|
self.server.domain, self.server.port,
|
|
self.server.http_prefix,
|
|
fields['message'],
|
|
save_to_file,
|
|
client_to_server, comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['replyTo'], fields['replyTo'],
|
|
fields['subject'],
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'],
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate, buy_url,
|
|
chat_url)
|
|
if message_json:
|
|
if fields['schedulePost']:
|
|
return 1
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
refresh_newswire(self.server.base_dir)
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type == 'editblogpost':
|
|
print('Edited blog post received')
|
|
post_filename = \
|
|
locate_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
fields['postUrl'])
|
|
if os.path.isfile(post_filename):
|
|
post_json_object = load_json(post_filename)
|
|
if post_json_object:
|
|
cached_filename = \
|
|
acct_dir(self.server.base_dir,
|
|
nickname, self.server.domain) + \
|
|
'/postcache/' + \
|
|
fields['postUrl'].replace('/', '#') + '.html'
|
|
if os.path.isfile(cached_filename):
|
|
print('Edited blog post, removing cached html')
|
|
try:
|
|
os.remove(cached_filename)
|
|
except OSError:
|
|
print('EX: _receive_new_post_process ' +
|
|
'unable to delete ' + cached_filename)
|
|
# remove from memory cache
|
|
remove_post_from_cache(post_json_object,
|
|
self.server.recent_posts_cache)
|
|
# change the blog post title
|
|
post_json_object['object']['summary'] = \
|
|
fields['subject']
|
|
# format message
|
|
tags = []
|
|
hashtags_dict = {}
|
|
mentioned_recipients = []
|
|
fields['message'] = \
|
|
add_html_tags(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname, self.server.domain,
|
|
fields['message'],
|
|
mentioned_recipients,
|
|
hashtags_dict,
|
|
self.server.translate,
|
|
True)
|
|
# replace emoji with unicode
|
|
tags = []
|
|
for _, tag in hashtags_dict.items():
|
|
tags.append(tag)
|
|
# get list of tags
|
|
fields['message'] = \
|
|
replace_emoji_from_tags(curr_session,
|
|
self.server.base_dir,
|
|
fields['message'],
|
|
tags, 'content',
|
|
self.server.debug,
|
|
True)
|
|
|
|
post_json_object['object']['content'] = \
|
|
fields['message']
|
|
content_map = post_json_object['object']['contentMap']
|
|
content_map[self.server.system_language] = \
|
|
fields['message']
|
|
|
|
img_description = ''
|
|
if fields.get('imageDescription'):
|
|
img_description = fields['imageDescription']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
|
|
if filename:
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
license_url = fields['mediaLicense']
|
|
if '://' not in license_url:
|
|
license_url = \
|
|
license_link_from_name(license_url)
|
|
creator = ''
|
|
if fields.get('mediaCreator'):
|
|
creator = fields['mediaCreator']
|
|
post_json_object['object'] = \
|
|
attach_media(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
post_json_object['object'],
|
|
filename,
|
|
attachment_media_type,
|
|
img_description,
|
|
video_transcript,
|
|
city,
|
|
self.server.low_bandwidth,
|
|
license_url, creator,
|
|
fields['languagesDropdown'])
|
|
|
|
replace_you_tube(post_json_object,
|
|
self.server.yt_replace_domain,
|
|
self.server.system_language)
|
|
replace_twitter(post_json_object,
|
|
self.server.twitter_replacement_domain,
|
|
self.server.system_language)
|
|
save_json(post_json_object, post_filename)
|
|
# also save to the news actor
|
|
if nickname != 'news':
|
|
post_filename = \
|
|
post_filename.replace('#users#' +
|
|
nickname + '#',
|
|
'#users#news#')
|
|
save_json(post_json_object, post_filename)
|
|
print('Edited blog post, resaved ' + post_filename)
|
|
return 1
|
|
else:
|
|
print('Edited blog post, unable to load json for ' +
|
|
post_filename)
|
|
else:
|
|
print('Edited blog post not found ' +
|
|
str(fields['postUrl']))
|
|
return -1
|
|
elif post_type == 'newunlisted':
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
save_to_file = False
|
|
client_to_server = False
|
|
|
|
conversation_id = None
|
|
if fields.get('conversationId'):
|
|
conversation_id = fields['conversationId']
|
|
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_unlisted_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain, self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_str + fields['message'],
|
|
save_to_file,
|
|
client_to_server, comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['replyTo'],
|
|
fields['replyTo'],
|
|
fields['subject'],
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'],
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate, buy_url,
|
|
chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'outbox',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited unlisted post ' +
|
|
str(message_json))
|
|
|
|
if fields['schedulePost']:
|
|
return 1
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newfollowers':
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
save_to_file = False
|
|
client_to_server = False
|
|
|
|
conversation_id = None
|
|
if fields.get('conversationId'):
|
|
conversation_id = fields['conversationId']
|
|
|
|
mentions_message = mentions_str + fields['message']
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_followers_only_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_message,
|
|
save_to_file,
|
|
client_to_server,
|
|
comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['replyTo'],
|
|
fields['replyTo'],
|
|
fields['subject'],
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'],
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url,
|
|
media_creator,
|
|
languages_understood,
|
|
self.server.translate,
|
|
buy_url, chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'outbox',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited followers post ' +
|
|
str(message_json))
|
|
|
|
if fields['schedulePost']:
|
|
return 1
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newdm':
|
|
message_json = None
|
|
print('A DM was posted')
|
|
if '@' in mentions_str:
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
save_to_file = False
|
|
client_to_server = False
|
|
|
|
conversation_id = None
|
|
if fields.get('conversationId'):
|
|
conversation_id = fields['conversationId']
|
|
content_license_url = self.server.content_license_url
|
|
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
|
|
reply_is_chat = False
|
|
if fields.get('replychatmsg'):
|
|
reply_is_chat = fields['replychatmsg']
|
|
|
|
dm_license_url = self.server.dm_license_url
|
|
media_license_url = content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_direct_message_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_str +
|
|
fields['message'],
|
|
save_to_file,
|
|
client_to_server,
|
|
comments_enabled,
|
|
filename,
|
|
attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['replyTo'],
|
|
fields['replyTo'],
|
|
fields['subject'],
|
|
True,
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'],
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
dm_license_url,
|
|
media_license_url,
|
|
media_creator,
|
|
languages_understood,
|
|
reply_is_chat,
|
|
self.server.translate,
|
|
buy_url, chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
print('DEBUG: posting DM edited_postid ' +
|
|
str(edited_postid))
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'outbox',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited dm post ' +
|
|
str(message_json))
|
|
|
|
if fields['schedulePost']:
|
|
return 1
|
|
print('Sending new DM to ' +
|
|
str(message_json['object']['to']))
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newreminder':
|
|
message_json = None
|
|
handle = nickname + '@' + self.server.domain_full
|
|
print('A reminder was posted for ' + handle)
|
|
if '@' + handle not in mentions_str:
|
|
mentions_str = '@' + handle + ' ' + mentions_str
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
save_to_file = False
|
|
client_to_server = False
|
|
comments_enabled = False
|
|
conversation_id = None
|
|
mentions_message = mentions_str + fields['message']
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_direct_message_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_message,
|
|
save_to_file,
|
|
client_to_server,
|
|
comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
None, None,
|
|
fields['subject'],
|
|
True, fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'],
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.dm_license_url,
|
|
media_license_url,
|
|
media_creator,
|
|
languages_understood,
|
|
False, self.server.translate,
|
|
buy_url, chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if fields['schedulePost']:
|
|
return 1
|
|
print('DEBUG: new reminder to ' +
|
|
str(message_json['object']['to']) + ' ' +
|
|
str(edited_postid))
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'dm',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited reminder post ' +
|
|
str(message_json))
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newreport':
|
|
if attachment_media_type:
|
|
if attachment_media_type != 'image':
|
|
return -1
|
|
# So as to be sure that this only goes to moderators
|
|
# and not accounts being reported we disable any
|
|
# included fediverse addresses by replacing '@' with '-at-'
|
|
fields['message'] = fields['message'].replace('@', '-at-')
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_report_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain, self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_str + fields['message'],
|
|
False, False, True,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
self.server.debug, fields['subject'],
|
|
fields['languagesDropdown'],
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
return 1
|
|
return -1
|
|
elif post_type == 'newquestion':
|
|
if not fields.get('duration'):
|
|
return -1
|
|
if not fields.get('message'):
|
|
return -1
|
|
q_options = []
|
|
for question_ctr in range(8):
|
|
if fields.get('questionOption' + str(question_ctr)):
|
|
q_options.append(fields['questionOption' +
|
|
str(question_ctr)])
|
|
if not q_options:
|
|
return -1
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
if isinstance(fields['duration'], str):
|
|
if len(fields['duration']) > 5:
|
|
return -1
|
|
int_duration_days = int(fields['duration'])
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
message_json = \
|
|
create_question_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
fields['message'], q_options,
|
|
False, False,
|
|
comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city,
|
|
fields['subject'],
|
|
int_duration_days,
|
|
fields['languagesDropdown'],
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if self.server.debug:
|
|
print('DEBUG: new Question')
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
return 1
|
|
return -1
|
|
elif post_type in ('newreadingstatus'):
|
|
if not fields.get('readingupdatetype'):
|
|
print(post_type + ' no readingupdatetype')
|
|
return -1
|
|
if fields['readingupdatetype'] not in ('readingupdatewant',
|
|
'readingupdateread',
|
|
'readingupdatefinished',
|
|
'readingupdaterating'):
|
|
print(post_type + ' not recognised ' +
|
|
fields['readingupdatetype'])
|
|
return -1
|
|
if not fields.get('booktitle'):
|
|
print(post_type + ' no booktitle')
|
|
return -1
|
|
if not fields.get('bookurl'):
|
|
print(post_type + ' no bookurl')
|
|
return -1
|
|
book_rating = 0.0
|
|
if fields.get('bookrating'):
|
|
if isinstance(fields['bookrating'], float) or \
|
|
isinstance(fields['bookrating'], int):
|
|
book_rating = fields['bookrating']
|
|
media_license_url = self.server.content_license_url
|
|
if fields.get('mediaLicense'):
|
|
media_license_url = fields['mediaLicense']
|
|
if '://' not in media_license_url:
|
|
media_license_url = \
|
|
license_link_from_name(media_license_url)
|
|
media_creator = ''
|
|
if fields.get('mediaCreator'):
|
|
media_creator = fields['mediaCreator']
|
|
video_transcript = ''
|
|
if fields.get('videoTranscript'):
|
|
video_transcript = fields['videoTranscript']
|
|
conversation_id = None
|
|
languages_understood = \
|
|
get_understood_languages(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain_full,
|
|
self.server.person_cache)
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname, self.server.domain)
|
|
msg_str = fields['readingupdatetype']
|
|
# reading status
|
|
message_json = \
|
|
create_reading_post(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain,
|
|
self.server.port,
|
|
self.server.http_prefix,
|
|
mentions_str, msg_str,
|
|
fields['booktitle'],
|
|
fields['bookurl'],
|
|
book_rating,
|
|
False, False, comments_enabled,
|
|
filename, attachment_media_type,
|
|
fields['imageDescription'],
|
|
video_transcript,
|
|
city, None, None,
|
|
fields['subject'],
|
|
fields['schedulePost'],
|
|
fields['eventDate'],
|
|
fields['eventTime'],
|
|
fields['eventEndTime'],
|
|
fields['location'], False,
|
|
fields['languagesDropdown'],
|
|
conversation_id,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
media_license_url, media_creator,
|
|
languages_understood,
|
|
self.server.translate, buy_url,
|
|
chat_url,
|
|
self.server.auto_cw_cache)
|
|
if message_json:
|
|
if edited_postid:
|
|
recent_posts_cache = self.server.recent_posts_cache
|
|
allow_local_network_access = \
|
|
self.server.allow_local_network_access
|
|
signing_priv_key_pem = \
|
|
self.server.signing_priv_key_pem
|
|
twitter_replacement_domain = \
|
|
self.server.twitter_replacement_domain
|
|
show_published_date_only = \
|
|
self.server.show_published_date_only
|
|
min_images_for_accounts = \
|
|
self.server.min_images_for_accounts
|
|
peertube_instances = \
|
|
self.server.peertube_instances
|
|
update_edited_post(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
message_json,
|
|
edited_published,
|
|
edited_postid,
|
|
recent_posts_cache,
|
|
'outbox',
|
|
self.server.max_mentions,
|
|
self.server.max_emoji,
|
|
allow_local_network_access,
|
|
self.server.debug,
|
|
self.server.system_language,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.person_cache,
|
|
signing_priv_key_pem,
|
|
self.server.max_recent_posts,
|
|
self.server.translate,
|
|
curr_session,
|
|
self.server.cached_webfingers,
|
|
self.server.port,
|
|
self.server.allow_deletion,
|
|
self.server.yt_replace_domain,
|
|
twitter_replacement_domain,
|
|
show_published_date_only,
|
|
peertube_instances,
|
|
self.server.theme_name,
|
|
self.server.max_like_count,
|
|
self.server.cw_lists,
|
|
self.server.dogwhistles,
|
|
min_images_for_accounts,
|
|
self.server.max_hashtags,
|
|
self.server.buy_sites,
|
|
self.server.auto_cw_cache)
|
|
print('DEBUG: sending edited reading status post ' +
|
|
str(message_json))
|
|
if fields['schedulePost']:
|
|
return 1
|
|
if not fields.get('pinToProfile'):
|
|
pin_to_profile = False
|
|
else:
|
|
pin_to_profile = True
|
|
if pin_to_profile:
|
|
sys_language = self.server.system_language
|
|
content_str = \
|
|
get_base_content_from_post(message_json,
|
|
sys_language)
|
|
pin_post2(self.server.base_dir,
|
|
nickname, self.server.domain, content_str)
|
|
return 1
|
|
if post_to_outbox(self, message_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type):
|
|
populate_replies(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
message_json,
|
|
self.server.max_replies,
|
|
self.server.debug)
|
|
return 1
|
|
return -1
|
|
elif post_type in ('newshare', 'newwanted'):
|
|
if not fields.get('itemQty'):
|
|
print(post_type + ' no itemQty')
|
|
return -1
|
|
if not fields.get('itemType'):
|
|
print(post_type + ' no itemType')
|
|
return -1
|
|
if 'itemPrice' not in fields:
|
|
print(post_type + ' no itemPrice')
|
|
return -1
|
|
if 'itemCurrency' not in fields:
|
|
print(post_type + ' no itemCurrency')
|
|
return -1
|
|
if not fields.get('category'):
|
|
print(post_type + ' no category')
|
|
return -1
|
|
if not fields.get('duration'):
|
|
print(post_type + ' no duratio')
|
|
return -1
|
|
if attachment_media_type:
|
|
if attachment_media_type != 'image':
|
|
print('Attached media is not an image')
|
|
return -1
|
|
duration_str = fields['duration']
|
|
if duration_str:
|
|
if ' ' not in duration_str:
|
|
duration_str = duration_str + ' days'
|
|
city = get_spoofed_city(self.server.city,
|
|
self.server.base_dir,
|
|
nickname,
|
|
self.server.domain)
|
|
item_qty = 1
|
|
if fields['itemQty']:
|
|
if is_float(fields['itemQty']):
|
|
item_qty = float(fields['itemQty'])
|
|
item_price = "0.00"
|
|
item_currency = "EUR"
|
|
if fields['itemPrice']:
|
|
item_price, item_currency = \
|
|
get_price_from_string(fields['itemPrice'])
|
|
if fields['itemCurrency']:
|
|
item_currency = fields['itemCurrency']
|
|
if post_type == 'newshare':
|
|
print('Adding shared item')
|
|
shares_file_type = 'shares'
|
|
else:
|
|
print('Adding wanted item')
|
|
shares_file_type = 'wanted'
|
|
share_on_profile = False
|
|
if fields.get('shareOnProfile'):
|
|
if fields['shareOnProfile'] == 'on':
|
|
share_on_profile = True
|
|
add_share(self.server.base_dir,
|
|
self.server.http_prefix,
|
|
nickname,
|
|
self.server.domain, self.server.port,
|
|
fields['subject'],
|
|
fields['message'],
|
|
filename,
|
|
item_qty, fields['itemType'],
|
|
fields['category'],
|
|
fields['location'],
|
|
duration_str,
|
|
self.server.debug,
|
|
city, item_price, item_currency,
|
|
fields['languagesDropdown'],
|
|
self.server.translate, shares_file_type,
|
|
self.server.low_bandwidth,
|
|
self.server.content_license_url,
|
|
share_on_profile,
|
|
self.server.block_federated)
|
|
if post_type == 'newshare':
|
|
# add shareOnProfile items to the actor attachments
|
|
# https://codeberg.org/fediverse/fep/src/branch/main/fep/0837/fep-0837.md
|
|
actor = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
'/users/' + nickname
|
|
person_cache = self.server.person_cache
|
|
actor_json = get_person_from_cache(self.server.base_dir,
|
|
actor, person_cache)
|
|
if not actor_json:
|
|
actor_filename = \
|
|
acct_dir(self.server.base_dir, nickname,
|
|
self.server.domain) + '.json'
|
|
if os.path.isfile(actor_filename):
|
|
actor_json = load_json(actor_filename, 1, 1)
|
|
if actor_json:
|
|
max_shares_on_profile = \
|
|
self.server.max_shares_on_profile
|
|
if add_shares_to_actor(self.server.base_dir,
|
|
nickname, self.server.domain,
|
|
actor_json,
|
|
max_shares_on_profile):
|
|
remove_person_from_cache(self.server.base_dir,
|
|
actor, person_cache)
|
|
store_person_in_cache(self.server.base_dir, actor,
|
|
actor_json, person_cache,
|
|
True)
|
|
actor_filename = \
|
|
acct_dir(self.server.base_dir,
|
|
nickname,
|
|
self.server.domain) + '.json'
|
|
save_json(actor_json, actor_filename)
|
|
# send profile update to followers
|
|
update_actor_json = \
|
|
get_actor_update_json(actor_json)
|
|
print('Sending actor update ' +
|
|
'after change to attached shares: ' +
|
|
str(update_actor_json))
|
|
post_to_outbox(self, update_actor_json,
|
|
self.server.project_version,
|
|
nickname,
|
|
curr_session, proxy_type)
|
|
|
|
if filename:
|
|
if os.path.isfile(filename):
|
|
try:
|
|
os.remove(filename)
|
|
except OSError:
|
|
print('EX: _receive_new_post_process ' +
|
|
'unable to delete ' + filename)
|
|
self.post_to_nickname = nickname
|
|
return 1
|
|
return -1
|
|
|
|
|
|
def _set_hashtag_category2(self, calling_domain: str, cookie: str,
|
|
path: str, base_dir: str,
|
|
domain: str, debug: bool,
|
|
system_language: str) -> None:
|
|
"""On the screen after selecting a hashtag from the swarm, this sets
|
|
the category for that tag
|
|
"""
|
|
users_path = path.replace('/sethashtagcategory', '')
|
|
hashtag = ''
|
|
if '/tags/' not in users_path:
|
|
# no hashtag is specified within the path
|
|
http_404(self, 14)
|
|
return
|
|
hashtag = users_path.split('/tags/')[1].strip()
|
|
hashtag = urllib.parse.unquote_plus(hashtag)
|
|
if not hashtag:
|
|
# no hashtag was given in the path
|
|
http_404(self, 15)
|
|
return
|
|
hashtag_filename = base_dir + '/tags/' + hashtag + '.txt'
|
|
if not os.path.isfile(hashtag_filename):
|
|
# the hashtag does not exist
|
|
http_404(self, 16)
|
|
return
|
|
users_path = users_path.split('/tags/')[0]
|
|
actor_str = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
users_path
|
|
tag_screen_str = actor_str + '/tags/' + hashtag
|
|
|
|
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 = None
|
|
if nickname:
|
|
editor = is_editor(base_dir, nickname)
|
|
if not hashtag or not editor:
|
|
if not nickname:
|
|
print('WARN: nickname not found in ' + actor_str)
|
|
else:
|
|
print('WARN: nickname is not a moderator' + actor_str)
|
|
redirect_headers(self, tag_screen_str, cookie, calling_domain)
|
|
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 > self.server.max_post_length:
|
|
print('Maximum links data length exceeded ' + str(length))
|
|
redirect_headers(self, tag_screen_str, cookie, calling_domain)
|
|
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)
|
|
|
|
if fields.get('hashtagCategory'):
|
|
category_str = fields['hashtagCategory'].lower()
|
|
if not is_blocked_hashtag(base_dir, category_str) and \
|
|
not is_filtered(base_dir, nickname, domain, category_str,
|
|
system_language):
|
|
set_hashtag_category(base_dir, hashtag,
|
|
category_str, False)
|
|
else:
|
|
category_filename = base_dir + '/tags/' + hashtag + '.category'
|
|
if os.path.isfile(category_filename):
|
|
try:
|
|
os.remove(category_filename)
|
|
except OSError:
|
|
print('EX: _set_hashtag_category unable to delete ' +
|
|
category_filename)
|
|
|
|
# redirect back to the default timeline
|
|
redirect_headers(self, tag_screen_str,
|
|
cookie, calling_domain)
|
|
self.server.postreq_busy = False
|
|
|
|
|
|
def _post_login_screen(self, calling_domain: str, cookie: str,
|
|
base_dir: str, http_prefix: str,
|
|
domain: str, port: int,
|
|
ua_str: str, debug: bool,
|
|
registrations_open: bool) -> None:
|
|
"""POST to login screen, containing credentials
|
|
"""
|
|
# ensure that there is a minimum delay between failed login
|
|
# attempts, to mitigate brute force
|
|
if int(time.time()) - self.server.last_login_failure < 5:
|
|
http_503(self)
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
# get the contents of POST containing login credentials
|
|
length = int(self.headers['Content-length'])
|
|
if length > 512:
|
|
print('Login failed - credentials too long')
|
|
http_401(self, 'Credentials are too long')
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
try:
|
|
login_params = self.rfile.read(length).decode('utf-8')
|
|
except SocketError as ex:
|
|
if ex.errno == errno.ECONNRESET:
|
|
print('EX: POST login read ' +
|
|
'connection reset by peer')
|
|
else:
|
|
print('EX: POST login read socket error')
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
except ValueError as ex:
|
|
print('EX: POST login read failed, ' + str(ex))
|
|
self.send_response(400)
|
|
self.end_headers()
|
|
self.server.postreq_busy = False
|
|
return
|
|
|
|
login_nickname, login_password, register = \
|
|
html_get_login_credentials(login_params,
|
|
self.server.last_login_time,
|
|
registrations_open)
|
|
if login_nickname and login_password:
|
|
if is_system_account(login_nickname):
|
|
print('Invalid username login: ' + login_nickname +
|
|
' (system account)')
|
|
clear_login_details(self, login_nickname, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
self.server.last_login_time = int(time.time())
|
|
if register:
|
|
if not valid_password(login_password):
|
|
self.server.postreq_busy = False
|
|
login_url = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
'/login'
|
|
redirect_headers(self, login_url, cookie, calling_domain)
|
|
return
|
|
|
|
if not register_account(base_dir, http_prefix, domain, port,
|
|
login_nickname, login_password,
|
|
self.server.manual_follower_approval):
|
|
self.server.postreq_busy = False
|
|
login_url = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
'/login'
|
|
redirect_headers(self, login_url, cookie, calling_domain)
|
|
return
|
|
auth_header = \
|
|
create_basic_auth_header(login_nickname, login_password)
|
|
if self.headers.get('X-Forward-For'):
|
|
ip_address = self.headers['X-Forward-For']
|
|
elif self.headers.get('X-Forwarded-For'):
|
|
ip_address = self.headers['X-Forwarded-For']
|
|
else:
|
|
ip_address = self.client_address[0]
|
|
if not domain.endswith('.onion'):
|
|
if not is_local_network_address(ip_address):
|
|
print('Login attempt from IP: ' + str(ip_address))
|
|
if not authorize_basic(base_dir, '/users/' +
|
|
login_nickname + '/outbox',
|
|
auth_header, False):
|
|
print('Login failed: ' + login_nickname)
|
|
clear_login_details(self, login_nickname, calling_domain)
|
|
fail_time = int(time.time())
|
|
self.server.last_login_failure = fail_time
|
|
if not domain.endswith('.onion'):
|
|
if not is_local_network_address(ip_address):
|
|
record_login_failure(base_dir, ip_address,
|
|
self.server.login_failure_count,
|
|
fail_time,
|
|
self.server.log_login_failures)
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
if self.server.login_failure_count.get(ip_address):
|
|
del self.server.login_failure_count[ip_address]
|
|
if is_suspended(base_dir, login_nickname):
|
|
msg = \
|
|
html_suspended(base_dir).encode('utf-8')
|
|
msglen = len(msg)
|
|
login_headers(self, 'text/html',
|
|
msglen, calling_domain)
|
|
write2(self, msg)
|
|
self.server.postreq_busy = False
|
|
return
|
|
# login success - redirect with authorization
|
|
print('====== Login success: ' + login_nickname +
|
|
' ' + ua_str)
|
|
# re-activate account if needed
|
|
activate_account(base_dir, login_nickname, domain)
|
|
# This produces a deterministic token based
|
|
# on nick+password+salt
|
|
salt_filename = \
|
|
acct_dir(base_dir, login_nickname, domain) + '/.salt'
|
|
salt = create_password(32)
|
|
if os.path.isfile(salt_filename):
|
|
try:
|
|
with open(salt_filename, 'r',
|
|
encoding='utf-8') as fp_salt:
|
|
salt = fp_salt.read()
|
|
except OSError as ex:
|
|
print('EX: Unable to read salt for ' +
|
|
login_nickname + ' ' + str(ex))
|
|
else:
|
|
try:
|
|
with open(salt_filename, 'w+',
|
|
encoding='utf-8') as fp_salt:
|
|
fp_salt.write(salt)
|
|
except OSError as ex:
|
|
print('EX: Unable to save salt for ' +
|
|
login_nickname + ' ' + str(ex))
|
|
|
|
token_text = login_nickname + login_password + salt
|
|
token = sha256(token_text.encode('utf-8')).hexdigest()
|
|
self.server.tokens[login_nickname] = token
|
|
login_handle = login_nickname + '@' + domain
|
|
token_filename = \
|
|
base_dir + '/accounts/' + \
|
|
login_handle + '/.token'
|
|
try:
|
|
with open(token_filename, 'w+',
|
|
encoding='utf-8') as fp_tok:
|
|
fp_tok.write(token)
|
|
except OSError as ex:
|
|
print('EX: Unable to save token for ' +
|
|
login_nickname + ' ' + str(ex))
|
|
|
|
person_upgrade_actor(base_dir, None,
|
|
base_dir + '/accounts/' +
|
|
login_handle + '.json')
|
|
|
|
index = self.server.tokens[login_nickname]
|
|
self.server.tokens_lookup[index] = login_nickname
|
|
cookie_str = 'SET:epicyon=' + \
|
|
self.server.tokens[login_nickname] + '; SameSite=Strict'
|
|
tl_url = \
|
|
get_instance_url(calling_domain,
|
|
self.server.http_prefix,
|
|
self.server.domain_full,
|
|
self.server.onion_domain,
|
|
self.server.i2p_domain) + \
|
|
'/users/' + login_nickname + '/' + \
|
|
self.server.default_timeline
|
|
redirect_headers(self, tl_url, cookie_str, calling_domain)
|
|
self.server.postreq_busy = False
|
|
return
|
|
else:
|
|
print('WARN: No login credentials presented to /login')
|
|
if debug:
|
|
# be careful to avoid logging the password
|
|
login_str = login_params
|
|
if '=' in login_params:
|
|
login_params_list = login_params.split('=')
|
|
login_str = ''
|
|
skip_param = False
|
|
for login_prm in login_params_list:
|
|
if not skip_param:
|
|
login_str += login_prm + '='
|
|
else:
|
|
len_str = login_prm.split('&')[0]
|
|
if len(len_str) > 0:
|
|
login_str += login_prm + '*'
|
|
len_str = ''
|
|
if '&' in login_prm:
|
|
login_str += \
|
|
'&' + login_prm.split('&')[1] + '='
|
|
skip_param = False
|
|
if 'password' in login_prm:
|
|
skip_param = True
|
|
login_str = login_str[:len(login_str) - 1]
|
|
print(login_str)
|
|
http_401(self, 'No login credentials were posted')
|
|
self.server.postreq_busy = False
|
|
http_200(self)
|
|
self.server.postreq_busy = False
|