__filename__ = "webapp_utils.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.3.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
import os
from shutil import copyfile
from collections import OrderedDict
from session import get_json
from utils import remove_id_ending
from utils import get_attachment_property_value
from utils import is_account_dir
from utils import remove_html
from utils import get_protocol_prefixes
from utils import load_json
from utils import get_cached_post_filename
from utils import get_config_param
from utils import acct_dir
from utils import get_nickname_from_actor
from utils import get_domain_from_actor
from utils import is_float
from utils import get_audio_extensions
from utils import get_video_extensions
from utils import get_image_extensions
from utils import local_actor_url
from utils import text_in_file
from utils import remove_eol
from cache import store_person_in_cache
from content import add_html_tags
from content import replace_emoji_from_tags
from person import get_person_avatar_url
from posts import is_moderator
from blocking import is_blocked
def minimizing_attached_images(base_dir: str, nickname: str, domain: str,
                               following_nickname: str,
                               following_domain: str) -> bool:
    """Returns true if images from the account being followed should be
    minimized by default
    """
    if following_nickname == nickname and following_domain == domain:
        # reminder post
        return False
    minimize_filename = \
        acct_dir(base_dir, nickname, domain) + '/followingMinimizeImages.txt'
    handle = following_nickname + '@' + following_domain
    if not os.path.isfile(minimize_filename):
        following_filename = \
            acct_dir(base_dir, nickname, domain) + '/following.txt'
        if not os.path.isfile(following_filename):
            return False
        # create a new minimize file from the following file
        try:
            with open(minimize_filename, 'w+',
                      encoding='utf-8') as fp_min:
                fp_min.write('')
        except OSError:
            print('EX: minimizing_attached_images 2 ' + minimize_filename)
    return text_in_file(handle + '\n', minimize_filename)
def get_broken_link_substitute() -> str:
    """Returns html used to show a default image if the link to
    an image is broken
    """
    return " onerror=\"this.onerror=null; this.src='" + \
        "/icons/avatar_default.png'\""
def html_following_list(base_dir: str, following_filename: str) -> str:
    """Returns a list of handles being followed
    """
    with open(following_filename, 'r', encoding='utf-8') as following_file:
        msg = following_file.read()
        following_list = msg.split('\n')
        following_list.sort()
        if following_list:
            css_filename = base_dir + '/epicyon-profile.css'
            if os.path.isfile(base_dir + '/epicyon.css'):
                css_filename = base_dir + '/epicyon.css'
            instance_title = \
                get_config_param(base_dir, 'instanceTitle')
            following_list_html = \
                html_header_with_external_style(css_filename,
                                                instance_title, None)
            for following_address in following_list:
                if following_address:
                    following_list_html += \
                        '
@' + following_address + ' '
            following_list_html += html_footer()
            msg = following_list_html
        return msg
    return ''
def html_hashtag_blocked(base_dir: str, translate: {}) -> str:
    """Show the screen for a blocked hashtag
    """
    blocked_hashtag_form = ''
    css_filename = base_dir + '/epicyon-suspended.css'
    if os.path.isfile(base_dir + '/suspended.css'):
        css_filename = base_dir + '/suspended.css'
    instance_title = \
        get_config_param(base_dir, 'instanceTitle')
    blocked_hashtag_form = \
        html_header_with_external_style(css_filename, instance_title, None)
    blocked_hashtag_form += '\n'
    blocked_hashtag_form += html_footer()
    return blocked_hashtag_form
def header_buttons_front_screen(translate: {},
                                nickname: str, box_name: str,
                                authorized: bool,
                                icons_as_buttons: bool) -> str:
    """Returns the header buttons for the front page of a news instance
    """
    header_str = ''
    if nickname == 'news':
        button_features = 'buttonMobile'
        button_newswire = 'buttonMobile'
        button_links = 'buttonMobile'
        if box_name == 'features':
            button_features = 'buttonselected'
        elif box_name == 'newswire':
            button_newswire = 'buttonselected'
        elif box_name == 'links':
            button_links = 'buttonselected'
        header_str += \
            '        ' + \
            '' + \
            '' + translate['Features'] + \
            '   '
        if not authorized:
            header_str += \
                '        ' + \
                '' + \
                '' + translate['Login'] + \
                '   '
        if icons_as_buttons:
            header_str += \
                '        ' + \
                '' + \
                '' + translate['Newswire'] + \
                '   '
            header_str += \
                '        ' + \
                '' + \
                '' + translate['Links'] + \
                '   '
        else:
            header_str += \
                '        ' + \
                ' \n'
            header_str += \
                '        ' + \
                ' \n'
    else:
        if not authorized:
            header_str += \
                '        ' + \
                '' + \
                '' + translate['Login'] + \
                '   '
    if header_str:
        header_str = \
            '\n      \n' + \
            header_str + \
            '      
