mirror of https://gitlab.com/bashrc2/epicyon
2426 lines
103 KiB
Python
2426 lines
103 KiB
Python
__filename__ = "desktop_client.py"
|
|
__author__ = "Bob Mottram"
|
|
__license__ = "AGPL3+"
|
|
__version__ = "1.2.0"
|
|
__maintainer__ = "Bob Mottram"
|
|
__email__ = "bob@freedombone.net"
|
|
__status__ = "Production"
|
|
__module_group__ = "Client"
|
|
|
|
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 getBaseContentFromPost
|
|
from utils import hasObjectDict
|
|
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:
|
|
with open(readPostsFilename, 'w+') as readFile:
|
|
readFile.write(postId + '\n')
|
|
|
|
|
|
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 hasObjectDict(postJsonObject):
|
|
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: str = '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: str = 'screen reader',
|
|
speakerGender: str = '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,
|
|
systemLanguage, 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,
|
|
systemLanguage, 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 hasObjectDict(postJsonObject):
|
|
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 hasObjectDict(postJsonObject):
|
|
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,
|
|
domainFull: str, personCache: {}) -> {}:
|
|
"""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,
|
|
systemLanguage,
|
|
domainFull, personCache)
|
|
if postJsonObject2:
|
|
if hasObjectDict(postJsonObject2):
|
|
if postJsonObject2['object'].get('attributedTo') and \
|
|
postJsonObject2['object'].get('content'):
|
|
attributedTo = postJsonObject2['object']['attributedTo']
|
|
content = \
|
|
getBaseContentFromPost(postJsonObject2, systemLanguage)
|
|
if isinstance(attributedTo, str) and content:
|
|
actor = attributedTo
|
|
nameStr += ' ' + translate['announces'] + ' ' + \
|
|
getNicknameFromActor(actor)
|
|
sayStr = nameStr
|
|
_sayCommand(sayStr, sayStr, screenreader,
|
|
systemLanguage, espeak)
|
|
print('')
|
|
if screenreader:
|
|
time.sleep(2)
|
|
content = \
|
|
_textOnlyContent(content)
|
|
content += _getImageDescription(postJsonObject2)
|
|
messageStr, detectedLinks = \
|
|
speakableText(baseDir, content, translate)
|
|
sayStr = content
|
|
_sayCommand(sayStr, messageStr, screenreader,
|
|
systemLanguage, espeak)
|
|
return postJsonObject2
|
|
return {}
|
|
|
|
attributedTo = postJsonObject['object']['attributedTo']
|
|
if not attributedTo:
|
|
return {}
|
|
content = getBaseContentFromPost(postJsonObject, systemLanguage)
|
|
if not isinstance(attributedTo, str) or \
|
|
not isinstance(content, str):
|
|
return {}
|
|
actor = attributedTo
|
|
nameStr = getNicknameFromActor(actor)
|
|
content = _textOnlyContent(content)
|
|
content += _getImageDescription(postJsonObject)
|
|
|
|
if isPGPEncrypted(content):
|
|
sayStr = 'Encrypted message. Please enter your passphrase.'
|
|
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
|
|
content = pgpDecrypt(domain, 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, asHeader = \
|
|
getActorJson(domain, 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, asHeader = \
|
|
getActorJson(domain, 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 hasObjectDict(postJsonObject):
|
|
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: int = 1,
|
|
newReplies: bool = False,
|
|
newDMs: bool = 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 hasObjectDict(postJsonObject):
|
|
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'])
|
|
|
|
contentStr = getBaseContentFromPost(postJsonObject, systemLanguage)
|
|
content = _textOnlyContent(contentStr)
|
|
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(domain, 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,
|
|
systemLanguage, 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,
|
|
domainFull, personCache)
|
|
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 hasObjectDict(postJsonObject):
|
|
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 hasObjectDict(postJsonObject):
|
|
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,
|
|
systemLanguage,
|
|
domainFull, personCache)
|
|
if postJsonObject2:
|
|
postJsonObject = postJsonObject2
|
|
if postJsonObject:
|
|
content = \
|
|
getBaseContentFromPost(postJsonObject, systemLanguage)
|
|
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'])
|
|
contentStr = getBaseContentFromPost(postJsonObject,
|
|
systemLanguage)
|
|
print(contentStr)
|
|
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)
|