__filename__ = "desktop_client.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"

import os
import html
import time
import sys
import select
import webbrowser
import urllib.parse
from pathlib import Path
from random import randint
from utils import getFullDomain
from utils import isDM
from utils import loadTranslationsFromFile
from utils import removeHtml
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import isPGPEncrypted
from session import createSession
from speaker import speakableText
from speaker import getSpeakerPitch
from speaker import getSpeakerRate
from speaker import getSpeakerRange
from like import sendLikeViaServer
from like import sendUndoLikeViaServer
from follow import approveFollowRequestViaServer
from follow import denyFollowRequestViaServer
from follow import getFollowRequestsViaServer
from follow import getFollowingViaServer
from follow import getFollowersViaServer
from follow import sendFollowRequestViaServer
from follow import sendUnfollowRequestViaServer
from posts import sendBlockViaServer
from posts import sendUndoBlockViaServer
from posts import sendMuteViaServer
from posts import sendUndoMuteViaServer
from posts import sendPostViaServer
from posts import c2sBoxJson
from posts import downloadAnnounce
from announce import sendAnnounceViaServer
from announce import sendUndoAnnounceViaServer
from pgp import pgpLocalPublicKey
from pgp import pgpDecrypt
from pgp import hasLocalPGPkey
from pgp import pgpEncryptToActor
from pgp import pgpPublicKeyUpload
from like import noOfLikes
from bookmarks import sendBookmarkViaServer
from bookmarks import sendUndoBookmarkViaServer
from delete import sendDeleteViaServer
from person import getActorJson


def _desktopHelp() -> None:
    """Shows help
    """
    _desktopClearScreen()
    indent = '   '
    print('')
    print(indent + _highlightText('Help Commands:'))
    print('')
    print(indent + 'quit                                  ' +
          'Exit from the desktop client')
    print(indent + 'show dm|sent|inbox|replies|bookmarks  ' +
          'Show a timeline')
    print(indent + 'mute                                  ' +
          'Turn off the screen reader')
    print(indent + 'speak                                 ' +
          'Turn on the screen reader')
    print(indent + 'sounds on                             ' +
          'Turn on notification sounds')
    print(indent + 'sounds off                            ' +
          'Turn off notification sounds')
    print(indent + 'rp                                    ' +
          'Repeat the last post')
    print(indent + 'like                                  ' +
          'Like the last post')
    print(indent + 'unlike                                ' +
          'Unlike the last post')
    print(indent + 'bookmark                              ' +
          'Bookmark the last post')
    print(indent + 'unbookmark                            ' +
          'Unbookmark the last post')
    print(indent + 'block [post number|handle]            ' +
          'Block someone via post number or handle')
    print(indent + 'unblock [handle]                      ' +
          'Unblock someone')
    print(indent + 'mute                                  ' +
          'Mute the last post')
    print(indent + 'unmute                                ' +
          'Unmute the last post')
    print(indent + 'reply                                 ' +
          'Reply to the last post')
    print(indent + 'post                                  ' +
          'Create a new post')
    print(indent + 'post to [handle]                      ' +
          'Create a new direct message')
    print(indent + 'announce/boost                        ' +
          'Boost the last post')
    print(indent + 'follow [handle]                       ' +
          'Make a follow request')
    print(indent + 'unfollow [handle]                     ' +
          'Stop following the give handle')
    print(indent + 'next                                  ' +
          'Next page in the timeline')
    print(indent + 'prev                                  ' +
          'Previous page in the timeline')
    print(indent + 'read [post number]                    ' +
          'Read a post from a timeline')
    print(indent + 'open [post number]                    ' +
          'Open web links within a timeline post')
    print(indent + 'profile [post number or handle]       ' +
          'Show profile for the person who made the given post')
    print(indent + 'following [page number]               ' +
          'Show accounts that you are following')
    print(indent + 'followers [page number]               ' +
          'Show accounts that are following you')
    print(indent + 'approve [handle]                      ' +
          'Approve a follow request')
    print(indent + 'deny [handle]                         ' +
          'Deny a follow request')
    print(indent + 'pgp                                   ' +
          'Show your PGP public key')
    print('')


def _createDesktopConfig(actor: str) -> None:
    """Sets up directories for desktop client configuration
    """
    homeDir = str(Path.home())
    if not os.path.isdir(homeDir + '/.config'):
        os.mkdir(homeDir + '/.config')
    if not os.path.isdir(homeDir + '/.config/epicyon'):
        os.mkdir(homeDir + '/.config/epicyon')
    nickname = getNicknameFromActor(actor)
    domain, port = getDomainFromActor(actor)
    handle = nickname + '@' + domain
    if port != 443 and port != 80:
        handle += '_' + str(port)
    readPostsDir = homeDir + '/.config/epicyon/' + handle
    if not os.path.isdir(readPostsDir):
        os.mkdir(readPostsDir)


def _markPostAsRead(actor: str, postId: str, postCategory: str) -> None:
    """Marks the given post as read by the given actor
    """
    homeDir = str(Path.home())
    _createDesktopConfig(actor)
    nickname = getNicknameFromActor(actor)
    domain, port = getDomainFromActor(actor)
    handle = nickname + '@' + domain
    if port != 443 and port != 80:
        handle += '_' + str(port)
    readPostsDir = homeDir + '/.config/epicyon/' + handle
    readPostsFilename = readPostsDir + '/' + postCategory + '.txt'
    if os.path.isfile(readPostsFilename):
        if postId in open(readPostsFilename).read():
            return
        try:
            # prepend to read posts file
            postId += '\n'
            with open(readPostsFilename, 'r+') as readFile:
                content = readFile.read()
                if postId not in content:
                    readFile.seek(0, 0)
                    readFile.write(postId + content)
        except Exception as e:
            print('WARN: Failed to mark post as read' + str(e))
    else:
        readFile = open(readPostsFilename, 'w+')
        if readFile:
            readFile.write(postId + '\n')
            readFile.close()


def _hasReadPost(actor: str, postId: str, postCategory: str) -> bool:
    """Returns true if the given post has been read by the actor
    """
    homeDir = str(Path.home())
    _createDesktopConfig(actor)
    nickname = getNicknameFromActor(actor)
    domain, port = getDomainFromActor(actor)
    handle = nickname + '@' + domain
    if port != 443 and port != 80:
        handle += '_' + str(port)
    readPostsDir = homeDir + '/.config/epicyon/' + handle
    readPostsFilename = readPostsDir + '/' + postCategory + '.txt'
    if os.path.isfile(readPostsFilename):
        if postId in open(readPostsFilename).read():
            return True
    return False


def _postIsToYou(actor: str, postJsonObject: {}) -> bool:
    """Returns true if the post is to the actor
    """
    toYourActor = False
    if postJsonObject.get('to'):
        if actor in postJsonObject['to']:
            toYourActor = True
    if not toYourActor and postJsonObject.get('cc'):
        if actor in postJsonObject['cc']:
            toYourActor = True
    if not toYourActor and postJsonObject.get('object'):
        if isinstance(postJsonObject['object'], dict):
            if postJsonObject['object'].get('to'):
                if actor in postJsonObject['object']['to']:
                    toYourActor = True
            if not toYourActor and postJsonObject['object'].get('cc'):
                if actor in postJsonObject['object']['cc']:
                    toYourActor = True
    return toYourActor


def _newDesktopNotifications(actor: str, inboxJson: {},
                             notifyJson: {}) -> None:
    """Looks for changes in the inbox and adds notifications
    """
    notifyJson['dmNotifyChanged'] = False
    notifyJson['repliesNotifyChanged'] = False
    if not inboxJson:
        return
    if not inboxJson.get('orderedItems'):
        return
    DMdone = False
    replyDone = False
    for postJsonObject in inboxJson['orderedItems']:
        if not postJsonObject.get('id'):
            continue
        if not postJsonObject.get('type'):
            continue
        if postJsonObject['type'] == 'Announce':
            continue
        if not _postIsToYou(actor, postJsonObject):
            continue
        if isDM(postJsonObject):
            if not DMdone:
                if not _hasReadPost(actor, postJsonObject['id'], 'dm'):
                    changed = False
                    if not notifyJson.get('dmPostId'):
                        changed = True
                    else:
                        if notifyJson['dmPostId'] != postJsonObject['id']:
                            changed = True
                    if changed:
                        notifyJson['dmNotify'] = True
                        notifyJson['dmNotifyChanged'] = True
                        notifyJson['dmPostId'] = postJsonObject['id']
                    DMdone = True
        else:
            if not replyDone:
                if not _hasReadPost(actor, postJsonObject['id'], 'replies'):
                    changed = False
                    if not notifyJson.get('repliesPostId'):
                        changed = True
                    else:
                        if notifyJson['repliesPostId'] != postJsonObject['id']:
                            changed = True
                    if changed:
                        notifyJson['repliesNotify'] = True
                        notifyJson['repliesNotifyChanged'] = True
                        notifyJson['repliesPostId'] = postJsonObject['id']
                    replyDone = True


def _desktopClearScreen() -> None:
    """Clears the screen
    """
    os.system('cls' if os.name == 'nt' else 'clear')