\n'
    return header_str
def get_content_warning_button(post_id: str, translate: {},
                               content: str) -> str:
    """Returns the markup for a content warning button
    """
    return '       ' + \
        translate['SHOW MORE'] + ' ' + \
        '' + content + \
        '
' + instance_title + ' \n' + \
        '  \n' + \
        '  \n'
    return html_str
def html_header_with_person_markup(css_filename: str, instance_title: str,
                                   actor_json: {}, city: str,
                                   content_license_url: str,
                                   lang='en') -> str:
    """html header which includes person markup
    https://schema.org/Person
    """
    if not actor_json:
        html_str = \
            html_header_with_external_style(css_filename,
                                            instance_title, None, lang)
        return html_str
    city_markup = ''
    if city:
        city = city.lower().title()
        add_comma = ''
        country_markup = ''
        if ',' in city:
            country = city.split(',', 1)[1].strip().title()
            city = city.split(',', 1)[0]
            country_markup = \
                '          "addressCountry": "' + country + '"\n'
            add_comma = ','
        city_markup = \
            '        "address": {\n' + \
            '          "@type": "PostalAddress",\n' + \
            '          "addressLocality": "' + city + '"' + \
            add_comma + '\n' + country_markup + '        },\n'
    skills_markup = ''
    if actor_json.get('hasOccupation'):
        if isinstance(actor_json['hasOccupation'], list):
            skills_markup = '        "hasOccupation": [\n'
            first_entry = True
            for skill_dict in actor_json['hasOccupation']:
                if skill_dict['@type'] == 'Role':
                    if not first_entry:
                        skills_markup += ',\n'
                    skl = skill_dict['hasOccupation']
                    role_name = skl['name']
                    if not role_name:
                        role_name = 'member'
                    category = \
                        skl['occupationalCategory']['codeValue']
                    category_url = \
                        'https://www.onetonline.org/link/summary/' + category
                    skills_markup += \
                        '        {\n' + \
                        '          "@type": "Role",\n' + \
                        '          "hasOccupation": {\n' + \
                        '            "@type": "Occupation",\n' + \
                        '            "name": "' + role_name + '",\n' + \
                        '            "description": ' + \
                        '"Fediverse instance role",\n' + \
                        '            "occupationLocation": {\n' + \
                        '              "@type": "City",\n' + \
                        '              "name": "' + city + '"\n' + \
                        '            },\n' + \
                        '            "occupationalCategory": {\n' + \
                        '              "@type": "CategoryCode",\n' + \
                        '              "inCodeSet": {\n' + \
                        '                "@type": "CategoryCodeSet",\n' + \
                        '                "name": "O*Net-SOC",\n' + \
                        '                "dateModified": "2019",\n' + \
                        '                ' + \
                        '"url": "https://www.onetonline.org/"\n' + \
                        '              },\n' + \
                        '              "codeValue": "' + category + '",\n' + \
                        '              "url": "' + category_url + '"\n' + \
                        '            }\n' + \
                        '          }\n' + \
                        '        }'
                elif skill_dict['@type'] == 'Occupation':
                    if not first_entry:
                        skills_markup += ',\n'
                    oc_name = skill_dict['name']
                    if not oc_name:
                        oc_name = 'member'
                    skills_list = skill_dict['skills']
                    skills_list_str = '['
                    for skill_str in skills_list:
                        if skills_list_str != '[':
                            skills_list_str += ', '
                        skills_list_str += '"' + skill_str + '"'
                    skills_list_str += ']'
                    skills_markup += \
                        '        {\n' + \
                        '          "@type": "Occupation",\n' + \
                        '          "name": "' + oc_name + '",\n' + \
                        '          "description": ' + \
                        '"Fediverse instance occupation",\n' + \
                        '          "occupationLocation": {\n' + \
                        '            "@type": "City",\n' + \
                        '            "name": "' + city + '"\n' + \
                        '          },\n' + \
                        '          "skills": ' + skills_list_str + '\n' + \
                        '        }'
                first_entry = False
            skills_markup += '\n        ],\n'
    description = remove_html(actor_json['summary'])
    name_str = remove_html(actor_json['name'])
    domain_full = actor_json['id'].split('://')[1].split('/')[0]
    handle = actor_json['preferredUsername'] + '@' + domain_full
    person_markup = \
        '      "about": {\n' + \
        '        "@type" : "Person",\n' + \
        '        "name": "' + name_str + '",\n' + \
        '        "image": "' + actor_json['icon']['url'] + '",\n' + \
        '        "description": "' + description + '",\n' + \
        city_markup + skills_markup + \
        '        "url": "' + actor_json['id'] + '"\n' + \
        '      },\n'
    profile_markup = \
        '    \n'
    description = remove_html(description)
    og_metadata = \
        "    ', '').replace('
