__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 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 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 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(css_cache: {}, base_dir: str,
                        following_filename: str) -> str:
    """Returns a list of handles being followed
    """
    with open(following_filename, 'r') 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(css_cache: {}, 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 get_post_attachments_as_html(base_dir: 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) -> (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 += ''
    return attachment_str, gallery_str
def html_post_separator(base_dir: str, column: str) -> str:
    """Returns the html for a timeline post separator image
    """
    theme = get_config_param(base_dir, 'theme')
    filename = 'separator.png'
    separator_class = "postSeparatorImage"
    if column:
        separator_class = "postSeparatorImage" + column.title()
        filename = 'separator_' + column + '.png'
    separator_image_filename = \
        base_dir + '/theme/' + theme + '/icons/' + filename
    separator_str = ''
    if os.path.isfile(separator_image_filename):
        separator_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