def _desktopShowBanner() -> None:
    """Shows the banner at the top
    """
    bannerFilename = 'banner.txt'
    if not os.path.isfile(bannerFilename):
        bannerTheme = 'starlight'
        bannerFilename = 'theme/' + bannerTheme + '/banner.txt'
        if not os.path.isfile(bannerFilename):
            return
    with open(bannerFilename, 'r') as bannerFile:
        banner = bannerFile.read()
        if banner:
            print(banner + '\n')


def _desktopWaitForCmd(timeout: int, debug: bool) -> str:
    """Waits for a command to be entered with a timeout
    Returns the command, or None on timeout
    """
    i, o, e = select.select([sys.stdin], [], [], timeout)

    if (i):
        text = sys.stdin.readline().strip()
        if debug:
            print("Text entered: " + text)
        return text
    else:
        if debug:
            print("Timeout")
        return None


def _speakerEspeak(espeak, pitch: int, rate: int, srange: int,
                   sayText: str) -> None:
    """Speaks the given text with espeak
    """
    espeak.set_parameter(espeak.Parameter.Pitch, pitch)
    espeak.set_parameter(espeak.Parameter.Rate, rate)
    espeak.set_parameter(espeak.Parameter.Range, srange)
    espeak.synth(html.unescape(sayText))


def _speakerPicospeaker(pitch: int, rate: int, systemLanguage: str,
                        sayText: str) -> None:
    """TTS using picospeaker
    """
    speakerLang = 'en-GB'
    if systemLanguage:
        if systemLanguage.startswith('fr'):
            speakerLang = 'fr-FR'
        elif systemLanguage.startswith('es'):
            speakerLang = 'es-ES'
        elif systemLanguage.startswith('de'):
            speakerLang = 'de-DE'
        elif systemLanguage.startswith('it'):
            speakerLang = 'it-IT'
    sayText = str(sayText).replace('"', "'")
    speakerCmd = 'picospeaker ' + \
        '-l ' + speakerLang + \
        ' -r ' + str(rate) + \
        ' -p ' + str(pitch) + ' "' + \
        html.unescape(str(sayText)) + '" 2> /dev/null'
    os.system(speakerCmd)


def _playNotificationSound(soundFilename: str, player='ffplay') -> None:
    """Plays a sound
    """
    if not os.path.isfile(soundFilename):
        return

    if player == 'ffplay':
        os.system('ffplay ' + soundFilename +
                  ' -autoexit -hide_banner -nodisp 2> /dev/null')


def _desktopNotification(notificationType: str,
                         title: str, message: str) -> None:
    """Shows a desktop notification
    """
    if not notificationType:
        return

    if notificationType == 'notify-send':
        # Ubuntu
        os.system('notify-send "' + title + '" "' + message + '"')
    elif notificationType == 'zenity':
        # Zenity
        os.system('zenity --notification --title "' + title +
                  '" --text="' + message + '"')
    elif notificationType == 'osascript':
        # Mac
        os.system("osascript -e 'display notification \"" +
                  message + "\" with title \"" + title + "\"'")
    elif notificationType == 'New-BurntToastNotification':
        # Windows
        os.system("New-BurntToastNotification -Text \"" +
                  title + "\", '" + message + "'")


def _textToSpeech(sayStr: str, screenreader: str,
                  pitch: int, rate: int, srange: int,
                  systemLanguage: str, espeak=None) -> None:
    """Say something via TTS
    """
    # speak the post content
    if screenreader == 'espeak':
        _speakerEspeak(espeak, pitch, rate, srange, sayStr)
    elif screenreader == 'picospeaker':
        _speakerPicospeaker(pitch, rate,
                            systemLanguage, sayStr)


def _sayCommand(content: str, sayStr: str, screenreader: str,
                systemLanguage: str,
                espeak=None,
                speakerName='screen reader',
                speakerGender='They/Them') -> None:
    """Speaks a command
    """
    print(content)
    if not screenreader:
        return

    pitch = getSpeakerPitch(speakerName,
                            screenreader, speakerGender)
    rate = getSpeakerRate(speakerName, screenreader)
    srange = getSpeakerRange(speakerName)

    _textToSpeech(sayStr, screenreader,
                  pitch, rate, srange,
                  systemLanguage, espeak)


def _desktopReplyToPost(session, postId: str,
                        baseDir: str, nickname: str, password: str,
                        domain: str, port: int, httpPrefix: str,
                        cachedWebfingers: {}, personCache: {},
                        debug: bool, subject: str,
                        screenreader: str, systemLanguage: str,
                        espeak) -> None:
    """Use the desktop client to send a reply to the most recent post
    """
    if '://' not in postId:
        return
    toNickname = getNicknameFromActor(postId)
    toDomain, toPort = getDomainFromActor(postId)
    sayStr = 'Replying to ' + toNickname + '@' + toDomain
    _sayCommand(sayStr, sayStr,
                screenreader, systemLanguage, espeak)
    sayStr = 'Type your reply message, then press Enter.'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    replyMessage = input()
    if not replyMessage:
        sayStr = 'No reply was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    replyMessage = replyMessage.strip()
    if not replyMessage:
        sayStr = 'No reply was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    print('')
    sayStr = 'You entered this reply:'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    _sayCommand(replyMessage, replyMessage, screenreader,
                systemLanguage, espeak)
    sayStr = 'Send this reply, yes or no?'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    yesno = input()
    if 'y' not in yesno.lower():
        sayStr = 'Abandoning reply'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    ccUrl = None
    followersOnly = False
    attach = None
    mediaType = None
    attachedImageDescription = None
    isArticle = False
    subject = None
    commentsEnabled = True
    city = 'London, England'
    sayStr = 'Sending reply'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    if sendPostViaServer(__version__,
                         baseDir, session, nickname, password,
                         domain, port,
                         toNickname, toDomain, toPort, ccUrl,
                         httpPrefix, replyMessage, followersOnly,
                         commentsEnabled, attach, mediaType,
                         attachedImageDescription, city,
                         cachedWebfingers, personCache, isArticle,
                         debug, postId, postId, subject) == 0:
        sayStr = 'Reply sent'
    else:
        sayStr = 'Reply failed'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)


def _desktopNewPost(session,
                    baseDir: str, nickname: str, password: str,
                    domain: str, port: int, httpPrefix: str,
                    cachedWebfingers: {}, personCache: {},
                    debug: bool,
                    screenreader: str, systemLanguage: str,
                    espeak) -> None:
    """Use the desktop client to create a new post
    """
    sayStr = 'Create new post'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    sayStr = 'Type your post, then press Enter.'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    newMessage = input()
    if not newMessage:
        sayStr = 'No post was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    newMessage = newMessage.strip()
    if not newMessage:
        sayStr = 'No post was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    print('')
    sayStr = 'You entered this public post:'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    _sayCommand(newMessage, newMessage, screenreader, systemLanguage, espeak)
    sayStr = 'Send this post, yes or no?'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    yesno = input()
    if 'y' not in yesno.lower():
        sayStr = 'Abandoning new post'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    ccUrl = None
    followersOnly = False
    attach = None
    mediaType = None
    attachedImageDescription = None
    city = 'London, England'
    isArticle = False
    subject = None
    commentsEnabled = True
    subject = None
    sayStr = 'Sending'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    if sendPostViaServer(__version__,
                         baseDir, session, nickname, password,
                         domain, port,
                         None, '#Public', port, ccUrl,
                         httpPrefix, newMessage, followersOnly,
                         commentsEnabled, attach, mediaType,
                         attachedImageDescription, city,
                         cachedWebfingers, personCache, isArticle,
                         debug, None, None, subject) == 0:
        sayStr = 'Post sent'
    else:
        sayStr = 'Post failed'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)


def _safeMessage(content: str) -> str:
    """Removes anything potentially unsafe from a string
    """
    return content.replace('`', '').replace('$(', '$ (')


def _timelineIsEmpty(boxJson: {}) -> bool:
    """Returns true if the given timeline is empty
    """
    empty = False
    if not boxJson:
        empty = True
    else:
        if not isinstance(boxJson, dict):
            empty = True
        elif not boxJson.get('orderedItems'):
            empty = True
    return empty


def _getFirstItemId(boxJson: {}) -> str:
    """Returns the id of the first item in the timeline
    """
    if _timelineIsEmpty(boxJson):
        return
    if len(boxJson['orderedItems']) == 0:
        return
    return boxJson['orderedItems'][0]['id']


def _textOnlyContent(content: str) -> str:
    """Remove formatting from the given string
    """
    content = urllib.parse.unquote_plus(content)
    content = html.unescape(content)
    return removeHtml(content)


def _getImageDescription(postJsonObject: {}) -> str:
    """Returns a image description/s on a post
    """
    imageDescription = ''
    if not postJsonObject['object'].get('attachment'):
        return imageDescription

    attachList = postJsonObject['object']['attachment']
    if not isinstance(attachList, list):
        return imageDescription

    # for each attachment
    for img in attachList:
        if not isinstance(img, dict):
            continue
        if not img.get('name'):
            continue
        if not isinstance(img['name'], str):
            continue
        messageStr = img['name']
        if messageStr:
            messageStr = messageStr.strip()
            if not messageStr.endswith('.'):
                imageDescription += messageStr + '. '
            else:
                imageDescription += messageStr + ' '
    return imageDescription