', '')
    emoji_tags = {}
#    print('TAG: display_name before tags: ' + display_name)
    display_name = \
        add_html_tags(base_dir, http_prefix,
                      nickname, domain, display_name, [], emoji_tags)
    display_name = display_name.replace('', '').replace('
', '')
#    print('TAG: display_name after tags: ' + display_name)
    # convert the emoji dictionary to a list
    emoji_tags_list = []
    for _, tag in emoji_tags.items():
        emoji_tags_list.append(tag)
#    print('TAG: emoji tags list: ' + str(emoji_tags_list))
    if not in_profile_name:
        display_name = \
            replace_emoji_from_tags(session, base_dir,
                                    display_name, emoji_tags_list,
                                    'post header', False, False)
    else:
        display_name = \
            replace_emoji_from_tags(session, base_dir,
                                    display_name, emoji_tags_list, 'profile',
                                    False, False)
#    print('TAG: display_name after tags 2: ' + display_name)
    # remove any stray emoji
    while ':' in display_name:
        if '://' in display_name:
            break
        emoji_str = display_name.split(':')[1]
        prev_display_name = display_name
        display_name = display_name.replace(':' + emoji_str + ':', '').strip()
        if prev_display_name == display_name:
            break
#        print('TAG: display_name after tags 3: ' + display_name)
#    print('TAG: display_name after tag replacements: ' + display_name)
    return display_name
def _is_image_mime_type(mime_type: str) -> bool:
    """Is the given mime type an image?
    """
    if mime_type == 'image/svg+xml':
        return True
    if not mime_type.startswith('image/'):
        return False
    extensions = get_image_extensions()
    ext = mime_type.split('/')[1]
    if ext in extensions:
        return True
    return False
def _is_video_mime_type(mime_type: str) -> bool:
    """Is the given mime type a video?
    """
    if not mime_type.startswith('video/'):
        return False
    extensions = get_video_extensions()
    ext = mime_type.split('/')[1]
    if ext in extensions:
        return True
    return False
def _is_audio_mime_type(mime_type: str) -> bool:
    """Is the given mime type an audio file?
    """
    if mime_type == 'audio/mpeg':
        return True
    if not mime_type.startswith('audio/'):
        return False
    extensions = get_audio_extensions()
    ext = mime_type.split('/')[1]
    if ext in extensions:
        return True
    return False
def _is_attached_image(attachment_filename: str) -> bool:
    """Is the given attachment filename an image?
    """
    if '.' not in attachment_filename:
        return False
    image_ext = (
        'png', 'jpg', 'jpeg', 'webp', 'avif', 'svg', 'gif', 'jxl'
    )
    ext = attachment_filename.split('.')[-1]
    if ext in image_ext:
        return True
    return False
def _is_attached_video(attachment_filename: str) -> bool:
    """Is the given attachment filename a video?
    """
    if '.' not in attachment_filename:
        return False
    video_ext = (
        'mp4', 'webm', 'ogv'
    )
    ext = attachment_filename.split('.')[-1]
    if ext in video_ext:
        return True
    return False
def _is_nsfw(content: str) -> bool:
    """Does the given content indicate nsfw?
    """
    content_lower = content.lower()
    nsfw_tags = (
        'nsfw', 'porn', 'pr0n', 'explicit', 'lewd',
        'nude', 'boob', 'erotic', 'sex'
    )
    for tag_name in nsfw_tags:
        if tag_name in content_lower:
            return True
    return False
def get_post_attachments_as_html(base_dir: str,
                                 nickname: str, domain: str,
                                 domain_full: str,
                                 post_json_object: {}, box_name: str,
                                 translate: {},
                                 is_muted: bool, avatar_link: str,
                                 reply_str: str, announce_str: str,
                                 like_str: str,
                                 bookmark_str: str, delete_str: str,
                                 mute_str: str,
                                 content: str) -> (str, str):
    """Returns a string representing any attachments
    """
    attachment_str = ''
    gallery_str = ''
    if not post_json_object['object'].get('attachment'):
        return attachment_str, gallery_str
    if not isinstance(post_json_object['object']['attachment'], list):
        return attachment_str, gallery_str
    attachment_ctr = 0
    attachment_str = ''
    media_style_added = False
    post_id = None
    if post_json_object['object'].get('id'):
        post_id = post_json_object['object']['id']
        post_id = remove_id_ending(post_id).replace('/', '--')
    for attach in post_json_object['object']['attachment']:
        if not (attach.get('mediaType') and attach.get('url')):
            continue
        media_type = attach['mediaType']
        image_description = ''
        if attach.get('name'):
            image_description = attach['name'].replace('"', "'")
        if _is_image_mime_type(media_type):
            image_url = attach['url']
            # display svg images if they have first been rendered harmless
            svg_harmless = True
            if 'svg' in media_type:
                svg_harmless = False
                if '://' + domain_full + '/' in image_url:
                    svg_harmless = True
                else:
                    if post_id:
                        if '/' in image_url:
                            im_filename = image_url.split('/')[-1]
                        else:
                            im_filename = image_url
                        cached_svg_filename = \
                            base_dir + '/media/' + post_id + '_' + im_filename
                        if os.path.isfile(cached_svg_filename):
                            svg_harmless = True
            if _is_attached_image(attach['url']) and svg_harmless:
                if not attachment_str:
                    attachment_str += '\n'
                    media_style_added = True
                if attachment_ctr > 0:
                    attachment_str += '
'
                if box_name == 'tlmedia':
                    gallery_str += '
\n'
                    if not is_muted:
                        gallery_str += '  
\n'
                        gallery_str += \
                            '     \n'
                    if post_json_object['object'].get('url'):
                        image_post_url = post_json_object['object']['url']
                    else:
                        image_post_url = post_json_object['object']['id']
                    if image_description and not is_muted:
                        gallery_str += \
                            '  
' + \
                            image_description + '
\n'
                    else:
                        gallery_str += \
                            '
--- '
                    gallery_str += '  
\n'
                    gallery_str += \
                        '    ' + reply_str + announce_str + like_str + \
                        bookmark_str + delete_str + mute_str + '\n'
                    gallery_str += '  
\n'
                    gallery_str += '  
\n'
                    gallery_str += '    ' + avatar_link + '\n'
                    gallery_str += '  
\n'
                    gallery_str += '
\n'
                # optionally hide the image
                attributed_actor = None
                minimize_images = False
                if post_json_object['object'].get('attributedTo'):
                    if isinstance(post_json_object['object']['attributedTo'],
                                  str):
                        attributed_actor = \
                            post_json_object['object']['attributedTo']
                if attributed_actor:
                    following_nickname = \
                        get_nickname_from_actor(attributed_actor)
                    following_domain, _ = \
                        get_domain_from_actor(attributed_actor)
                    minimize_images = \
                        minimizing_attached_images(base_dir, nickname, domain,
                                                   following_nickname,
                                                   following_domain)
                # minimize any NSFW images
                if not minimize_images and content:
                    if _is_nsfw(content):
                        minimize_images = True
                if minimize_images:
                    show_img_str = 'SHOW MEDIA'
                    if translate:
                        show_img_str = translate['SHOW MEDIA']
                    attachment_str += \
                        '
' + \
                        show_img_str + ' ' + \
                        '\n'
                attachment_str += '
'
                attachment_str += \
                    ' \n'
                if minimize_images:
                    attachment_str += '
\n'
                attachment_ctr += 1
        elif _is_video_mime_type(media_type):
            if _is_attached_video(attach['url']):
                extension = attach['url'].split('.')[-1]
                if attachment_ctr > 0:
                    attachment_str += '
'
                if box_name == 'tlmedia':
                    gallery_str += '
\n'
                attachment_str += \
                    '
\n' + \
                    '    \n'
                attachment_str += \
                    ''
                attachment_str += \
                    translate['Your browser does not support the video tag.']
                attachment_str += '   '
                attachment_ctr += 1
        elif _is_audio_mime_type(media_type):
            extension = '.mp3'
            if attach['url'].endswith('.ogg'):
                extension = '.ogg'
            elif attach['url'].endswith('.opus'):
                extension = '.opus'
            elif attach['url'].endswith('.flac'):
                extension = '.flac'
            if attach['url'].endswith(extension):
                if attachment_ctr > 0:
                    attachment_str += '
'
                if box_name == 'tlmedia':
                    gallery_str += '
\n'
                attachment_str += '
\n\n'
                attachment_str += \
                    ''
                attachment_str += \
                    translate['Your browser does not support the audio tag.']
                attachment_str += '  \n \n'
                attachment_ctr += 1
    if media_style_added:
        attachment_str += '
' + \
            ' ' + html_str + ' '
def html_keyboard_navigation(banner: str, links: {}, access_keys: {},
                             sub_heading: str = None,
                             users_path: str = None, translate: {} = None,
                             follow_approvals: bool = False) -> str:
    """Given a set of links return the html for keyboard navigation
    """
    html_str = '\n'
    if banner:
        html_str += ' \n' + banner + '\n \n'
    if sub_heading:
        html_str += '
' + \
            sub_heading + ' \n'
    # show new follower approvals
    if users_path and translate and follow_approvals:
        html_str += '
' + \
            '' + \
            translate['Approve follow requests'] + ' ' + \
            ' \n'
    # show the list of links
    for title, url in links.items():
        access_key_str = ''
        if access_keys.get(title):
            access_key_str = 'accesskey="' + access_keys[title] + '"'
        html_str += '
' + \
            '' + \
            str(title) + '  \n'
    html_str += '
' + label + ' \n' + \
        ''
def end_edit_section() -> str:
    """returns the html for ending a dropdown section on edit profile screen
    """
    return '    
' + label + ' ' + label + ' ' + label + ' ' + label + ' \n'
    shared_items_form += \
        '
' + shared_item['displayName'] + '
\n'
    if shared_item.get('imageUrl'):
        shared_items_form += \
            '
\n'
        shared_items_form += \
            ' \n'
    shared_items_form += '
' + shared_item['summary'] + '
\n
'
    if shared_item.get('itemQty'):
        if shared_item['itemQty'] > 1:
            shared_items_form += \
                '' + translate['Quantity'] + \
                ':  ' + str(shared_item['itemQty']) + '' + translate['Type'] + ':  ' + shared_item['itemType'] + '' + translate['Category'] + ':  ' + \
        shared_item['category'] + '' + translate['Location'] + ':  ' + \
            shared_item['location'] + '' + translate['Price'] + \
                    ':  ' + shared_item['itemPrice'] + \
                    ' ' + shared_item['itemCurrency']
                contact_title_str = translate['Buy']
    shared_items_form += '
\n'
    contact_actor = \
        local_actor_url(http_prefix, contact_nickname, domain_full)
    button_style_str = 'button'
    if category == 'accommodation':
        contact_title_str = translate['Request to stay']
        button_style_str = 'contactbutton'
    shared_items_form += \
        '
' + \
        '' + contact_title_str + \
        ' ' + \
        translate['Profile'] + ' ' + \
                translate['Remove'] + ' ' + \
                translate['Remove'] + ' 
' + \
                ' \n'
            ctr += 1
        line_ctr += 1
    return html_str