__filename__ = "webapp_profile.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import os
from pprint import pprint
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import isSystemAccount
from utils import removeHtml
from utils import loadJson
from utils import getConfigParam
from skills import getSkills
from theme import getThemesList
from person import personBoxJson
from webfinger import webfingerHandle
from session import getJson
from posts import parseUserFeed
from posts import getUserUrl
from posts import getPersonBox
from donate import getDonationUrl
from xmpp import getXmppAddress
from matrix import getMatrixAddress
from ssb import getSSBAddress
from pgp import getEmailAddress
from pgp import getPGPfingerprint
from pgp import getPGPpubKey
from tox import getToxAddress
from webapp_utils import scheduledPostsExist
from webapp_utils import getPersonAvatarUrl
from webapp_utils import getIconsWebPath
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import addEmojiToDisplayName
from webapp_utils import headerButtonsFrontScreen
from webapp_utils import getBannerFile
from webapp_utils import htmlPostSeparator
from webapp_utils import getBlogAddress
from webapp_post import individualPostAsHtml
from webapp_column_left import getLeftColumnContent
from webapp_column_right import getRightColumnContent
from webapp_timeline import htmlIndividualShare
def htmlProfileAfterSearch(cssCache: {},
                           recentPostsCache: {}, maxRecentPosts: int,
                           translate: {},
                           baseDir: str, path: str, httpPrefix: str,
                           nickname: str, domain: str, port: int,
                           profileHandle: str,
                           session, cachedWebfingers: {}, personCache: {},
                           debug: bool, projectVersion: str,
                           YTReplacementDomain: str,
                           showPublishedDateOnly: bool,
                           defaultTimeline: str) -> str:
    """Show a profile page after a search for a fediverse address
    """
    if '/users/' in profileHandle or \
       '/accounts/' in profileHandle or \
       '/channel/' in profileHandle or \
       '/profile/' in profileHandle or \
       '/@' in profileHandle:
        searchNickname = getNicknameFromActor(profileHandle)
        searchDomain, searchPort = getDomainFromActor(profileHandle)
    else:
        if '@' not in profileHandle:
            print('DEBUG: no @ in ' + profileHandle)
            return None
        if profileHandle.startswith('@'):
            profileHandle = profileHandle[1:]
        if '@' not in profileHandle:
            print('DEBUG: no @ in ' + profileHandle)
            return None
        searchNickname = profileHandle.split('@')[0]
        searchDomain = profileHandle.split('@')[1]
        searchPort = None
        if ':' in searchDomain:
            searchPortStr = searchDomain.split(':')[1]
            if searchPortStr.isdigit():
                searchPort = int(searchPortStr)
            searchDomain = searchDomain.split(':')[0]
    if searchPort:
        print('DEBUG: Search for handle ' +
              str(searchNickname) + '@' + str(searchDomain) + ':' +
              str(searchPort))
    else:
        print('DEBUG: Search for handle ' +
              str(searchNickname) + '@' + str(searchDomain))
    if not searchNickname:
        print('DEBUG: No nickname found in ' + profileHandle)
        return None
    if not searchDomain:
        print('DEBUG: No domain found in ' + profileHandle)
        return None
    searchDomainFull = searchDomain
    if searchPort:
        if searchPort != 80 and searchPort != 443:
            if ':' not in searchDomain:
                searchDomainFull = searchDomain + ':' + str(searchPort)
    profileStr = ''
    cssFilename = baseDir + '/epicyon-profile.css'
    if os.path.isfile(baseDir + '/epicyon.css'):
        cssFilename = baseDir + '/epicyon.css'
    wf = \
        webfingerHandle(session,
                        searchNickname + '@' + searchDomainFull,
                        httpPrefix, cachedWebfingers,
                        domain, projectVersion)
    if not wf:
        print('DEBUG: Unable to webfinger ' +
              searchNickname + '@' + searchDomainFull)
        print('DEBUG: cachedWebfingers ' + str(cachedWebfingers))
        print('DEBUG: httpPrefix ' + httpPrefix)
        print('DEBUG: domain ' + domain)
        return None
    if not isinstance(wf, dict):
        print('WARN: Webfinger search for ' +
              searchNickname + '@' + searchDomainFull +
              ' did not return a dict. ' +
              str(wf))
        return None
    personUrl = None
    if wf.get('errors'):
        personUrl = httpPrefix + '://' + \
            searchDomainFull + '/users/' + searchNickname
    profileStr = 'https://www.w3.org/ns/activitystreams'
    asHeader = {
        'Accept': 'application/activity+json; profile="' + profileStr + '"'
    }
    if not personUrl:
        personUrl = getUserUrl(wf)
    if not personUrl:
        # try single user instance
        asHeader = {
            'Accept': 'application/ld+json; profile="' + profileStr + '"'
        }
        personUrl = httpPrefix + '://' + searchDomainFull
    profileJson = \
        getJson(session, personUrl, asHeader, None,
                projectVersion, httpPrefix, domain)
    if not profileJson:
        asHeader = {
            'Accept': 'application/ld+json; profile="' + profileStr + '"'
        }
        profileJson = \
            getJson(session, personUrl, asHeader, None,
                    projectVersion, httpPrefix, domain)
    if not profileJson:
        print('DEBUG: No actor returned from ' + personUrl)
        return None
    avatarUrl = ''
    if profileJson.get('icon'):
        if profileJson['icon'].get('url'):
            avatarUrl = profileJson['icon']['url']
    if not avatarUrl:
        avatarUrl = getPersonAvatarUrl(baseDir, personUrl,
                                       personCache, True)
    displayName = searchNickname
    if profileJson.get('name'):
        displayName = profileJson['name']
    profileDescription = ''
    if profileJson.get('summary'):
        profileDescription = profileJson['summary']
    outboxUrl = None
    if not profileJson.get('outbox'):
        if debug:
            pprint(profileJson)
            print('DEBUG: No outbox found')
        return None
    outboxUrl = profileJson['outbox']
    # profileBackgroundImage = ''
    # if profileJson.get('image'):
    #     if profileJson['image'].get('url'):
    #         profileBackgroundImage = profileJson['image']['url']
    # url to return to
    backUrl = path
    if not backUrl.endswith('/inbox'):
        backUrl += '/inbox'
    profileDescriptionShort = profileDescription
    if '\n' in profileDescription:
        if len(profileDescription.split('\n')) > 2:
            profileDescriptionShort = ''
    else:
        if '