def _showLikesOnPost(postJsonObject: {}, maxLikes: int) -> None:
    """Shows the likes on a post
    """
    if not postJsonObject.get('object'):
        return
    if not isinstance(postJsonObject['object'], dict):
        return
    if not postJsonObject['object'].get('likes'):
        return
    if not isinstance(postJsonObject['object']['likes'], dict):
        return
    if not postJsonObject['object']['likes'].get('items'):
        return
    if not isinstance(postJsonObject['object']['likes']['items'], list):
        return
    print('')
    ctr = 0
    for item in postJsonObject['object']['likes']['items']:
        print('  ❤ ' + str(item['actor']))
        ctr += 1
        if ctr >= maxLikes:
            break


def _showRepliesOnPost(postJsonObject: {}, maxReplies: int) -> None:
    """Shows the replies on a post
    """
    if not postJsonObject.get('object'):
        return
    if not isinstance(postJsonObject['object'], dict):
        return
    if not postJsonObject['object'].get('replies'):
        return
    if not isinstance(postJsonObject['object']['replies'], dict):
        return
    if not postJsonObject['object']['replies'].get('items'):
        return
    if not isinstance(postJsonObject['object']['replies']['items'], list):
        return
    print('')
    ctr = 0
    for item in postJsonObject['object']['replies']['items']:
        print('  ↰ ' + str(item['url']))
        ctr += 1
        if ctr >= maxReplies:
            break


def _readLocalBoxPost(session, nickname: str, domain: str,
                      httpPrefix: str, baseDir: str, boxName: str,
                      pageNumber: int, index: int, boxJson: {},
                      systemLanguage: str,
                      screenreader: str, espeak,
                      translate: {}, yourActor: str) -> {}:
    """Reads a post from the given timeline
    Returns the post json
    """
    if _timelineIsEmpty(boxJson):
        return {}

    postJsonObject = _desktopGetBoxPostObject(boxJson, index)
    if not postJsonObject:
        return {}
    gender = 'They/Them'

    boxNameStr = boxName
    if boxName.startswith('tl'):
        boxNameStr = boxName[2:]
    sayStr = 'Reading ' + boxNameStr + ' post ' + str(index) + \
        ' from page ' + str(pageNumber) + '.'
    sayStr2 = sayStr.replace(' dm ', ' DM ')
    _sayCommand(sayStr, sayStr2, screenreader, systemLanguage, espeak)
    print('')

    if postJsonObject['type'] == 'Announce':
        actor = postJsonObject['actor']
        nameStr = getNicknameFromActor(actor)
        recentPostsCache = {}
        allowLocalNetworkAccess = False
        YTReplacementDomain = None
        postJsonObject2 = \
            downloadAnnounce(session, baseDir,
                             httpPrefix,
                             nickname, domain,
                             postJsonObject,
                             __version__, translate,
                             YTReplacementDomain,
                             allowLocalNetworkAccess,
                             recentPostsCache, False)
        if postJsonObject2:
            if postJsonObject2.get('object'):
                if postJsonObject2['object'].get('attributedTo') and \
                   postJsonObject2['object'].get('content'):
                    actor = postJsonObject2['object']['attributedTo']
                    nameStr += ' ' + translate['announces'] + ' ' + \
                        getNicknameFromActor(actor)
                    sayStr = nameStr
                    _sayCommand(sayStr, sayStr, screenreader,
                                systemLanguage, espeak)
                    print('')
                    if screenreader:
                        time.sleep(2)
                    content = \
                        _textOnlyContent(postJsonObject2['object']['content'])
                    content += _getImageDescription(postJsonObject2)
                    messageStr, detectedLinks = \
                        speakableText(baseDir, content, translate)
                    sayStr = content
                    _sayCommand(sayStr, messageStr, screenreader,
                                systemLanguage, espeak)
                    return postJsonObject2
        return {}

    actor = postJsonObject['object']['attributedTo']
    nameStr = getNicknameFromActor(actor)
    content = _textOnlyContent(postJsonObject['object']['content'])
    content += _getImageDescription(postJsonObject)

    if isPGPEncrypted(content):
        sayStr = 'Encrypted message. Please enter your passphrase.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        content = pgpDecrypt(content, actor)
        if isPGPEncrypted(content):
            sayStr = 'Message could not be decrypted'
            _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
            return {}

    content = _safeMessage(content)
    messageStr, detectedLinks = speakableText(baseDir, content, translate)

    if screenreader:
        time.sleep(2)

    # say the speaker's name
    _sayCommand(nameStr, nameStr, screenreader,
                systemLanguage, espeak, nameStr, gender)
    print('')

    if postJsonObject['object'].get('inReplyTo'):
        print('Replying to ' + postJsonObject['object']['inReplyTo'] + '\n')

    if screenreader:
        time.sleep(2)

    # speak the post content
    _sayCommand(content, messageStr, screenreader,
                systemLanguage, espeak, nameStr, gender)

    _showLikesOnPost(postJsonObject, 10)
    _showRepliesOnPost(postJsonObject, 10)

    # if the post is addressed to you then mark it as read
    if _postIsToYou(yourActor, postJsonObject):
        if isDM(postJsonObject):
            _markPostAsRead(yourActor, postJsonObject['id'], 'dm')
        else:
            _markPostAsRead(yourActor, postJsonObject['id'], 'replies')

    return postJsonObject


def _desktopShowActor(baseDir: str, actorJson: {}, translate: {},
                      systemLanguage: str, screenreader: str,
                      espeak) -> None:
    """Shows information for the given actor
    """
    actor = actorJson['id']
    actorNickname = getNicknameFromActor(actor)
    actorDomain, actorPort = getDomainFromActor(actor)
    actorDomainFull = getFullDomain(actorDomain, actorPort)
    handle = '@' + actorNickname + '@' + actorDomainFull

    sayStr = 'Profile for ' + html.unescape(handle)
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    print(actor)
    if actorJson.get('movedTo'):
        sayStr = 'Moved to ' + html.unescape(actorJson['movedTo'])
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    if actorJson.get('alsoKnownAs'):
        alsoKnownAsStr = ''
        ctr = 0
        for altActor in actorJson['alsoKnownAs']:
            if ctr > 0:
                alsoKnownAsStr += ', '
            ctr += 1
            alsoKnownAsStr += altActor

        sayStr = 'Also known as ' + html.unescape(alsoKnownAsStr)
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    if actorJson.get('summary'):
        sayStr = html.unescape(removeHtml(actorJson['summary']))
        sayStr = sayStr.replace('"', "'")
        sayStr2 = speakableText(baseDir, sayStr, translate)[0]
        _sayCommand(sayStr, sayStr2, screenreader, systemLanguage, espeak)


def _desktopShowProfile(session, nickname: str, domain: str,
                        httpPrefix: str, baseDir: str, boxName: str,
                        pageNumber: int, index: int, boxJson: {},
                        systemLanguage: str,
                        screenreader: str, espeak,
                        translate: {}, yourActor: str,
                        postJsonObject: {}) -> {}:
    """Shows the profile of the actor for the given post
    Returns the actor json
    """
    if _timelineIsEmpty(boxJson):
        return {}

    if not postJsonObject:
        postJsonObject = _desktopGetBoxPostObject(boxJson, index)
        if not postJsonObject:
            return {}

    actor = None
    if postJsonObject['type'] == 'Announce':
        nickname = getNicknameFromActor(postJsonObject['object'])
        if nickname:
            nickStr = '/' + nickname + '/'
            if nickStr in postJsonObject['object']:
                actor = \
                    postJsonObject['object'].split(nickStr)[0] + \
                    '/' + nickname
    else:
        actor = postJsonObject['object']['attributedTo']

    if not actor:
        return {}

    isHttp = False
    if 'http://' in actor:
        isHttp = True
    actorJson = getActorJson(actor, isHttp, False, False, True)

    _desktopShowActor(baseDir, actorJson, translate,
                      systemLanguage, screenreader, espeak)

    return actorJson


def _desktopShowProfileFromHandle(session, nickname: str, domain: str,
                                  httpPrefix: str, baseDir: str, boxName: str,
                                  handle: str,
                                  systemLanguage: str,
                                  screenreader: str, espeak,
                                  translate: {}, yourActor: str,
                                  postJsonObject: {}) -> {}:
    """Shows the profile for a handle
    Returns the actor json
    """
    actorJson = getActorJson(handle, False, False, False, True)

    _desktopShowActor(baseDir, actorJson, translate,
                      systemLanguage, screenreader, espeak)

    return actorJson


def _desktopGetBoxPostObject(boxJson: {}, index: int) -> {}:
    """Gets the post with the given index from the timeline
    """
    ctr = 0
    for postJsonObject in boxJson['orderedItems']:
        if not postJsonObject.get('type'):
            continue
        if not postJsonObject.get('object'):
            continue
        if postJsonObject['type'] == 'Announce':
            if not isinstance(postJsonObject['object'], str):
                continue
            ctr += 1
            if ctr == index:
                return postJsonObject
            continue
        if not isinstance(postJsonObject['object'], dict):
            continue
        if not postJsonObject['object'].get('published'):
            continue
        if not postJsonObject['object'].get('content'):
            continue
        ctr += 1
        if ctr == index:
            return postJsonObject
    return None


def _formatPublished(published: str) -> str:
    """Formats the published time for display on timeline
    """
    dateStr = published.split('T')[0]
    monthStr = dateStr.split('-')[1]
    dayStr = dateStr.split('-')[2]
    timeStr = published.split('T')[1]
    hourStr = timeStr.split(':')[0]
    minStr = timeStr.split(':')[1]
    return monthStr + '-' + dayStr + ' ' + hourStr + ':' + minStr + 'Z'


def _padToWidth(content: str, width: int) -> str:
    """Pads the given string to the given width
    """
    if len(content) > width:
        content = content[:width]
    else:
        while len(content) < width:
            content += ' '
    return content


def _highlightText(text: str) -> str:
    """Returns a highlighted version of the given text
    """
    return '\33[7m' + text + '\33[0m'


def _desktopShowBox(indent: str,
                    followRequestsJson: {},
                    yourActor: str, boxName: str, boxJson: {},
                    translate: {},
                    screenreader: str, systemLanguage: str, espeak,
                    pageNumber=1,
                    newReplies=False,
                    newDMs=False) -> bool:
    """Shows online timeline
    """
    numberWidth = 2
    nameWidth = 16
    contentWidth = 50

    # title
    _desktopClearScreen()
    _desktopShowBanner()

    notificationIcons = ''
    if boxName.startswith('tl'):
        boxNameStr = boxName[2:]
    else:
        boxNameStr = boxName
    titleStr = _highlightText(boxNameStr.upper())
    # if newDMs:
    #     notificationIcons += ' 📩'
    # if newReplies:
    #     notificationIcons += ' 📨'

    if notificationIcons:
        while len(titleStr) < 95 - len(notificationIcons):
            titleStr += ' '
        titleStr += notificationIcons
    print(indent + titleStr + '\n')

    if _timelineIsEmpty(boxJson):
        boxStr = boxNameStr
        if boxName == 'dm':
            boxStr = 'DM'
        sayStr = indent + 'You have no ' + boxStr + ' posts yet.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        print('')
        return False

    ctr = 1
    for postJsonObject in boxJson['orderedItems']:
        if not postJsonObject.get('type'):
            continue
        if postJsonObject['type'] == 'Announce':
            if postJsonObject.get('actor') and \
               postJsonObject.get('object'):
                if isinstance(postJsonObject['object'], str):
                    authorActor = postJsonObject['actor']
                    name = getNicknameFromActor(authorActor) + ' ⮌'
                    name = _padToWidth(name, nameWidth)
                    ctrStr = str(ctr)
                    posStr = _padToWidth(ctrStr, numberWidth)
                    published = _formatPublished(postJsonObject['published'])
                    announcedNickname = \
                        getNicknameFromActor(postJsonObject['object'])
                    announcedDomain, announcedPort = \
                        getDomainFromActor(postJsonObject['object'])
                    announcedHandle = announcedNickname + '@' + announcedDomain
                    lineStr = \
                        indent + str(posStr) + ' | ' + name + ' | ' + \
                        published + ' | ' + \
                        _padToWidth(announcedHandle, contentWidth)
                    print(lineStr)
                    ctr += 1
                    continue

        if not postJsonObject.get('object'):
            continue
        if not isinstance(postJsonObject['object'], dict):
            continue
        if not postJsonObject['object'].get('published'):
            continue
        if not postJsonObject['object'].get('content'):
            continue
        ctrStr = str(ctr)
        posStr = _padToWidth(ctrStr, numberWidth)

        authorActor = postJsonObject['object']['attributedTo']
        contentWarning = None
        if postJsonObject['object'].get('summary'):
            contentWarning = '⚡' + \
                _padToWidth(postJsonObject['object']['summary'],
                            contentWidth)
        name = getNicknameFromActor(authorActor)

        # append icons to the end of the name
        spaceAdded = False
        if postJsonObject['object'].get('inReplyTo'):
            if not spaceAdded:
                spaceAdded = True
                name += ' '
            name += '↲'
            if postJsonObject['object'].get('replies'):
                repliesList = postJsonObject['object']['replies']
                if repliesList.get('items'):
                    items = repliesList['items']
                    for i in range(int(items)):
                        name += '↰'
                        if i > 10:
                            break
        likesCount = noOfLikes(postJsonObject)
        if likesCount > 10:
            likesCount = 10
        for like in range(likesCount):
            if not spaceAdded:
                spaceAdded = True
                name += ' '
            name += '❤'
        name = _padToWidth(name, nameWidth)

        published = _formatPublished(postJsonObject['published'])

        content = _textOnlyContent(postJsonObject['object']['content'])
        if boxName != 'dm':
            if isDM(postJsonObject):
                content = '📧' + content
        if not contentWarning:
            if isPGPEncrypted(content):
                content = '🔒' + content
            elif '://' in content:
                content = '🔗' + content
            content = _padToWidth(content, contentWidth)
        else:
            # display content warning
            if isPGPEncrypted(content):
                content = '🔒' + contentWarning
            else:
                if '://' in content:
                    content = '🔗' + contentWarning
                else:
                    content = contentWarning
        if postJsonObject['object'].get('ignores'):
            content = '🔇'
        if postJsonObject['object'].get('bookmarks'):
            content = '🔖' + content
        if '\n' in content:
            content = content.replace('\n', ' ')
        lineStr = indent + str(posStr) + ' | ' + name + ' | ' + \
            published + ' | ' + content
        if boxName == 'inbox' and \
           _postIsToYou(yourActor, postJsonObject):
            if not _hasReadPost(yourActor, postJsonObject['id'], 'dm'):
                if not _hasReadPost(yourActor, postJsonObject['id'],
                                    'replies'):
                    lineStr = _highlightText(lineStr)
        print(lineStr)
        ctr += 1

    if followRequestsJson:
        _desktopShowFollowRequests(followRequestsJson, translate)

    print('')

    # say the post number range
    sayStr = indent + boxNameStr + ' page ' + str(pageNumber) + \
        ' containing ' + str(ctr - 1) + ' posts. '
    sayStr2 = sayStr.replace('\33[3m', '').replace('\33[0m', '')
    sayStr2 = sayStr2.replace('show dm', 'show DM')
    sayStr2 = sayStr2.replace('dm post', 'Direct message post')
    _sayCommand(sayStr, sayStr2, screenreader, systemLanguage, espeak)
    print('')
    return True


def _desktopNewDM(session, toHandle: str,
                  baseDir: str, nickname: str, password: str,
                  domain: str, port: int, httpPrefix: str,
                  cachedWebfingers: {}, personCache: {},
                  debug: bool,
                  screenreader: str, systemLanguage: str,
                  espeak) -> None:
    """Use the desktop client to create a new direct message
    which can include multiple destination handles
    """
    if ' ' in toHandle:
        handlesList = toHandle.split(' ')
    elif ',' in toHandle:
        handlesList = toHandle.split(',')
    elif ';' in toHandle:
        handlesList = toHandle.split(';')
    else:
        handlesList = [toHandle]

    for handle in handlesList:
        handle = handle.strip()
        _desktopNewDMbase(session, handle,
                          baseDir, nickname, password,
                          domain, port, httpPrefix,
                          cachedWebfingers, personCache,
                          debug,
                          screenreader, systemLanguage,
                          espeak)


def _desktopNewDMbase(session, toHandle: str,
                      baseDir: str, nickname: str, password: str,
                      domain: str, port: int, httpPrefix: str,
                      cachedWebfingers: {}, personCache: {},
                      debug: bool,
                      screenreader: str, systemLanguage: str,
                      espeak) -> None:
    """Use the desktop client to create a new direct message
    """
    toPort = port
    if '://' in toHandle:
        toNickname = getNicknameFromActor(toHandle)
        toDomain, toPort = getDomainFromActor(toHandle)
        toHandle = toNickname + '@' + toDomain
    else:
        if toHandle.startswith('@'):
            toHandle = toHandle[1:]
        toNickname = toHandle.split('@')[0]
        toDomain = toHandle.split('@')[1]

    sayStr = 'Create new direct message to ' + toHandle
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    sayStr = 'Type your direct message, then press Enter.'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    newMessage = input()
    if not newMessage:
        sayStr = 'No direct message was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    newMessage = newMessage.strip()
    if not newMessage:
        sayStr = 'No direct message was entered.'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return
    sayStr = 'You entered this direct message to ' + toHandle + ':'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    _sayCommand(newMessage, newMessage, screenreader, systemLanguage, espeak)
    ccUrl = None
    followersOnly = False
    attach = None
    mediaType = None
    attachedImageDescription = None
    city = 'London, England'
    isArticle = False
    subject = None
    commentsEnabled = True
    subject = None

    # if there is a local PGP key then attempt to encrypt the DM
    # using the PGP public key of the recipient
    if hasLocalPGPkey():
        sayStr = \
            'Local PGP key detected...' + \
            'Fetching PGP public key for ' + toHandle
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        paddedMessage = newMessage
        if len(paddedMessage) < 32:
            # add some padding before and after
            # This is to guard against cribs based on small messages, like "Hi"
            for before in range(randint(1, 16)):
                paddedMessage = ' ' + paddedMessage
            for after in range(randint(1, 16)):
                paddedMessage += ' '
        cipherText = \
            pgpEncryptToActor(paddedMessage, toHandle)
        if not cipherText:
            sayStr = \
                toHandle + ' has no PGP public key. ' + \
                'Your message will be sent in clear text'
            _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        else:
            newMessage = cipherText
            sayStr = 'Message encrypted'
            _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)

    sayStr = 'Send this direct message, yes or no?'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    yesno = input()
    if 'y' not in yesno.lower():
        sayStr = 'Abandoning new direct message'
        _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
        return

    sayStr = 'Sending'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
    if sendPostViaServer(__version__,
                         baseDir, session, nickname, password,
                         domain, port,
                         toNickname, toDomain, toPort, ccUrl,
                         httpPrefix, newMessage, followersOnly,
                         commentsEnabled, attach, mediaType,
                         attachedImageDescription, city,
                         cachedWebfingers, personCache, isArticle,
                         debug, None, None, subject) == 0:
        sayStr = 'Direct message sent'
    else:
        sayStr = 'Direct message failed'
    _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)