' in profileDescription:
            if len(profileDescription.split('
')) > 2:
                profileDescriptionShort = ''
    # keep the profile description short
    if len(profileDescriptionShort) > 256:
        profileDescriptionShort = ''
    # remove formatting from profile description used on title
    avatarDescription = ''
    if profileJson.get('summary'):
        if isinstance(profileJson['summary'], str):
            avatarDescription = \
                profileJson['summary'].replace('
', '\n')
            avatarDescription = avatarDescription.replace('
', '') avatarDescription = avatarDescription.replace('
', '') if '<' in avatarDescription: avatarDescription = removeHtml(avatarDescription) imageUrl = '' if profileJson.get('image'): if profileJson['image'].get('url'): imageUrl = profileJson['image']['url'] profileStr = \ getProfileHeaderAfterSearch(baseDir, nickname, defaultTimeline, searchNickname, searchDomainFull, translate, displayName, profileDescriptionShort, avatarUrl, imageUrl) domainFull = domain if port: if port != 80 and port != 443: domainFull = domain + ':' + str(port) followIsPermitted = True if searchNickname == 'news' and searchDomainFull == domainFull: # currently the news actor is not something you can follow followIsPermitted = False elif searchNickname == nickname and searchDomainFull == domainFull: # don't follow yourself! followIsPermitted = False if followIsPermitted: profileStr += '@' + nickname + '@' + domainFull + '
\n'
    htmlStr += \
        '    ' + \
        '
' + profileDescriptionShort + '
\n' htmlStr += loginButton htmlStr += '@' + searchNickname + '@' + searchDomainFull + '
\n'
    htmlStr += '        
' + profileDescriptionShort + '
\n' htmlStr += '' + translate['Email'] + ': ' + emailAddress + '
\n' if xmppAddress: donateSection += \ '' + translate['XMPP'] + ': '+xmppAddress + '
\n' if matrixAddress: donateSection += \ '' + translate['Matrix'] + ': ' + matrixAddress + '
\n' if ssbAddress: donateSection += \ 'SSB:
\n' if toxAddress: donateSection += \ 'Tox:
\n' if PGPfingerprint: donateSection += \ 'PGP: ' + \
                PGPfingerprint.replace('\n', '
') + '
' + PGPpubKey.replace('\n', '
') + '
![' + translate['Edit'] + \
            ' | ' + translate['Edit'] + '](/' + iconsPath + \
            '/edit.png) \n'
        logoutStr = \
            '' + \
            '
\n'
        logoutStr = \
            '' + \
            '![' + translate['Logout'] + \
            ' | ' + translate['Logout'] + \
            '](/' + iconsPath + \
            '/logout.png) \n'
        # are there any follow requests?
        followRequestsFilename = \
            baseDir + '/accounts/' + \
            nickname + '@' + domain + '/followrequests.txt'
        if os.path.isfile(followRequestsFilename):
            with open(followRequestsFilename, 'r') as f:
                for line in f:
                    if len(line) > 0:
                        followApprovals = True
                        followersButton = 'buttonhighlighted'
                        if selected == 'followers':
                            followersButton = 'buttonselectedhighlighted'
                        break
        if selected == 'followers':
            if followApprovals:
                with open(followRequestsFilename, 'r') as f:
                    for followerHandle in f:
                        if len(line) > 0:
                            if '://' in followerHandle:
                                followerActor = followerHandle
                            else:
                                followerActor = \
                                    httpPrefix + '://' + \
                                    followerHandle.split('@')[1] + \
                                    '/users/' + followerHandle.split('@')[0]
                            basePath = '/users/' + nickname
                            followApprovalsSection += '
\n'
        # are there any follow requests?
        followRequestsFilename = \
            baseDir + '/accounts/' + \
            nickname + '@' + domain + '/followrequests.txt'
        if os.path.isfile(followRequestsFilename):
            with open(followRequestsFilename, 'r') as f:
                for line in f:
                    if len(line) > 0:
                        followApprovals = True
                        followersButton = 'buttonhighlighted'
                        if selected == 'followers':
                            followersButton = 'buttonselectedhighlighted'
                        break
        if selected == 'followers':
            if followApprovals:
                with open(followRequestsFilename, 'r') as f:
                    for followerHandle in f:
                        if len(line) > 0:
                            if '://' in followerHandle:
                                followerActor = followerHandle
                            else:
                                followerActor = \
                                    httpPrefix + '://' + \
                                    followerHandle.split('@')[1] + \
                                    '/users/' + followerHandle.split('@')[0]
                            basePath = '/users/' + nickname
                            followApprovalsSection += '', '') avatarDescription = avatarDescription.replace('
', '') # If this is the news account then show a different banner if isSystemAccount(nickname): bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) profileHeaderStr = \ '| \n' iconsPath = getIconsWebPath(baseDir) profileHeaderStr += \ getLeftColumnContent(baseDir, 'news', domainFull, httpPrefix, translate, iconsPath, False, False, None, rssIconAtTop, True, True) profileHeaderStr += '\n' profileHeaderStr += ' | \n'
    else:
        avatarUrl = profileJson['icon']['url']
        profileHeaderStr = \
            getProfileHeader(baseDir, nickname, domain,
                             domainFull, translate, iconsPath,
                             defaultTimeline, displayName,
                             avatarDescription,
                             profileDescriptionShort,
                             loginButton, avatarUrl)
    profileStr = profileHeaderStr + donateSection
    if not isSystemAccount(nickname):
        profileStr += ' \n'
        profileFooterStr += '\n'
        profileStr += '  '
    profileStr += followApprovalsSection
    cssFilename = baseDir + '/epicyon-profile.css'
    if os.path.isfile(baseDir + '/epicyon.css'):
        cssFilename = baseDir + '/epicyon.css'
    if isSystemAccount(nickname):
        bannerFile, bannerFilename = \
            getBannerFile(baseDir, nickname, domain)
    licenseStr = \
        '' + \
        ' ![' + \
        translate['Get the source code'] + ' ' + \
        translate['Get the source code'] + '](/icons/agpl.png) '
    if selected == 'posts':
        profileStr += \
            htmlProfilePosts(recentPostsCache, maxRecentPosts,
                             translate,
                             baseDir, httpPrefix, authorized,
                             nickname, domain, port,
                             session, wfRequest, personCache,
                             projectVersion,
                             YTReplacementDomain,
                             showPublishedDateOnly) + licenseStr
    if selected == 'following':
        profileStr += \
            htmlProfileFollowing(translate, baseDir, httpPrefix,
                                 authorized, nickname,
                                 domain, port, session,
                                 wfRequest, personCache, extraJson,
                                 projectVersion, ["unfollow"], selected,
                                 usersPath, pageNumber, maxItemsPerPage)
    if selected == 'followers':
        profileStr += \
            htmlProfileFollowing(translate, baseDir, httpPrefix,
                                 authorized, nickname,
                                 domain, port, session,
                                 wfRequest, personCache, extraJson,
                                 projectVersion, ["block"],
                                 selected, usersPath, pageNumber,
                                 maxItemsPerPage)
    if selected == 'roles':
        profileStr += \
            htmlProfileRoles(translate, nickname, domainFull, extraJson)
    if selected == 'skills':
        profileStr += \
            htmlProfileSkills(translate, nickname, domainFull, extraJson)
    if selected == 'shares':
        profileStr += \
            htmlProfileShares(actor, translate,
                              nickname, domainFull,
                              extraJson) + licenseStr
    # Footer which is only used for system accounts
    profileFooterStr = ''
    if isSystemAccount(nickname):
        profileFooterStr = ' | \n' iconsPath = getIconsWebPath(baseDir) profileFooterStr += \ getRightColumnContent(baseDir, 'news', domainFull, httpPrefix, translate, iconsPath, False, False, newswire, False, False, None, False, False, False, True, authorized, True) profileFooterStr += '\n' profileFooterStr += ' | 
![' + \
                translate['Page up'] + ' ' + \
                translate['Page up'] + '](/' + \
                iconsPath + '/pageup.png) \n' + \
                '
\n' + \
                '  ![' + \
                translate['Page down'] + ' ' + \
                translate['Page down'] + '](/' + \
                iconsPath + '/pagedown.png) \n' + \
                '
\n' + \
                '  @' + nickname + '@' + domain + ' has no roles assigned
\n' else: profileStr = '', '').replace('
', '') if actorJson.get('manuallyApprovesFollowers'): if actorJson['manuallyApprovesFollowers']: manuallyApprovesFollowers = 'checked' else: manuallyApprovesFollowers = '' if actorJson.get('type'): if actorJson['type'] == 'Service': isBot = 'checked' isGroup = '' elif actorJson['type'] == 'Group': isGroup = 'checked' isBot = '' if os.path.isfile(baseDir + '/accounts/' + nickname + '@' + domain + '/.followDMs'): followDMs = 'checked' if os.path.isfile(baseDir + '/accounts/' + nickname + '@' + domain + '/.removeTwitter'): removeTwitter = 'checked' if os.path.isfile(baseDir + '/accounts/' + nickname + '@' + domain + '/.notifyLikes'): notifyLikes = 'checked' if os.path.isfile(baseDir + '/accounts/' + nickname + '@' + domain + '/.hideLikeButton'): hideLikeButton = 'checked' mediaInstance = getConfigParam(baseDir, "mediaInstance") if mediaInstance: if mediaInstance is True: mediaInstanceStr = 'checked' blogsInstanceStr = '' newsInstanceStr = '' newsInstance = getConfigParam(baseDir, "newsInstance") if newsInstance: if newsInstance is True: newsInstanceStr = 'checked' blogsInstanceStr = '' mediaInstanceStr = '' blogsInstance = getConfigParam(baseDir, "blogsInstance") if blogsInstance: if blogsInstance is True: blogsInstanceStr = 'checked' mediaInstanceStr = '' newsInstanceStr = '' filterStr = '' filterFilename = \ baseDir + '/accounts/' + nickname + '@' + domain + '/filters.txt' if os.path.isfile(filterFilename): with open(filterFilename, 'r') as filterfile: filterStr = filterfile.read() switchStr = '' switchFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/replacewords.txt' if os.path.isfile(switchFilename): with open(switchFilename, 'r') as switchfile: switchStr = switchfile.read() autoTags = '' autoTagsFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/autotags.txt' if os.path.isfile(autoTagsFilename): with open(autoTagsFilename, 'r') as autoTagsFile: autoTags = autoTagsFile.read() autoCW = '' autoCWFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/autocw.txt' if os.path.isfile(autoCWFilename): with open(autoCWFilename, 'r') as autoCWFile: autoCW = autoCWFile.read() blockedStr = '' blockedFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/blocking.txt' if os.path.isfile(blockedFilename): with open(blockedFilename, 'r') as blockedfile: blockedStr = blockedfile.read() allowedInstancesStr = '' allowedInstancesFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/allowedinstances.txt' if os.path.isfile(allowedInstancesFilename): with open(allowedInstancesFilename, 'r') as allowedInstancesFile: allowedInstancesStr = allowedInstancesFile.read() gitProjectsStr = '' gitProjectsFilename = \ baseDir + '/accounts/' + \ nickname + '@' + domain + '/gitprojects.txt' if os.path.isfile(gitProjectsFilename): with open(gitProjectsFilename, 'r') as gitProjectsFile: gitProjectsStr = gitProjectsFile.read() skills = getSkills(baseDir, nickname, domain) skillsStr = '' skillCtr = 1 if skills: for skillDesc, skillValue in skills.items(): skillsStr += \ '' skillsStr += \ '
' skillCtr += 1 skillsStr += \ '' skillsStr += \ '
' cssFilename = baseDir + '/epicyon-profile.css' if os.path.isfile(baseDir + '/epicyon.css'): cssFilename = baseDir + '/epicyon.css' moderatorsStr = '' themesDropdown = '' instanceStr = '' adminNickname = getConfigParam(baseDir, 'admin') if adminNickname: if path.startswith('/users/' + adminNickname + '/'): instanceDescription = \ getConfigParam(baseDir, 'instanceDescription') instanceDescriptionShort = \ getConfigParam(baseDir, 'instanceDescriptionShort') instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') instanceStr += '