def _desktopShowFollowRequests(followRequestsJson: {}, translate: {}) -> None:
    """Shows any follow requests
    """
    if not isinstance(followRequestsJson, dict):
        return
    if not followRequestsJson.get('orderedItems'):
        return
    if not followRequestsJson['orderedItems']:
        return
    indent = '   '
    print('')
    print(indent + 'Follow requests:')
    print('')
    for item in followRequestsJson['orderedItems']:
        handleNickname = getNicknameFromActor(item)
        handleDomain, handlePort = getDomainFromActor(item)
        handleDomainFull = \
            getFullDomain(handleDomain, handlePort)
        print(indent + '  👤 ' +
              handleNickname + '@' + handleDomainFull)


def _desktopShowFollowing(followingJson: {}, translate: {},
                          pageNumber: int, indent: str,
                          followType='following') -> None:
    """Shows a page of accounts followed
    """
    if not isinstance(followingJson, dict):
        return
    if not followingJson.get('orderedItems'):
        return
    if not followingJson['orderedItems']:
        return
    print('')
    if followType == 'following':
        print(indent + 'Following page ' + str(pageNumber))
    elif followType == 'followers':
        print(indent + 'Followers page ' + str(pageNumber))
    print('')
    for item in followingJson['orderedItems']:
        handleNickname = getNicknameFromActor(item)
        handleDomain, handlePort = getDomainFromActor(item)
        handleDomainFull = \
            getFullDomain(handleDomain, handlePort)
        print(indent + '  👤 ' +
              handleNickname + '@' + handleDomainFull)


def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
                     nickname: str, domain: str, port: int,
                     password: str, screenreader: str,
                     systemLanguage: str,
                     notificationSounds: bool,
                     notificationType: str,
                     noKeyPress: bool,
                     storeInboxPosts: bool,
                     showNewPosts: bool,
                     language: str,
                     debug: bool) -> None:
    """Runs the desktop and screen reader client,
    which announces new inbox items
    """
    indent = '   '
    if showNewPosts:
        indent = ''

    _desktopClearScreen()
    _desktopShowBanner()

    espeak = None
    if screenreader:
        if screenreader == 'espeak':
            print('Setting up espeak')
            from espeak import espeak
        elif screenreader != 'picospeaker':
            print(screenreader + ' is not a supported TTS system')
            return

        sayStr = indent + 'Running ' + screenreader + ' for ' + \
            nickname + '@' + domain
        _sayCommand(sayStr, sayStr, screenreader,
                    systemLanguage, espeak)
    else:
        print(indent + 'Running desktop notifications for ' +
              nickname + '@' + domain)
    if notificationSounds:
        sayStr = indent + 'Notification sounds on'
    else:
        sayStr = indent + 'Notification sounds off'
    _sayCommand(sayStr, sayStr, screenreader,
                systemLanguage, espeak)

    currTimeline = 'inbox'
    pageNumber = 1

    postJsonObject = {}
    originalScreenReader = screenreader
    soundsDir = 'theme/default/sounds/'
    # prevSay = ''
    # prevCalendar = False
    # prevFollow = False
    # prevLike = ''
    # prevShare = False
    dmSoundFilename = soundsDir + 'dm.ogg'
    replySoundFilename = soundsDir + 'reply.ogg'
    # calendarSoundFilename = soundsDir + 'calendar.ogg'
    # followSoundFilename = soundsDir + 'follow.ogg'
    # likeSoundFilename = soundsDir + 'like.ogg'
    # shareSoundFilename = soundsDir + 'share.ogg'
    player = 'ffplay'
    nameStr = None
    gender = None
    messageStr = None
    content = None
    cachedWebfingers = {}
    personCache = {}
    newRepliesExist = False
    newDMsExist = False
    pgpKeyUpload = False

    sayStr = indent + 'Loading translations file'
    _sayCommand(sayStr, sayStr, screenreader,
                systemLanguage, espeak)
    translate, systemLanguage = \
        loadTranslationsFromFile(baseDir, language)

    sayStr = indent + 'Connecting...'
    _sayCommand(sayStr, sayStr, screenreader,
                systemLanguage, espeak)
    session = createSession(proxyType)

    sayStr = indent + '/q or /quit to exit'
    _sayCommand(sayStr, sayStr, screenreader,
                systemLanguage, espeak)

    domainFull = getFullDomain(domain, port)
    yourActor = httpPrefix + '://' + domainFull + '/users/' + nickname
    actorJson = None

    notifyJson = {
        "dmPostId": "Initial",
        "dmNotify": False,
        "dmNotifyChanged": False,
        "repliesPostId": "Initial",
        "repliesNotify": False,
        "repliesNotifyChanged": False
    }
    prevTimelineFirstId = ''
    desktopShown = False
    while (1):
        if not pgpKeyUpload:
            if not hasLocalPGPkey():
                print('No PGP public key was found')
            else:
                sayStr = indent + 'Uploading PGP public key'
                _sayCommand(sayStr, sayStr, screenreader,
                            systemLanguage, espeak)
                pgpPublicKeyUpload(baseDir, session,
                                   nickname, password,
                                   domain, port, httpPrefix,
                                   cachedWebfingers, personCache,
                                   debug, False)
                sayStr = indent + 'PGP public key uploaded'
                _sayCommand(sayStr, sayStr, screenreader,
                            systemLanguage, espeak)
            pgpKeyUpload = True

        boxJson = c2sBoxJson(baseDir, session,
                             nickname, password,
                             domain, port, httpPrefix,
                             currTimeline, pageNumber,
                             debug)

        followRequestsJson = \
            getFollowRequestsViaServer(baseDir, session,
                                       nickname, password,
                                       domain, port,
                                       httpPrefix, 1,
                                       cachedWebfingers, personCache,
                                       debug, __version__)

        if not (currTimeline == 'inbox' and pageNumber == 1):
            # monitor the inbox to generate notifications
            inboxJson = c2sBoxJson(baseDir, session,
                                   nickname, password,
                                   domain, port, httpPrefix,
                                   'inbox', 1, debug)
        else:
            inboxJson = boxJson
        newDMsExist = False
        newRepliesExist = False
        if inboxJson:
            _newDesktopNotifications(yourActor, inboxJson, notifyJson)
            if notifyJson.get('dmNotify'):
                newDMsExist = True
                if notifyJson.get('dmNotifyChanged'):
                    _desktopNotification(notificationType,
                                         "Epicyon",
                                         "New DM " + yourActor + '/dm')
                    if notificationSounds:
                        _playNotificationSound(dmSoundFilename, player)
            if notifyJson.get('repliesNotify'):
                newRepliesExist = True
                if notifyJson.get('repliesNotifyChanged'):
                    _desktopNotification(notificationType,
                                         "Epicyon",
                                         "New reply " + yourActor + '/replies')
                    if notificationSounds:
                        _playNotificationSound(replySoundFilename, player)

        if boxJson:
            timelineFirstId = _getFirstItemId(boxJson)
            if timelineFirstId != prevTimelineFirstId:
                _desktopClearScreen()
                _desktopShowBox(indent, followRequestsJson,
                                yourActor, currTimeline, boxJson,
                                translate,
                                None, systemLanguage, espeak,
                                pageNumber,
                                newRepliesExist,
                                newDMsExist)
                desktopShown = True
            prevTimelineFirstId = timelineFirstId
        else:
            session = createSession(proxyType)
            if not desktopShown:
                if not session:
                    print('No session\n')

                _desktopClearScreen()
                _desktopShowBanner()
                print('No posts\n')
                if proxyType == 'tor':
                    print('You may need to run the desktop client ' +
                          'with the --http option')

        # wait for a while, or until a key is pressed
        if noKeyPress:
            time.sleep(10)
        else:
            commandStr = _desktopWaitForCmd(30, debug)
        if commandStr:
            refreshTimeline = False

            if commandStr.startswith('/'):
                commandStr = commandStr[1:]
            if commandStr == 'q' or \
               commandStr == 'quit' or \
               commandStr == 'exit':
                sayStr = 'Quit'
                _sayCommand(sayStr, sayStr, screenreader,
                            systemLanguage, espeak)
                if screenreader:
                    commandStr = _desktopWaitForCmd(2, debug)
                break
            elif commandStr.startswith('show dm'):
                pageNumber = 1
                prevTimelineFirstId = ''
                currTimeline = 'dm'
                boxJson = c2sBoxJson(baseDir, session,
                                     nickname, password,
                                     domain, port, httpPrefix,
                                     currTimeline, pageNumber,
                                     debug)
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage, espeak,
                                    pageNumber,
                                    newRepliesExist, newDMsExist)
                newDMsExist = False
            elif commandStr.startswith('show rep'):
                pageNumber = 1
                prevTimelineFirstId = ''
                currTimeline = 'tlreplies'
                boxJson = c2sBoxJson(baseDir, session,
                                     nickname, password,
                                     domain, port, httpPrefix,
                                     currTimeline, pageNumber,
                                     debug)
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage, espeak,
                                    pageNumber,
                                    newRepliesExist, newDMsExist)
                # Turn off the replies indicator
                newRepliesExist = False
            elif commandStr.startswith('show b'):
                pageNumber = 1
                prevTimelineFirstId = ''
                currTimeline = 'tlbookmarks'
                boxJson = c2sBoxJson(baseDir, session,
                                     nickname, password,
                                     domain, port, httpPrefix,
                                     currTimeline, pageNumber,
                                     debug)
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage, espeak,
                                    pageNumber,
                                    newRepliesExist, newDMsExist)
                # Turn off the replies indicator
                newRepliesExist = False
            elif (commandStr.startswith('show sen') or
                  commandStr.startswith('show out')):
                pageNumber = 1
                prevTimelineFirstId = ''
                currTimeline = 'outbox'
                boxJson = c2sBoxJson(baseDir, session,
                                     nickname, password,
                                     domain, port, httpPrefix,
                                     currTimeline, pageNumber,
                                     debug)
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage, espeak,
                                    pageNumber,
                                    newRepliesExist, newDMsExist)
            elif (commandStr == 'show' or commandStr.startswith('show in') or
                  commandStr == 'clear'):
                pageNumber = 1
                prevTimelineFirstId = ''
                currTimeline = 'inbox'
                refreshTimeline = True
            elif commandStr.startswith('next'):
                pageNumber += 1
                prevTimelineFirstId = ''
                refreshTimeline = True
            elif commandStr.startswith('prev'):
                pageNumber -= 1
                if pageNumber < 1:
                    pageNumber = 1
                prevTimelineFirstId = ''
                boxJson = c2sBoxJson(baseDir, session,
                                     nickname, password,
                                     domain, port, httpPrefix,
                                     currTimeline, pageNumber,
                                     debug)
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage, espeak,
                                    pageNumber,
                                    newRepliesExist, newDMsExist)
            elif commandStr.startswith('read ') or commandStr == 'read':
                if commandStr == 'read':
                    postIndexStr = '1'
                else:
                    postIndexStr = commandStr.split('read ')[1]
                if boxJson and postIndexStr.isdigit():
                    _desktopClearScreen()
                    _desktopShowBanner()
                    postIndex = int(postIndexStr)
                    postJsonObject = \
                        _readLocalBoxPost(session, nickname, domain,
                                          httpPrefix, baseDir, currTimeline,
                                          pageNumber, postIndex, boxJson,
                                          systemLanguage, screenreader,
                                          espeak, translate, yourActor)
                    print('')
                    sayStr = 'Press Enter to continue...'
                    sayStr2 = _highlightText(sayStr)
                    _sayCommand(sayStr2, sayStr,
                                screenreader, systemLanguage, espeak)
                    input()
                    prevTimelineFirstId = ''
                    refreshTimeline = True
                print('')
            elif commandStr.startswith('profile ') or commandStr == 'profile':
                actorJson = None
                if commandStr == 'profile':
                    if postJsonObject:
                        actorJson = \
                            _desktopShowProfile(session, nickname, domain,
                                                httpPrefix, baseDir,
                                                currTimeline,
                                                pageNumber, postIndex,
                                                boxJson,
                                                systemLanguage, screenreader,
                                                espeak, translate, yourActor,
                                                postJsonObject)
                    else:
                        postIndexStr = '1'
                else:
                    postIndexStr = commandStr.split('profile ')[1]

                if not postIndexStr.isdigit():
                    profileHandle = postIndexStr
                    _desktopClearScreen()
                    _desktopShowBanner()
                    _desktopShowProfileFromHandle(session, nickname, domain,
                                                  httpPrefix, baseDir,
                                                  currTimeline, profileHandle,
                                                  systemLanguage, screenreader,
                                                  espeak, translate, yourActor,
                                                  None)
                    sayStr = 'Press Enter to continue...'
                    sayStr2 = _highlightText(sayStr)
                    _sayCommand(sayStr2, sayStr,
                                screenreader, systemLanguage, espeak)
                    input()
                    prevTimelineFirstId = ''
                    refreshTimeline = True
                elif not actorJson and boxJson:
                    _desktopClearScreen()
                    _desktopShowBanner()
                    postIndex = int(postIndexStr)
                    actorJson = \
                        _desktopShowProfile(session, nickname, domain,
                                            httpPrefix, baseDir, currTimeline,
                                            pageNumber, postIndex, boxJson,
                                            systemLanguage, screenreader,
                                            espeak, translate, yourActor,
                                            None)
                    sayStr = 'Press Enter to continue...'
                    sayStr2 = _highlightText(sayStr)
                    _sayCommand(sayStr2, sayStr,
                                screenreader, systemLanguage, espeak)
                    input()
                    prevTimelineFirstId = ''
                    refreshTimeline = True
                print('')
            elif commandStr == 'reply' or commandStr == 'r':
                if postJsonObject:
                    if postJsonObject.get('id'):
                        postId = postJsonObject['id']
                        subject = None
                        if postJsonObject['object'].get('summary'):
                            subject = postJsonObject['object']['summary']
                        sessionReply = createSession(proxyType)
                        _desktopReplyToPost(sessionReply, postId,
                                            baseDir, nickname, password,
                                            domain, port, httpPrefix,
                                            cachedWebfingers, personCache,
                                            debug, subject,
                                            screenreader, systemLanguage,
                                            espeak)
                refreshTimeline = True
                print('')
            elif (commandStr == 'post' or commandStr == 'p' or
                  commandStr == 'send' or
                  commandStr.startswith('dm ') or
                  commandStr.startswith('direct message ') or
                  commandStr.startswith('post ') or
                  commandStr.startswith('send ')):
                sessionPost = createSession(proxyType)
                if commandStr.startswith('dm ') or \
                   commandStr.startswith('direct message ') or \
                   commandStr.startswith('post ') or \
                   commandStr.startswith('send '):
                    commandStr = commandStr.replace(' to ', ' ')
                    commandStr = commandStr.replace(' dm ', ' ')
                    commandStr = commandStr.replace(' DM ', ' ')
                    # direct message
                    toHandle = None
                    if commandStr.startswith('post '):
                        toHandle = commandStr.split('post ', 1)[1]
                    elif commandStr.startswith('send '):
                        toHandle = commandStr.split('send ', 1)[1]
                    elif commandStr.startswith('dm '):
                        toHandle = commandStr.split('dm ', 1)[1]
                    elif commandStr.startswith('direct message '):
                        toHandle = commandStr.split('direct message ', 1)[1]
                    if toHandle:
                        _desktopNewDM(sessionPost, toHandle,
                                      baseDir, nickname, password,
                                      domain, port, httpPrefix,
                                      cachedWebfingers, personCache,
                                      debug,
                                      screenreader, systemLanguage,
                                      espeak)
                        refreshTimeline = True
                else:
                    # public post
                    _desktopNewPost(sessionPost,
                                    baseDir, nickname, password,
                                    domain, port, httpPrefix,
                                    cachedWebfingers, personCache,
                                    debug,
                                    screenreader, systemLanguage,
                                    espeak)
                    refreshTimeline = True
                print('')
            elif commandStr == 'like' or commandStr.startswith('like '):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        likeActor = postJsonObject['object']['attributedTo']
                        sayStr = 'Liking post by ' + \
                            getNicknameFromActor(likeActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionLike = createSession(proxyType)
                        sendLikeViaServer(baseDir, sessionLike,
                                          nickname, password,
                                          domain, port, httpPrefix,
                                          postJsonObject['id'],
                                          cachedWebfingers, personCache,
                                          False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr == 'undo mute' or
                  commandStr == 'undo ignore' or
                  commandStr == 'remove mute' or
                  commandStr == 'rm mute' or
                  commandStr == 'unmute' or
                  commandStr == 'unignore' or
                  commandStr == 'mute undo' or
                  commandStr.startswith('undo mute ') or
                  commandStr.startswith('undo ignore ') or
                  commandStr.startswith('remove mute ') or
                  commandStr.startswith('remove ignore ') or
                  commandStr.startswith('unignore ') or
                  commandStr.startswith('unmute ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        muteActor = postJsonObject['object']['attributedTo']
                        sayStr = 'Unmuting post by ' + \
                            getNicknameFromActor(muteActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionMute = createSession(proxyType)
                        sendUndoMuteViaServer(baseDir, sessionMute,
                                              nickname, password,
                                              domain, port,
                                              httpPrefix, postJsonObject['id'],
                                              cachedWebfingers, personCache,
                                              False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr == 'mute' or
                  commandStr == 'ignore' or
                  commandStr.startswith('mute ') or
                  commandStr.startswith('ignore ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        muteActor = postJsonObject['object']['attributedTo']
                        sayStr = 'Muting post by ' + \
                            getNicknameFromActor(muteActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionMute = createSession(proxyType)
                        sendMuteViaServer(baseDir, sessionMute,
                                          nickname, password,
                                          domain, port,
                                          httpPrefix, postJsonObject['id'],
                                          cachedWebfingers, personCache,
                                          False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr == 'undo bookmark' or
                  commandStr == 'remove bookmark' or
                  commandStr == 'rm bookmark' or
                  commandStr == 'undo bm' or
                  commandStr == 'rm bm' or
                  commandStr == 'remove bm' or
                  commandStr == 'unbookmark' or
                  commandStr == 'bookmark undo' or
                  commandStr == 'bm undo ' or
                  commandStr.startswith('undo bm ') or
                  commandStr.startswith('remove bm ') or
                  commandStr.startswith('undo bookmark ') or
                  commandStr.startswith('remove bookmark ') or
                  commandStr.startswith('unbookmark ') or
                  commandStr.startswith('unbm ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        bmActor = postJsonObject['object']['attributedTo']
                        sayStr = 'Unbookmarking post by ' + \
                            getNicknameFromActor(bmActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionbm = createSession(proxyType)
                        sendUndoBookmarkViaServer(baseDir, sessionbm,
                                                  nickname, password,
                                                  domain, port, httpPrefix,
                                                  postJsonObject['id'],
                                                  cachedWebfingers,
                                                  personCache,
                                                  False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr == 'bookmark' or
                  commandStr == 'bm' or
                  commandStr.startswith('bookmark ') or
                  commandStr.startswith('bm ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        bmActor = postJsonObject['object']['attributedTo']
                        sayStr = 'Bookmarking post by ' + \
                            getNicknameFromActor(bmActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionbm = createSession(proxyType)
                        sendBookmarkViaServer(baseDir, sessionbm,
                                              nickname, password,
                                              domain, port, httpPrefix,
                                              postJsonObject['id'],
                                              cachedWebfingers, personCache,
                                              False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr.startswith('undo block ') or
                  commandStr.startswith('remove block ') or
                  commandStr.startswith('rm block ') or
                  commandStr.startswith('unblock ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id') and \
                       postJsonObject.get('object'):
                        if isinstance(postJsonObject['object'], dict):
                            if postJsonObject['object'].get('attributedTo'):
                                blockActor = \
                                    postJsonObject['object']['attributedTo']
                                sayStr = 'Unblocking ' + \
                                    getNicknameFromActor(blockActor)
                                _sayCommand(sayStr, sayStr,
                                            screenreader,
                                            systemLanguage, espeak)
                                sessionBlock = createSession(proxyType)
                                sendUndoBlockViaServer(baseDir, sessionBlock,
                                                       nickname, password,
                                                       domain, port,
                                                       httpPrefix,
                                                       blockActor,
                                                       cachedWebfingers,
                                                       personCache,
                                                       False, __version__)
                refreshTimeline = True
                print('')
            elif commandStr.startswith('block '):
                blockActor = None
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                    else:
                        if '@' in postIndex:
                            blockHandle = postIndex
                            if blockHandle.startswith('@'):
                                blockHandle = blockHandle[1:]
                            if '@' in blockHandle:
                                blockDomain = blockHandle.split('@')[1]
                                blockNickname = blockHandle.split('@')[0]
                                blockActor = \
                                    httpPrefix + '://' + blockDomain + \
                                    '/users/' + blockNickname
                if currIndex > 0 and boxJson and not blockActor:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject and not blockActor:
                    if postJsonObject.get('id') and \
                       postJsonObject.get('object'):
                        if isinstance(postJsonObject['object'], dict):
                            if postJsonObject['object'].get('attributedTo'):
                                blockActor = \
                                    postJsonObject['object']['attributedTo']
                if blockActor:
                    sayStr = 'Blocking ' + \
                        getNicknameFromActor(blockActor)
                    _sayCommand(sayStr, sayStr,
                                screenreader,
                                systemLanguage, espeak)
                    sessionBlock = createSession(proxyType)
                    sendBlockViaServer(baseDir, sessionBlock,
                                       nickname, password,
                                       domain, port,
                                       httpPrefix,
                                       blockActor,
                                       cachedWebfingers,
                                       personCache,
                                       False, __version__)
                refreshTimeline = True
                print('')
            elif commandStr == 'unlike' or commandStr == 'undo like':
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        unlikeActor = postJsonObject['object']['attributedTo']
                        sayStr = \
                            'Undoing like of post by ' + \
                            getNicknameFromActor(unlikeActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionUnlike = createSession(proxyType)
                        sendUndoLikeViaServer(baseDir, sessionUnlike,
                                              nickname, password,
                                              domain, port, httpPrefix,
                                              postJsonObject['id'],
                                              cachedWebfingers, personCache,
                                              False, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr.startswith('announce') or
                  commandStr.startswith('boost') or
                  commandStr.startswith('retweet')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        postId = postJsonObject['id']
                        announceActor = \
                            postJsonObject['object']['attributedTo']
                        sayStr = 'Announcing post by ' + \
                            getNicknameFromActor(announceActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionAnnounce = createSession(proxyType)
                        sendAnnounceViaServer(baseDir, sessionAnnounce,
                                              nickname, password,
                                              domain, port,
                                              httpPrefix, postId,
                                              cachedWebfingers, personCache,
                                              True, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr.startswith('unannounce') or
                  commandStr.startswith('undo announce') or
                  commandStr.startswith('unboost') or
                  commandStr.startswith('undo boost') or
                  commandStr.startswith('undo retweet')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        postId = postJsonObject['id']
                        announceActor = \
                            postJsonObject['object']['attributedTo']
                        sayStr = 'Undoing announce post by ' + \
                            getNicknameFromActor(announceActor)
                        _sayCommand(sayStr, sayStr,
                                    screenreader,
                                    systemLanguage, espeak)
                        sessionAnnounce = createSession(proxyType)
                        sendUndoAnnounceViaServer(baseDir, sessionAnnounce,
                                                  postJsonObject,
                                                  nickname, password,
                                                  domain, port,
                                                  httpPrefix, postId,
                                                  cachedWebfingers,
                                                  personCache,
                                                  True, __version__)
                        refreshTimeline = True
                print('')
            elif (commandStr == 'follow requests' or
                  commandStr.startswith('follow requests ')):
                currPage = 1
                if ' ' in commandStr:
                    pageNum = commandStr.split(' ')[-1].strip()
                    if pageNum.isdigit():
                        currPage = int(pageNum)
                followRequestsJson = \
                    getFollowRequestsViaServer(baseDir, session,
                                               nickname, password,
                                               domain, port,
                                               httpPrefix, currPage,
                                               cachedWebfingers, personCache,
                                               debug, __version__)
                if followRequestsJson:
                    if isinstance(followRequestsJson, dict):
                        _desktopShowFollowRequests(followRequestsJson,
                                                   translate)
                print('')
            elif (commandStr == 'following' or
                  commandStr.startswith('following ')):
                currPage = 1
                if ' ' in commandStr:
                    pageNum = commandStr.split(' ')[-1].strip()
                    if pageNum.isdigit():
                        currPage = int(pageNum)
                followingJson = \
                    getFollowingViaServer(baseDir, session,
                                          nickname, password,
                                          domain, port,
                                          httpPrefix, currPage,
                                          cachedWebfingers, personCache,
                                          debug, __version__)
                if followingJson:
                    if isinstance(followingJson, dict):
                        _desktopShowFollowing(followingJson, translate,
                                              currPage, indent,
                                              'following')
                print('')
            elif (commandStr == 'followers' or
                  commandStr.startswith('followers ')):
                currPage = 1
                if ' ' in commandStr:
                    pageNum = commandStr.split(' ')[-1].strip()
                    if pageNum.isdigit():
                        currPage = int(pageNum)
                followersJson = \
                    getFollowersViaServer(baseDir, session,
                                          nickname, password,
                                          domain, port,
                                          httpPrefix, currPage,
                                          cachedWebfingers, personCache,
                                          debug, __version__)
                if followersJson:
                    if isinstance(followersJson, dict):
                        _desktopShowFollowing(followersJson, translate,
                                              currPage, indent,
                                              'followers')
                print('')
            elif (commandStr == 'follow' or
                  commandStr.startswith('follow ')):
                if commandStr == 'follow':
                    if actorJson:
                        followHandle = actorJson['id']
                    else:
                        followHandle = ''
                else:
                    followHandle = commandStr.replace('follow ', '').strip()
                    if followHandle.startswith('@'):
                        followHandle = followHandle[1:]

                if '@' in followHandle or '://' in followHandle:
                    followNickname = getNicknameFromActor(followHandle)
                    followDomain, followPort = \
                        getDomainFromActor(followHandle)
                    if followNickname and followDomain:
                        sayStr = 'Sending follow request to ' + \
                            followNickname + '@' + followDomain
                        _sayCommand(sayStr, sayStr,
                                    screenreader, systemLanguage, espeak)
                        sessionFollow = createSession(proxyType)
                        sendFollowRequestViaServer(baseDir,
                                                   sessionFollow,
                                                   nickname, password,
                                                   domain, port,
                                                   followNickname,
                                                   followDomain,
                                                   followPort,
                                                   httpPrefix,
                                                   cachedWebfingers,
                                                   personCache,
                                                   debug, __version__)
                    else:
                        if followHandle:
                            sayStr = followHandle + ' is not valid'
                        else:
                            sayStr = 'Specify a handle to follow'
                        _sayCommand(sayStr,
                                    screenreader, systemLanguage, espeak)
                    print('')
            elif (commandStr.startswith('unfollow ') or
                  commandStr.startswith('stop following ')):
                followHandle = commandStr.replace('unfollow ', '').strip()
                followHandle = followHandle.replace('stop following ', '')
                if followHandle.startswith('@'):
                    followHandle = followHandle[1:]
                if '@' in followHandle or '://' in followHandle:
                    followNickname = getNicknameFromActor(followHandle)
                    followDomain, followPort = \
                        getDomainFromActor(followHandle)
                    if followNickname and followDomain:
                        sayStr = 'Stop following ' + \
                            followNickname + '@' + followDomain
                        _sayCommand(sayStr, sayStr,
                                    screenreader, systemLanguage, espeak)
                        sessionUnfollow = createSession(proxyType)
                        sendUnfollowRequestViaServer(baseDir, sessionUnfollow,
                                                     nickname, password,
                                                     domain, port,
                                                     followNickname,
                                                     followDomain,
                                                     followPort,
                                                     httpPrefix,
                                                     cachedWebfingers,
                                                     personCache,
                                                     debug, __version__)
                    else:
                        sayStr = followHandle + ' is not valid'
                        _sayCommand(sayStr, sayStr,
                                    screenreader, systemLanguage, espeak)
                    print('')
            elif commandStr.startswith('approve '):
                approveHandle = commandStr.replace('approve ', '').strip()
                if approveHandle.startswith('@'):
                    approveHandle = approveHandle[1:]

                if '@' in approveHandle or '://' in approveHandle:
                    approveNickname = getNicknameFromActor(approveHandle)
                    approveDomain, approvePort = \
                        getDomainFromActor(approveHandle)
                    if approveNickname and approveDomain:
                        sayStr = 'Sending approve follow request for ' + \
                            approveNickname + '@' + approveDomain
                        _sayCommand(sayStr, sayStr,
                                    screenreader, systemLanguage, espeak)
                        sessionApprove = createSession(proxyType)
                        approveFollowRequestViaServer(baseDir, sessionApprove,
                                                      nickname, password,
                                                      domain, port,
                                                      httpPrefix,
                                                      approveHandle,
                                                      cachedWebfingers,
                                                      personCache,
                                                      debug,
                                                      __version__)
                    else:
                        if approveHandle:
                            sayStr = approveHandle + ' is not valid'
                        else:
                            sayStr = 'Specify a handle to approve'
                        _sayCommand(sayStr,
                                    screenreader, systemLanguage, espeak)
                    print('')
            elif commandStr.startswith('deny '):
                denyHandle = commandStr.replace('deny ', '').strip()
                if denyHandle.startswith('@'):
                    denyHandle = denyHandle[1:]

                if '@' in denyHandle or '://' in denyHandle:
                    denyNickname = getNicknameFromActor(denyHandle)
                    denyDomain, denyPort = \
                        getDomainFromActor(denyHandle)
                    if denyNickname and denyDomain:
                        sayStr = 'Sending deny follow request for ' + \
                            denyNickname + '@' + denyDomain
                        _sayCommand(sayStr, sayStr,
                                    screenreader, systemLanguage, espeak)
                        sessionDeny = createSession(proxyType)
                        denyFollowRequestViaServer(baseDir, sessionDeny,
                                                   nickname, password,
                                                   domain, port,
                                                   httpPrefix,
                                                   denyHandle,
                                                   cachedWebfingers,
                                                   personCache,
                                                   debug,
                                                   __version__)
                    else:
                        if denyHandle:
                            sayStr = denyHandle + ' is not valid'
                        else:
                            sayStr = 'Specify a handle to deny'
                        _sayCommand(sayStr,
                                    screenreader, systemLanguage, espeak)
                    print('')
            elif (commandStr == 'repeat' or commandStr == 'replay' or
                  commandStr == 'rp' or commandStr == 'again' or
                  commandStr == 'say again'):
                if screenreader and nameStr and \
                   gender and messageStr and content:
                    sayStr = 'Repeating ' + nameStr
                    _sayCommand(sayStr, sayStr, screenreader,
                                systemLanguage, espeak,
                                nameStr, gender)
                    time.sleep(2)
                    _sayCommand(content, messageStr, screenreader,
                                systemLanguage, espeak,
                                nameStr, gender)
                    print('')
            elif (commandStr == 'sounds on' or
                  commandStr == 'sound on' or
                  commandStr == 'sound'):
                sayStr = 'Notification sounds on'
                _sayCommand(sayStr, sayStr, screenreader,
                            systemLanguage, espeak)
                notificationSounds = True
            elif (commandStr == 'sounds off' or
                  commandStr == 'sound off' or
                  commandStr == 'nosound'):
                sayStr = 'Notification sounds off'
                _sayCommand(sayStr, sayStr, screenreader,
                            systemLanguage, espeak)
                notificationSounds = False
            elif (commandStr == 'speak' or
                  commandStr == 'screen reader on' or
                  commandStr == 'speaker on' or
                  commandStr == 'talker on' or
                  commandStr == 'reader on'):
                if originalScreenReader:
                    screenreader = originalScreenReader
                    sayStr = 'Screen reader on'
                    _sayCommand(sayStr, sayStr, screenreader,
                                systemLanguage, espeak)
                else:
                    print('No --screenreader option was specified')
            elif (commandStr == 'mute' or
                  commandStr == 'screen reader off' or
                  commandStr == 'speaker off' or
                  commandStr == 'talker off' or
                  commandStr == 'reader off'):
                if originalScreenReader:
                    screenreader = None
                    sayStr = 'Screen reader off'
                    _sayCommand(sayStr, sayStr, originalScreenReader,
                                systemLanguage, espeak)
                else:
                    print('No --screenreader option was specified')
            elif commandStr.startswith('open'):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject['type'] == 'Announce':
                        recentPostsCache = {}
                        allowLocalNetworkAccess = False
                        YTReplacementDomain = None
                        postJsonObject2 = \
                            downloadAnnounce(session, baseDir,
                                             httpPrefix,
                                             nickname, domain,
                                             postJsonObject,
                                             __version__, translate,
                                             YTReplacementDomain,
                                             allowLocalNetworkAccess,
                                             recentPostsCache, False)
                        if postJsonObject2:
                            postJsonObject = postJsonObject2
                if postJsonObject:
                    content = postJsonObject['object']['content']
                    messageStr, detectedLinks = \
                        speakableText(baseDir, content, translate)
                    linkOpened = False
                    for url in detectedLinks:
                        if '://' in url:
                            webbrowser.open(url)
                            linkOpened = True
                    if linkOpened:
                        sayStr = 'Opened web links'
                        _sayCommand(sayStr, sayStr, originalScreenReader,
                                    systemLanguage, espeak)
                    else:
                        sayStr = 'There are no web links to open.'
                        _sayCommand(sayStr, sayStr, originalScreenReader,
                                    systemLanguage, espeak)
                print('')
            elif commandStr.startswith('pgp') or commandStr.startswith('gpg'):
                if not hasLocalPGPkey():
                    print('No PGP public key was found')
                else:
                    print(pgpLocalPublicKey())
                print('')
            elif commandStr.startswith('h'):
                _desktopHelp()
                sayStr = 'Press Enter to continue...'
                sayStr2 = _highlightText(sayStr)
                _sayCommand(sayStr2, sayStr,
                            screenreader, systemLanguage, espeak)
                input()
                prevTimelineFirstId = ''
                refreshTimeline = True
            elif (commandStr == 'delete' or
                  commandStr == 'rm' or
                  commandStr.startswith('delete ') or
                  commandStr.startswith('rm ')):
                currIndex = 0
                if ' ' in commandStr:
                    postIndex = commandStr.split(' ')[-1].strip()
                    if postIndex.isdigit():
                        currIndex = int(postIndex)
                if currIndex > 0 and boxJson:
                    postJsonObject = \
                        _desktopGetBoxPostObject(boxJson, currIndex)
                if postJsonObject:
                    if postJsonObject.get('id'):
                        rmActor = postJsonObject['object']['attributedTo']
                        if rmActor != yourActor:
                            sayStr = 'You can only delete your own posts'
                            _sayCommand(sayStr, sayStr,
                                        screenreader,
                                        systemLanguage, espeak)
                        else:
                            print('')
                            if postJsonObject['object'].get('summary'):
                                print(postJsonObject['object']['summary'])
                            print(postJsonObject['object']['content'])
                            print('')
                            sayStr = 'Confirm delete, yes or no?'
                            _sayCommand(sayStr, sayStr, screenreader,
                                        systemLanguage, espeak)
                            yesno = input()
                            if 'y' not in yesno.lower():
                                sayStr = 'Deleting post'
                                _sayCommand(sayStr, sayStr,
                                            screenreader,
                                            systemLanguage, espeak)
                                sessionrm = createSession(proxyType)
                                sendDeleteViaServer(baseDir, sessionrm,
                                                    nickname, password,
                                                    domain, port,
                                                    httpPrefix,
                                                    postJsonObject['id'],
                                                    cachedWebfingers,
                                                    personCache,
                                                    False, __version__)
                                refreshTimeline = True
                print('')

            if refreshTimeline:
                if boxJson:
                    _desktopShowBox(indent, followRequestsJson,
                                    yourActor, currTimeline, boxJson,
                                    translate,
                                    screenreader, systemLanguage,
                                    espeak, pageNumber,
                                    newRepliesExist, newDMsExist)