epicyon/desktop_client.py

2479 lines
107 KiB
Python
Raw Normal View History

2021-03-17 10:04:49 +00:00
__filename__ = "desktop_client.py"
2021-03-04 13:57:30 +00:00
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2021-06-15 15:08:12 +00:00
__module_group__ = "Client"
2021-03-04 13:57:30 +00:00
import os
import html
import time
2021-03-04 15:21:38 +00:00
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
2021-03-21 18:37:06 +00:00
from utils import getFullDomain
2021-03-18 19:43:10 +00:00
from utils import isDM
2021-03-18 17:27:46 +00:00
from utils import loadTranslationsFromFile
from utils import removeHtml
from utils import getNicknameFromActor
from utils import getDomainFromActor
2021-03-12 12:04:34 +00:00
from utils import isPGPEncrypted
2021-08-14 11:13:39 +00:00
from utils import localActorUrl
2021-03-04 13:57:30 +00:00
from session import createSession
2021-03-18 17:27:46 +00:00
from speaker import speakableText
2021-03-04 13:57:30 +00:00
from speaker import getSpeakerPitch
from speaker import getSpeakerRate
from speaker import getSpeakerRange
2021-03-10 13:04:22 +00:00
from like import sendLikeViaServer
2021-03-10 13:07:24 +00:00
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
2021-03-23 14:16:44 +00:00
from posts import sendBlockViaServer
from posts import sendUndoBlockViaServer
2021-03-21 12:31:48 +00:00
from posts import sendMuteViaServer
from posts import sendUndoMuteViaServer
from posts import sendPostViaServer
from posts import c2sBoxJson
from posts import downloadAnnounce
from announce import sendAnnounceViaServer
2021-03-18 20:56:08 +00:00
from announce import sendUndoAnnounceViaServer
2021-05-05 09:53:00 +00:00
from pgp import pgpLocalPublicKey
2021-03-11 17:15:32 +00:00
from pgp import pgpDecrypt
from pgp import hasLocalPGPkey
from pgp import pgpEncryptToActor
2021-03-17 20:18:00 +00:00
from pgp import pgpPublicKeyUpload
2021-03-18 20:04:49 +00:00
from like import noOfLikes
2021-03-19 22:04:57 +00:00
from bookmarks import sendBookmarkViaServer
2021-03-19 22:11:45 +00:00
from bookmarks import sendUndoBookmarkViaServer
2021-03-21 18:37:06 +00:00
from delete import sendDeleteViaServer
2021-03-22 18:27:48 +00:00
from person import getActorJson
2021-03-04 14:36:24 +00:00
2021-03-16 23:10:14 +00:00
def _desktopHelp() -> None:
"""Shows help
"""
2021-03-25 15:11:52 +00:00
_desktopClearScreen()
2021-03-16 23:14:34 +00:00
indent = ' '
2021-03-16 23:10:14 +00:00
print('')
2021-03-25 15:11:52 +00:00
print(indent + _highlightText('Help Commands:'))
2021-03-16 23:10:14 +00:00
print('')
2021-03-19 21:34:38 +00:00
print(indent + 'quit ' +
2021-03-17 10:04:49 +00:00
'Exit from the desktop client')
2021-03-19 21:34:38 +00:00
print(indent + 'show dm|sent|inbox|replies|bookmarks ' +
2021-03-16 23:14:34 +00:00
'Show a timeline')
2021-03-19 21:34:38 +00:00
print(indent + 'mute ' +
2021-03-16 23:14:34 +00:00
'Turn off the screen reader')
2021-03-19 21:34:38 +00:00
print(indent + 'speak ' +
2021-03-16 23:14:34 +00:00
'Turn on the screen reader')
2021-03-19 21:34:38 +00:00
print(indent + 'sounds on ' +
2021-03-16 23:14:34 +00:00
'Turn on notification sounds')
2021-03-19 21:34:38 +00:00
print(indent + 'sounds off ' +
2021-03-16 23:14:34 +00:00
'Turn off notification sounds')
2021-03-19 21:34:38 +00:00
print(indent + 'rp ' +
2021-03-16 23:14:34 +00:00
'Repeat the last post')
2021-03-19 21:34:38 +00:00
print(indent + 'like ' +
2021-03-16 23:14:34 +00:00
'Like the last post')
2021-03-19 21:34:38 +00:00
print(indent + 'unlike ' +
2021-03-16 23:14:34 +00:00
'Unlike the last post')
2021-03-20 09:49:43 +00:00
print(indent + 'bookmark ' +
2021-03-21 12:31:48 +00:00
'Bookmark the last post')
2021-03-20 09:49:43 +00:00
print(indent + 'unbookmark ' +
'Unbookmark the last post')
2021-03-23 14:16:44 +00:00
print(indent + 'block [post number|handle] ' +
'Block someone via post number or handle')
print(indent + 'unblock [handle] ' +
'Unblock someone')
2021-03-21 12:31:48 +00:00
print(indent + 'mute ' +
'Mute the last post')
print(indent + 'unmute ' +
'Unmute the last post')
2021-03-19 21:34:38 +00:00
print(indent + 'reply ' +
2021-03-16 23:14:34 +00:00
'Reply to the last post')
2021-03-19 21:34:38 +00:00
print(indent + 'post ' +
2021-03-16 23:14:34 +00:00
'Create a new post')
2021-03-19 21:34:38 +00:00
print(indent + 'post to [handle] ' +
2021-03-16 23:14:34 +00:00
'Create a new direct message')
2021-03-19 21:34:38 +00:00
print(indent + 'announce/boost ' +
2021-03-16 23:14:34 +00:00
'Boost the last post')
2021-03-19 21:34:38 +00:00
print(indent + 'follow [handle] ' +
2021-03-16 23:14:34 +00:00
'Make a follow request')
2021-03-19 21:34:38 +00:00
print(indent + 'unfollow [handle] ' +
2021-03-16 23:14:34 +00:00
'Stop following the give handle')
2021-03-19 21:34:38 +00:00
print(indent + 'next ' +
2021-03-16 23:14:34 +00:00
'Next page in the timeline')
2021-03-19 21:34:38 +00:00
print(indent + 'prev ' +
2021-03-16 23:14:34 +00:00
'Previous page in the timeline')
2021-03-19 21:34:38 +00:00
print(indent + 'read [post number] ' +
2021-03-16 23:14:34 +00:00
'Read a post from a timeline')
2021-03-19 21:34:38 +00:00
print(indent + 'open [post number] ' +
2021-03-16 23:14:34 +00:00
'Open web links within a timeline post')
2021-03-25 12:57:01 +00:00
print(indent + 'profile [post number or handle] ' +
2021-03-22 18:27:48 +00:00
'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')
2021-05-05 09:53:00 +00:00
print(indent + 'pgp ' +
'Show your PGP public key')
2021-03-16 23:10:14 +00:00
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:
2021-06-22 12:27:10 +00:00
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
2021-03-22 23:03:23 +00:00
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
"""
2021-03-23 10:03:45 +00:00
notifyJson['dmNotifyChanged'] = False
notifyJson['repliesNotifyChanged'] = False
if not inboxJson:
return
if not inboxJson.get('orderedItems'):
return
2021-03-23 10:03:45 +00:00
DMdone = False
replyDone = False
for postJsonObject in inboxJson['orderedItems']:
if not postJsonObject.get('id'):
continue
2021-03-23 19:14:49 +00:00
if not postJsonObject.get('type'):
continue
if postJsonObject['type'] == 'Announce':
continue
if not _postIsToYou(actor, postJsonObject):
continue
if isDM(postJsonObject):
2021-03-23 10:03:45 +00:00
if not DMdone:
if not _hasReadPost(actor, postJsonObject['id'], 'dm'):
2021-03-23 10:03:45 +00:00
changed = False
if not notifyJson.get('dmPostId'):
changed = True
else:
if notifyJson['dmPostId'] != postJsonObject['id']:
2021-03-23 10:03:45 +00:00
changed = True
if changed:
notifyJson['dmNotify'] = True
notifyJson['dmNotifyChanged'] = True
notifyJson['dmPostId'] = postJsonObject['id']
2021-03-23 22:52:00 +00:00
DMdone = True
else:
2021-03-23 10:03:45 +00:00
if not replyDone:
if not _hasReadPost(actor, postJsonObject['id'], 'replies'):
2021-03-23 10:03:45 +00:00
changed = False
if not notifyJson.get('repliesPostId'):
changed = True
else:
if notifyJson['repliesPostId'] != postJsonObject['id']:
2021-03-23 10:03:45 +00:00
changed = True
if changed:
notifyJson['repliesNotify'] = True
notifyJson['repliesNotifyChanged'] = True
notifyJson['repliesPostId'] = postJsonObject['id']
2021-03-23 22:52:00 +00:00
replyDone = True
2021-03-17 10:04:49 +00:00
def _desktopClearScreen() -> None:
2021-03-15 14:30:59 +00:00
"""Clears the screen
"""
2021-03-15 13:35:12 +00:00
os.system('cls' if os.name == 'nt' else 'clear')
2021-03-15 13:32:15 +00:00
2021-03-17 10:04:49 +00:00
def _desktopShowBanner() -> None:
2021-03-15 14:30:59 +00:00
"""Shows the banner at the top
"""
2021-03-15 14:36:03 +00:00
bannerFilename = 'banner.txt'
2021-03-15 14:31:33 +00:00
if not os.path.isfile(bannerFilename):
2021-03-15 14:36:03 +00:00
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')
2021-03-15 14:30:59 +00:00
2021-03-17 10:04:49 +00:00
def _desktopWaitForCmd(timeout: int, debug: bool) -> str:
"""Waits for a command to be entered with a timeout
Returns the command, or None on timeout
2021-03-04 14:36:24 +00:00
"""
2021-03-04 15:21:38 +00:00
i, o, e = select.select([sys.stdin], [], [], timeout)
if (i):
2021-03-04 15:27:27 +00:00
text = sys.stdin.readline().strip()
2021-03-04 15:21:38 +00:00
if debug:
2021-03-04 15:27:27 +00:00
print("Text entered: " + text)
return text
2021-03-04 15:21:38 +00:00
else:
2021-03-04 14:36:24 +00:00
if debug:
2021-03-04 15:21:38 +00:00
print("Timeout")
2021-03-04 14:36:24 +00:00
return None
2021-03-04 13:57:30 +00:00
2021-03-04 14:54:30 +00:00
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:
2021-03-16 10:37:51 +00:00
"""TTS using picospeaker
"""
2021-03-04 14:54:30 +00:00
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'
2021-03-25 14:15:36 +00:00
sayText = str(sayText).replace('"', "'")
2021-03-04 14:54:30 +00:00
speakerCmd = 'picospeaker ' + \
'-l ' + speakerLang + \
' -r ' + str(rate) + \
' -p ' + str(pitch) + ' "' + \
2021-03-25 13:57:47 +00:00
html.unescape(str(sayText)) + '" 2> /dev/null'
2021-03-04 14:54:30 +00:00
os.system(speakerCmd)
2021-07-13 15:49:29 +00:00
def _playNotificationSound(soundFilename: str, player: str = 'ffplay') -> None:
2021-03-09 19:52:10 +00:00
"""Plays a sound
"""
if not os.path.isfile(soundFilename):
return
if player == 'ffplay':
os.system('ffplay ' + soundFilename +
2021-03-10 15:43:21 +00:00
' -autoexit -hide_banner -nodisp 2> /dev/null')
2021-03-09 19:52:10 +00:00
2021-03-09 20:32:50 +00:00
def _desktopNotification(notificationType: str,
2021-03-09 21:23:32 +00:00
title: str, message: str) -> None:
2021-03-09 20:32:50 +00:00
"""Shows a desktop notification
"""
if not notificationType:
return
if notificationType == 'notify-send':
# Ubuntu
os.system('notify-send "' + title + '" "' + message + '"')
2021-03-11 10:01:05 +00:00
elif notificationType == 'zenity':
# Zenity
os.system('zenity --notification --title "' + title +
'" --text="' + message + '"')
2021-03-09 20:32:50 +00:00
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 + "'")
2021-03-10 12:11:42 +00:00
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,
2021-03-10 12:11:42 +00:00
systemLanguage: str,
espeak=None,
2021-06-20 11:28:35 +00:00
speakerName: str = 'screen reader',
speakerGender: str = 'They/Them') -> None:
2021-03-10 10:25:41 +00:00
"""Speaks a command
"""
print(content)
2021-03-10 10:32:08 +00:00
if not screenreader:
return
2021-03-10 10:25:41 +00:00
2021-03-10 12:11:42 +00:00
pitch = getSpeakerPitch(speakerName,
screenreader, speakerGender)
rate = getSpeakerRate(speakerName, screenreader)
srange = getSpeakerRange(speakerName)
2021-03-10 10:25:41 +00:00
2021-03-10 12:11:42 +00:00
_textToSpeech(sayStr, screenreader,
pitch, rate, srange,
systemLanguage, espeak)
2021-03-10 10:25:41 +00:00
2021-03-17 10:04:49 +00:00
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, conversationId: str,
lowBandwidth: bool,
signingPrivateKeyPem: str) -> None:
2021-03-17 10:04:49 +00:00
"""Use the desktop client to send a reply to the most recent post
2021-03-10 18:30:00 +00:00
"""
if '://' not in postId:
return
toNickname = getNicknameFromActor(postId)
toDomain, toPort = getDomainFromActor(postId)
sayStr = 'Replying to ' + toNickname + '@' + toDomain
_sayCommand(sayStr, sayStr,
2021-03-10 18:30:00 +00:00
screenreader, systemLanguage, espeak)
sayStr = 'Type your reply message, then press Enter.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
replyMessage = input()
if not replyMessage:
sayStr = 'No reply was entered.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
return
replyMessage = replyMessage.strip()
if not replyMessage:
sayStr = 'No reply was entered.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
return
print('')
2021-03-10 18:30:00 +00:00
sayStr = 'You entered this reply:'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
_sayCommand(replyMessage, replyMessage, screenreader,
systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
sayStr = 'Send this reply, yes or no?'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
yesno = input()
if 'y' not in yesno.lower():
sayStr = 'Abandoning reply'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
return
ccUrl = None
followersOnly = False
attach = None
mediaType = None
attachedImageDescription = None
isArticle = False
subject = None
commentsEnabled = True
2021-05-09 19:29:53 +00:00
city = 'London, England'
sayStr = 'Sending reply'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
if sendPostViaServer(signingPrivateKeyPem, __version__,
2021-03-10 19:31:33 +00:00
baseDir, session, nickname, password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
httpPrefix, replyMessage, followersOnly,
commentsEnabled, attach, mediaType,
2021-05-09 19:11:05 +00:00
attachedImageDescription, city,
2021-03-10 19:31:33 +00:00
cachedWebfingers, personCache, isArticle,
systemLanguage, lowBandwidth,
debug, postId, postId,
2021-08-08 16:52:32 +00:00
conversationId, subject) == 0:
2021-03-10 19:31:33 +00:00
sayStr = 'Reply sent'
else:
sayStr = 'Reply failed'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
2021-03-17 10:04:49 +00:00
def _desktopNewPost(session,
baseDir: str, nickname: str, password: str,
domain: str, port: int, httpPrefix: str,
cachedWebfingers: {}, personCache: {},
debug: bool,
screenreader: str, systemLanguage: str,
espeak, lowBandwidth: bool,
signingPrivateKeyPem: str) -> None:
2021-03-17 10:04:49 +00:00
"""Use the desktop client to create a new post
"""
2021-08-08 16:52:32 +00:00
conversationId = None
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
2021-03-21 13:43:04 +00:00
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
2021-05-09 19:29:53 +00:00
city = 'London, England'
isArticle = False
subject = None
commentsEnabled = True
subject = None
sayStr = 'Sending'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
if sendPostViaServer(signingPrivateKeyPem, __version__,
baseDir, session, nickname, password,
domain, port,
None, '#Public', port, ccUrl,
httpPrefix, newMessage, followersOnly,
commentsEnabled, attach, mediaType,
2021-05-09 19:11:05 +00:00
attachedImageDescription, city,
cachedWebfingers, personCache, isArticle,
systemLanguage, lowBandwidth,
debug, None, None,
2021-08-08 16:52:32 +00:00
conversationId, 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)
2021-03-19 14:49:40 +00:00
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
2021-03-23 14:36:35 +00:00
def _showLikesOnPost(postJsonObject: {}, maxLikes: int) -> None:
"""Shows the likes on a post
"""
if not hasObjectDict(postJsonObject):
2021-03-23 14:36:35 +00:00
return
if not postJsonObject['object'].get('likes'):
return
if not isinstance(postJsonObject['object']['likes'], dict):
return
if not postJsonObject['object']['likes'].get('items'):
return
2021-03-23 14:50:40 +00:00
if not isinstance(postJsonObject['object']['likes']['items'], list):
return
2021-03-23 14:36:35 +00:00
print('')
ctr = 0
for item in postJsonObject['object']['likes']['items']:
print('' + str(item['actor']))
ctr += 1
if ctr >= maxLikes:
break
2021-03-23 19:14:49 +00:00
def _showRepliesOnPost(postJsonObject: {}, maxReplies: int) -> None:
"""Shows the replies on a post
"""
if not hasObjectDict(postJsonObject):
2021-03-23 19:14:49 +00:00
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: {},
2021-03-15 15:14:54 +00:00
systemLanguage: str,
2021-03-18 17:27:46 +00:00
screenreader: str, espeak,
translate: {}, yourActor: str,
domainFull: str, personCache: {},
signingPrivateKeyPem: str) -> {}:
2021-03-15 15:14:54 +00:00
"""Reads a post from the given timeline
2021-03-22 18:27:48 +00:00
Returns the post json
2021-03-15 15:14:54 +00:00
"""
if _timelineIsEmpty(boxJson):
return {}
2021-03-12 19:55:16 +00:00
postJsonObject = _desktopGetBoxPostObject(boxJson, index)
if not postJsonObject:
return {}
2021-03-12 19:55:16 +00:00
gender = 'They/Them'
2021-03-19 17:23:30 +00:00
boxNameStr = boxName
if boxName.startswith('tl'):
boxNameStr = boxName[2:]
sayStr = 'Reading ' + boxNameStr + ' post ' + str(index) + \
' from page ' + str(pageNumber) + '.'
2021-03-16 18:08:18 +00:00
sayStr2 = sayStr.replace(' dm ', ' DM ')
_sayCommand(sayStr, sayStr2, screenreader, systemLanguage, espeak)
2021-03-23 11:22:09 +00:00
print('')
2021-03-12 20:51:04 +00:00
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,
signingPrivateKeyPem)
if postJsonObject2:
if hasObjectDict(postJsonObject2):
if postJsonObject2['object'].get('attributedTo') and \
postJsonObject2['object'].get('content'):
2021-07-09 20:00:02 +00:00
attributedTo = postJsonObject2['object']['attributedTo']
content = \
getBaseContentFromPost(postJsonObject2, systemLanguage)
if isinstance(attributedTo, str) and content:
2021-07-09 20:00:02 +00:00
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 {}
2021-07-09 20:00:02 +00:00
attributedTo = postJsonObject['object']['attributedTo']
if not attributedTo:
return {}
content = getBaseContentFromPost(postJsonObject, systemLanguage)
2021-07-09 20:00:02 +00:00
if not isinstance(attributedTo, str) or \
not isinstance(content, str):
return {}
actor = attributedTo
nameStr = getNicknameFromActor(actor)
2021-07-09 20:00:02 +00:00
content = _textOnlyContent(content)
2021-03-19 14:49:40 +00:00
content += _getImageDescription(postJsonObject)
if isPGPEncrypted(content):
2021-03-16 11:56:24 +00:00
sayStr = 'Encrypted message. Please enter your passphrase.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
content = pgpDecrypt(domain, content, actor, signingPrivateKeyPem)
2021-03-16 11:56:24 +00:00
if isPGPEncrypted(content):
sayStr = 'Message could not be decrypted'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
return {}
2021-03-16 11:56:24 +00:00
content = _safeMessage(content)
2021-03-25 14:51:41 +00:00
messageStr, detectedLinks = speakableText(baseDir, content, translate)
if screenreader:
time.sleep(2)
2021-03-12 20:51:04 +00:00
2021-03-12 19:55:16 +00:00
# say the speaker's name
_sayCommand(nameStr, nameStr, screenreader,
2021-03-22 18:27:48 +00:00
systemLanguage, espeak, nameStr, gender)
2021-03-23 11:22:09 +00:00
print('')
2021-03-12 19:55:16 +00:00
2021-03-19 17:23:30 +00:00
if postJsonObject['object'].get('inReplyTo'):
print('Replying to ' + postJsonObject['object']['inReplyTo'] + '\n')
if screenreader:
time.sleep(2)
2021-03-12 19:55:16 +00:00
# speak the post content
_sayCommand(content, messageStr, screenreader,
2021-03-22 18:27:48 +00:00
systemLanguage, espeak, nameStr, gender)
2021-03-23 14:36:35 +00:00
_showLikesOnPost(postJsonObject, 10)
2021-03-23 19:14:49 +00:00
_showRepliesOnPost(postJsonObject, 10)
2021-03-23 14:16:44 +00:00
# if the post is addressed to you then mark it as read
if _postIsToYou(yourActor, postJsonObject):
2021-03-23 10:39:35 +00:00
if isDM(postJsonObject):
_markPostAsRead(yourActor, postJsonObject['id'], 'dm')
else:
_markPostAsRead(yourActor, postJsonObject['id'], 'replies')
return postJsonObject
2021-03-12 19:55:16 +00:00
2021-03-25 12:43:11 +00:00
def _desktopShowActor(baseDir: str, actorJson: {}, translate: {},
systemLanguage: str, screenreader: str,
espeak) -> None:
"""Shows information for the given actor
2021-03-25 12:39:10 +00:00
"""
actor = actorJson['id']
actorNickname = getNicknameFromActor(actor)
actorDomain, actorPort = getDomainFromActor(actor)
actorDomainFull = getFullDomain(actorDomain, actorPort)
handle = '@' + actorNickname + '@' + actorDomainFull
2021-03-25 14:51:41 +00:00
sayStr = 'Profile for ' + html.unescape(handle)
2021-03-25 12:39:10 +00:00
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
print(actor)
if actorJson.get('movedTo'):
2021-03-25 13:57:47 +00:00
sayStr = 'Moved to ' + html.unescape(actorJson['movedTo'])
2021-03-25 12:39:10 +00:00
_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
2021-03-25 13:57:47 +00:00
sayStr = 'Also known as ' + html.unescape(alsoKnownAsStr)
2021-03-25 12:39:10 +00:00
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
if actorJson.get('summary'):
2021-03-25 13:57:47 +00:00
sayStr = html.unescape(removeHtml(actorJson['summary']))
sayStr = sayStr.replace('"', "'")
2021-03-25 14:31:34 +00:00
sayStr2 = speakableText(baseDir, sayStr, translate)[0]
2021-03-25 12:39:10 +00:00
_sayCommand(sayStr, sayStr2, screenreader, systemLanguage, espeak)
2021-03-25 12:43:11 +00:00
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: {}, signingPrivateKeyPem: str) -> {}:
2021-03-22 18:27:48 +00:00
"""Shows the profile of the actor for the given post
Returns the actor json
"""
if _timelineIsEmpty(boxJson):
return {}
if not postJsonObject:
2021-03-23 16:17:03 +00:00
postJsonObject = _desktopGetBoxPostObject(boxJson, index)
if not postJsonObject:
return {}
2021-03-22 18:27:48 +00:00
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
2021-06-18 09:22:16 +00:00
actorJson, asHeader = \
getActorJson(domain, actor, isHttp, False, False, True,
signingPrivateKeyPem)
2021-03-22 18:27:48 +00:00
2021-03-25 12:43:11 +00:00
_desktopShowActor(baseDir, actorJson, translate,
systemLanguage, screenreader, espeak)
2021-03-22 18:27:48 +00:00
return actorJson
2021-03-25 12:57:01 +00:00
def _desktopShowProfileFromHandle(session, nickname: str, domain: str,
httpPrefix: str, baseDir: str, boxName: str,
handle: str,
systemLanguage: str,
screenreader: str, espeak,
translate: {}, yourActor: str,
postJsonObject: {},
signingPrivateKeyPem: str) -> {}:
2021-03-25 12:57:01 +00:00
"""Shows the profile for a handle
Returns the actor json
"""
2021-06-18 09:22:16 +00:00
actorJson, asHeader = \
getActorJson(domain, handle, False, False, False, True,
signingPrivateKeyPem)
2021-03-25 12:57:01 +00:00
_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
2021-03-18 22:16:01 +00:00
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
2021-03-23 23:33:33 +00:00
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: {},
2021-03-19 11:59:14 +00:00
screenreader: str, systemLanguage: str, espeak,
2021-06-20 11:28:35 +00:00
pageNumber: int = 1,
newReplies: bool = False,
newDMs: bool = False) -> bool:
"""Shows online timeline
"""
numberWidth = 2
nameWidth = 16
contentWidth = 50
# title
_desktopClearScreen()
_desktopShowBanner()
notificationIcons = ''
2021-03-19 17:23:30 +00:00
if boxName.startswith('tl'):
2021-03-19 16:10:14 +00:00
boxNameStr = boxName[2:]
2021-03-19 15:39:16 +00:00
else:
2021-03-19 16:10:14 +00:00
boxNameStr = boxName
2021-03-23 23:33:33 +00:00
titleStr = _highlightText(boxNameStr.upper())
2021-03-24 17:24:22 +00:00
# if newDMs:
# notificationIcons += ' 📩'
# if newReplies:
# notificationIcons += ' 📨'
2021-03-22 16:56:13 +00:00
if notificationIcons:
while len(titleStr) < 95 - len(notificationIcons):
titleStr += ' '
titleStr += notificationIcons
print(indent + titleStr + '\n')
if _timelineIsEmpty(boxJson):
2021-03-19 16:10:14 +00:00
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
2021-03-23 11:22:09 +00:00
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
2021-03-18 20:11:28 +00:00
spaceAdded = False
2021-03-18 19:43:10 +00:00
if postJsonObject['object'].get('inReplyTo'):
2021-03-18 20:11:28 +00:00
if not spaceAdded:
spaceAdded = True
name += ' '
name += ''
2021-03-23 19:14:49 +00:00
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
2021-03-18 20:04:49 +00:00
likesCount = noOfLikes(postJsonObject)
if likesCount > 10:
likesCount = 10
for like in range(likesCount):
2021-03-18 20:11:28 +00:00
if not spaceAdded:
spaceAdded = True
name += ' '
2021-03-18 20:04:49 +00:00
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
2021-03-21 12:15:10 +00:00
if postJsonObject['object'].get('ignores'):
content = '🔇'
2021-03-21 13:20:07 +00:00
if postJsonObject['object'].get('bookmarks'):
2021-03-21 16:09:18 +00:00
content = '🔖' + content
if '\n' in content:
content = content.replace('\n', ' ')
2021-03-23 11:22:09 +00:00
lineStr = indent + str(posStr) + ' | ' + name + ' | ' + \
published + ' | ' + content
if boxName == 'inbox' and \
_postIsToYou(yourActor, postJsonObject):
2021-03-23 23:33:33 +00:00
if not _hasReadPost(yourActor, postJsonObject['id'], 'dm'):
if not _hasReadPost(yourActor, postJsonObject['id'],
'replies'):
lineStr = _highlightText(lineStr)
2021-03-23 11:22:09 +00:00
print(lineStr)
ctr += 1
if followRequestsJson:
_desktopShowFollowRequests(followRequestsJson, translate)
print('')
# say the post number range
2021-03-19 16:10:14 +00:00
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
2021-03-17 10:04:49 +00:00
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, lowBandwidth: bool,
signingPrivateKeyPem: str) -> None:
2021-03-17 10:04:49 +00:00
"""Use the desktop client to create a new direct message
2021-03-14 10:28:48 +00:00
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()
2021-03-17 10:04:49 +00:00
_desktopNewDMbase(session, handle,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak, lowBandwidth,
signingPrivateKeyPem)
2021-03-14 10:28:48 +00:00
2021-03-17 10:04:49 +00:00
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, lowBandwidth: bool,
signingPrivateKeyPem: str) -> None:
2021-03-17 10:04:49 +00:00
"""Use the desktop client to create a new direct message
2021-03-11 12:24:20 +00:00
"""
2021-08-08 16:52:32 +00:00
conversationId = None
2021-03-11 12:24:20 +00:00
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
2021-05-09 19:29:53 +00:00
city = 'London, England'
2021-03-11 12:24:20 +00:00
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,
signingPrivateKeyPem)
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
2021-03-11 12:24:20 +00:00
sayStr = 'Sending'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
if sendPostViaServer(signingPrivateKeyPem, __version__,
2021-03-11 12:24:20 +00:00
baseDir, session, nickname, password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
httpPrefix, newMessage, followersOnly,
commentsEnabled, attach, mediaType,
2021-05-09 19:11:05 +00:00
attachedImageDescription, city,
2021-03-11 12:24:20 +00:00
cachedWebfingers, personCache, isArticle,
systemLanguage, lowBandwidth,
debug, None, None,
2021-08-08 16:52:32 +00:00
conversationId, subject) == 0:
2021-03-11 12:24:20 +00:00
sayStr = 'Direct message sent'
else:
sayStr = 'Direct message failed'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
def _desktopShowFollowRequests(followRequestsJson: {}, translate: {}) -> None:
"""Shows any follow requests
"""
2021-03-25 10:56:41 +00:00
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
"""
2021-03-25 10:58:53 +00:00
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)
2021-03-17 10:04:49 +00:00
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,
2021-03-18 17:27:46 +00:00
language: str,
debug: bool, lowBandwidth: bool) -> None:
2021-03-17 10:04:49 +00:00
"""Runs the desktop and screen reader client,
which announces new inbox items
2021-03-04 13:57:30 +00:00
"""
# TODO: this should probably be retrieved somehow from the server
signingPrivateKeyPem = None
2021-03-15 14:30:59 +00:00
indent = ' '
2021-03-15 13:51:21 +00:00
if showNewPosts:
indent = ''
2021-03-17 10:04:49 +00:00
_desktopClearScreen()
_desktopShowBanner()
2021-03-14 10:41:21 +00:00
2021-03-10 10:25:41 +00:00
espeak = None
2021-03-09 19:52:10 +00:00
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
2021-03-04 13:57:30 +00:00
2021-03-15 13:51:21 +00:00
sayStr = indent + 'Running ' + screenreader + ' for ' + \
nickname + '@' + domain
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:49:45 +00:00
systemLanguage, espeak)
2021-03-09 21:30:23 +00:00
else:
2021-03-15 13:51:21 +00:00
print(indent + 'Running desktop notifications for ' +
nickname + '@' + domain)
if notificationSounds:
2021-03-15 13:51:21 +00:00
sayStr = indent + 'Notification sounds on'
else:
2021-03-15 13:51:21 +00:00
sayStr = indent + 'Notification sounds off'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:49:45 +00:00
systemLanguage, espeak)
currTimeline = 'inbox'
pageNumber = 1
2021-03-10 10:51:06 +00:00
postJsonObject = {}
2021-03-10 10:25:41 +00:00
originalScreenReader = screenreader
2021-03-22 13:43:20 +00:00
soundsDir = 'theme/default/sounds/'
# prevSay = ''
# prevCalendar = False
# prevFollow = False
# prevLike = ''
# prevShare = False
2021-03-22 13:43:20 +00:00
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'
2021-03-10 12:37:44 +00:00
nameStr = None
gender = None
messageStr = None
2021-03-11 10:52:06 +00:00
content = None
2021-03-10 13:04:22 +00:00
cachedWebfingers = {}
personCache = {}
newRepliesExist = False
newDMsExist = False
2021-03-17 20:18:00 +00:00
pgpKeyUpload = False
2021-03-18 17:30:47 +00:00
sayStr = indent + 'Loading translations file'
2021-03-18 17:27:46 +00:00
_sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak)
translate, systemLanguage = \
loadTranslationsFromFile(baseDir, language)
sayStr = indent + 'Connecting...'
_sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak)
session = createSession(proxyType)
2021-03-18 17:30:47 +00:00
sayStr = indent + '/q or /quit to exit'
_sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak)
domainFull = getFullDomain(domain, port)
2021-08-14 11:13:39 +00:00
yourActor = localActorUrl(httpPrefix, nickname, domainFull)
2021-03-22 18:27:48 +00:00
actorJson = None
2021-03-22 13:43:20 +00:00
notifyJson = {
"dmPostId": "Initial",
"dmNotify": False,
"dmNotifyChanged": False,
"repliesPostId": "Initial",
"repliesNotify": False,
"repliesNotifyChanged": False
}
prevTimelineFirstId = ''
2021-04-01 16:30:13 +00:00
desktopShown = False
while (1):
2021-03-17 20:18:00 +00:00
if not pgpKeyUpload:
2021-05-05 09:57:29 +00:00
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,
signingPrivateKeyPem)
2021-05-05 09:57:29 +00:00
sayStr = indent + 'PGP public key uploaded'
_sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak)
2021-03-17 20:18:00 +00:00
pgpKeyUpload = True
boxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
debug, signingPrivateKeyPem)
followRequestsJson = \
getFollowRequestsViaServer(baseDir, session,
nickname, password,
domain, port,
httpPrefix, 1,
cachedWebfingers, personCache,
debug, __version__,
signingPrivateKeyPem)
2021-03-22 13:44:14 +00:00
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,
signingPrivateKeyPem)
else:
inboxJson = boxJson
2021-03-22 16:56:13 +00:00
newDMsExist = False
newRepliesExist = False
if inboxJson:
_newDesktopNotifications(yourActor, inboxJson, notifyJson)
if notifyJson.get('dmNotify'):
2021-03-22 16:56:13 +00:00
newDMsExist = True
2021-03-22 13:43:20 +00:00
if notifyJson.get('dmNotifyChanged'):
_desktopNotification(notificationType,
"Epicyon",
"New DM " + yourActor + '/dm')
2021-03-23 09:43:42 +00:00
if notificationSounds:
_playNotificationSound(dmSoundFilename, player)
if notifyJson.get('repliesNotify'):
2021-03-22 16:56:13 +00:00
newRepliesExist = True
2021-03-22 13:43:20 +00:00
if notifyJson.get('repliesNotifyChanged'):
_desktopNotification(notificationType,
"Epicyon",
"New reply " + yourActor + '/replies')
2021-03-23 09:43:42 +00:00
if notificationSounds:
_playNotificationSound(replySoundFilename, player)
if boxJson:
timelineFirstId = _getFirstItemId(boxJson)
if timelineFirstId != prevTimelineFirstId:
_desktopClearScreen()
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 11:59:14 +00:00
None, systemLanguage, espeak,
pageNumber,
newRepliesExist,
newDMsExist)
2021-04-01 16:30:13 +00:00
desktopShown = True
prevTimelineFirstId = timelineFirstId
2021-03-19 15:31:16 +00:00
else:
session = createSession(proxyType)
2021-04-01 16:30:13 +00:00
if not desktopShown:
2021-04-01 16:36:45 +00:00
if not session:
print('No session\n')
2021-04-01 16:30:13 +00:00
_desktopClearScreen()
_desktopShowBanner()
2021-04-01 16:33:30 +00:00
print('No posts\n')
2021-04-01 16:49:37 +00:00
if proxyType == 'tor':
2021-04-04 11:58:48 +00:00
print('You may need to run the desktop client ' +
2021-04-01 16:49:37 +00:00
'with the --http option')
2021-03-04 13:57:30 +00:00
# wait for a while, or until a key is pressed
if noKeyPress:
time.sleep(10)
else:
commandStr = _desktopWaitForCmd(30, debug)
2021-03-17 10:04:49 +00:00
if commandStr:
2021-03-21 13:29:56 +00:00
refreshTimeline = False
2021-03-17 10:04:49 +00:00
if commandStr.startswith('/'):
commandStr = commandStr[1:]
if commandStr == 'q' or \
commandStr == 'quit' or \
commandStr == 'exit':
2021-03-10 10:46:50 +00:00
sayStr = 'Quit'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:46:50 +00:00
systemLanguage, espeak)
2021-03-10 18:31:45 +00:00
if screenreader:
2021-03-17 10:04:49 +00:00
commandStr = _desktopWaitForCmd(2, debug)
2021-03-04 18:05:46 +00:00
break
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('show dm'):
pageNumber = 1
prevTimelineFirstId = ''
2021-03-12 19:13:01 +00:00
currTimeline = 'dm'
boxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 11:59:14 +00:00
screenreader, systemLanguage, espeak,
pageNumber,
newRepliesExist, newDMsExist)
newDMsExist = False
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('show rep'):
pageNumber = 1
prevTimelineFirstId = ''
2021-03-19 15:39:16 +00:00
currTimeline = 'tlreplies'
boxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 11:59:14 +00:00
screenreader, systemLanguage, espeak,
pageNumber,
newRepliesExist, newDMsExist)
# Turn off the replies indicator
newRepliesExist = False
2021-03-19 21:34:38 +00:00
elif commandStr.startswith('show b'):
pageNumber = 1
prevTimelineFirstId = ''
currTimeline = 'tlbookmarks'
boxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
debug, signingPrivateKeyPem)
2021-03-19 21:34:38 +00:00
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 21:34:38 +00:00
screenreader, systemLanguage, espeak,
pageNumber,
newRepliesExist, newDMsExist)
# Turn off the replies indicator
newRepliesExist = False
2021-03-19 15:28:16 +00:00
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, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 11:59:14 +00:00
screenreader, systemLanguage, espeak,
pageNumber,
newRepliesExist, newDMsExist)
2021-03-17 10:04:49 +00:00
elif (commandStr == 'show' or commandStr.startswith('show in') or
commandStr == 'clear'):
pageNumber = 1
prevTimelineFirstId = ''
2021-03-12 19:13:01 +00:00
currTimeline = 'inbox'
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('next'):
pageNumber += 1
prevTimelineFirstId = ''
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('prev'):
pageNumber -= 1
if pageNumber < 1:
pageNumber = 1
prevTimelineFirstId = ''
boxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-19 11:59:14 +00:00
screenreader, systemLanguage, espeak,
pageNumber,
newRepliesExist, newDMsExist)
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('read ') or commandStr == 'read':
if commandStr == 'read':
2021-03-16 14:06:05 +00:00
postIndexStr = '1'
else:
2021-03-17 10:04:49 +00:00
postIndexStr = commandStr.split('read ')[1]
if boxJson and postIndexStr.isdigit():
2021-03-25 14:15:36 +00:00
_desktopClearScreen()
_desktopShowBanner()
2021-03-12 19:55:16 +00:00
postIndex = int(postIndexStr)
postJsonObject = \
_readLocalBoxPost(session, nickname, domain,
httpPrefix, baseDir, currTimeline,
pageNumber, postIndex, boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
domainFull, personCache,
signingPrivateKeyPem)
2021-03-25 10:56:41 +00:00
print('')
2021-03-25 14:15:36 +00:00
sayStr = 'Press Enter to continue...'
2021-03-25 15:11:52 +00:00
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
2021-03-25 14:15:36 +00:00
screenreader, systemLanguage, espeak)
input()
2021-03-25 10:56:41 +00:00
prevTimelineFirstId = ''
refreshTimeline = True
2021-03-12 19:55:16 +00:00
print('')
2021-03-22 18:27:48 +00:00
elif commandStr.startswith('profile ') or commandStr == 'profile':
2021-03-23 16:17:03 +00:00
actorJson = None
2021-03-22 18:27:48 +00:00
if commandStr == 'profile':
2021-03-23 16:17:03 +00:00
if postJsonObject:
actorJson = \
2021-03-25 12:43:11 +00:00
_desktopShowProfile(session, nickname, domain,
httpPrefix, baseDir,
currTimeline,
pageNumber, postIndex,
boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
postJsonObject,
signingPrivateKeyPem)
2021-03-23 16:17:03 +00:00
else:
postIndexStr = '1'
2021-03-22 18:27:48 +00:00
else:
postIndexStr = commandStr.split('profile ')[1]
2021-03-23 16:17:03 +00:00
2021-03-25 12:57:01 +00:00
if not postIndexStr.isdigit():
profileHandle = postIndexStr
2021-03-25 14:15:36 +00:00
_desktopClearScreen()
_desktopShowBanner()
2021-03-25 12:57:01 +00:00
_desktopShowProfileFromHandle(session, nickname, domain,
httpPrefix, baseDir,
currTimeline, profileHandle,
systemLanguage, screenreader,
espeak, translate, yourActor,
None, signingPrivateKeyPem)
2021-03-25 14:15:36 +00:00
sayStr = 'Press Enter to continue...'
2021-03-25 15:11:52 +00:00
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
2021-03-25 14:15:36 +00:00
screenreader, systemLanguage, espeak)
input()
2021-03-25 14:18:19 +00:00
prevTimelineFirstId = ''
refreshTimeline = True
2021-03-25 12:57:01 +00:00
elif not actorJson and boxJson:
2021-03-25 14:15:36 +00:00
_desktopClearScreen()
_desktopShowBanner()
2021-03-22 18:27:48 +00:00
postIndex = int(postIndexStr)
actorJson = \
2021-03-25 12:43:11 +00:00
_desktopShowProfile(session, nickname, domain,
httpPrefix, baseDir, currTimeline,
pageNumber, postIndex, boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
None, signingPrivateKeyPem)
2021-03-25 14:15:36 +00:00
sayStr = 'Press Enter to continue...'
2021-03-25 15:11:52 +00:00
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
2021-03-25 14:15:36 +00:00
screenreader, systemLanguage, espeak)
input()
2021-03-25 14:18:19 +00:00
prevTimelineFirstId = ''
refreshTimeline = True
2021-03-22 18:27:48 +00:00
print('')
2021-03-17 10:04:49 +00:00
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']
2021-08-08 16:52:32 +00:00
conversationId = None
if postJsonObject['object'].get('conversation'):
conversationId = \
postJsonObject['object']['conversation']
sessionReply = createSession(proxyType)
_desktopReplyToPost(sessionReply, postId,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug, subject,
screenreader, systemLanguage,
espeak, conversationId,
lowBandwidth,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
print('')
2021-03-17 10:04:49 +00:00
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)
2021-03-17 10:04:49 +00:00
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 ', ' ')
2021-03-11 12:24:20 +00:00
# direct message
2021-03-11 12:30:29 +00:00
toHandle = None
2021-03-17 10:04:49 +00:00
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]
2021-03-11 12:30:29 +00:00
if toHandle:
2021-03-17 10:04:49 +00:00
_desktopNewDM(sessionPost, toHandle,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak, lowBandwidth,
signingPrivateKeyPem)
2021-03-21 13:43:04 +00:00
refreshTimeline = True
2021-03-11 12:24:20 +00:00
else:
# public post
2021-03-17 10:04:49 +00:00
_desktopNewPost(sessionPost,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak, lowBandwidth,
signingPrivateKeyPem)
2021-03-21 13:43:04 +00:00
refreshTimeline = True
2021-03-10 18:30:00 +00:00
print('')
2021-03-18 20:56:08 +00:00
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,
2021-03-10 13:07:24 +00:00
nickname, password,
domain, port, httpPrefix,
postJsonObject['id'],
2021-03-10 13:07:24 +00:00
cachedWebfingers, personCache,
False, __version__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
print('')
2021-03-21 12:31:48 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-21 12:31:48 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-21 12:31:48 +00:00
print('')
2021-03-20 14:46:24 +00:00
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 ')):
2021-03-19 22:04:57 +00:00
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'):
2021-03-21 12:31:48 +00:00
bmActor = postJsonObject['object']['attributedTo']
2021-03-20 14:46:24 +00:00
sayStr = 'Unbookmarking post by ' + \
2021-03-21 12:31:48 +00:00
getNicknameFromActor(bmActor)
2021-03-19 22:04:57 +00:00
_sayCommand(sayStr, sayStr,
screenreader,
systemLanguage, espeak)
2021-03-21 12:31:48 +00:00
sessionbm = createSession(proxyType)
sendUndoBookmarkViaServer(baseDir, sessionbm,
2021-03-20 14:46:24 +00:00
nickname, password,
domain, port, httpPrefix,
postJsonObject['id'],
cachedWebfingers,
personCache,
False, __version__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-19 22:04:57 +00:00
print('')
2021-03-20 14:46:24 +00:00
elif (commandStr == 'bookmark' or
commandStr == 'bm' or
commandStr.startswith('bookmark ') or
commandStr.startswith('bm ')):
2021-03-19 22:11:45 +00:00
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'):
2021-03-21 12:31:48 +00:00
bmActor = postJsonObject['object']['attributedTo']
2021-03-20 14:46:24 +00:00
sayStr = 'Bookmarking post by ' + \
2021-03-21 12:31:48 +00:00
getNicknameFromActor(bmActor)
2021-03-19 22:11:45 +00:00
_sayCommand(sayStr, sayStr,
screenreader,
systemLanguage, espeak)
2021-03-21 12:31:48 +00:00
sessionbm = createSession(proxyType)
sendBookmarkViaServer(baseDir, sessionbm,
2021-03-20 14:46:24 +00:00
nickname, password,
domain, port, httpPrefix,
postJsonObject['id'],
cachedWebfingers, personCache,
False, __version__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-19 22:11:45 +00:00
print('')
2021-03-23 14:16:44 +00:00
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):
2021-03-23 14:16:44 +00:00
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__,
signingPrivateKeyPem)
2021-03-23 14:16:44 +00:00
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 = \
2021-08-14 11:13:39 +00:00
localActorUrl(httpPrefix,
blockNickname, blockDomain)
2021-03-23 14:16:44 +00:00
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):
2021-03-23 14:16:44 +00:00
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__,
signingPrivateKeyPem)
2021-03-23 14:16:44 +00:00
refreshTimeline = True
print('')
elif commandStr == 'unlike' or commandStr == 'undo like':
2021-03-18 20:56:08 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
print('')
2021-03-18 20:56:08 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
print('')
2021-03-18 20:56:08 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 13:29:56 +00:00
refreshTimeline = True
2021-03-18 20:56:08 +00:00
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__,
signingPrivateKeyPem)
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__,
signingPrivateKeyPem)
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__,
signingPrivateKeyPem)
if followersJson:
if isinstance(followersJson, dict):
_desktopShowFollowing(followersJson, translate,
currPage, indent,
'followers')
print('')
2021-03-22 18:27:48 +00:00
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)
2021-03-10 16:56:27 +00:00
sessionFollow = createSession(proxyType)
2021-03-22 18:27:48 +00:00
sendFollowRequestViaServer(baseDir,
sessionFollow,
nickname, password,
domain, port,
followNickname,
followDomain,
followPort,
httpPrefix,
cachedWebfingers,
personCache,
debug, __version__,
signingPrivateKeyPem)
else:
2021-03-22 18:27:48 +00:00
if followHandle:
sayStr = followHandle + ' is not valid'
else:
sayStr = 'Specify a handle to follow'
_sayCommand(sayStr,
screenreader, systemLanguage, espeak)
print('')
2021-03-17 10:04:49 +00:00
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)
2021-03-10 16:56:27 +00:00
sessionUnfollow = createSession(proxyType)
sendUnfollowRequestViaServer(baseDir, sessionUnfollow,
nickname, password,
domain, port,
followNickname,
followDomain,
followPort,
httpPrefix,
cachedWebfingers,
personCache,
debug, __version__,
signingPrivateKeyPem)
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__,
signingPrivateKeyPem)
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__,
signingPrivateKeyPem)
else:
if denyHandle:
sayStr = denyHandle + ' is not valid'
else:
sayStr = 'Specify a handle to deny'
_sayCommand(sayStr,
screenreader, systemLanguage, espeak)
print('')
2021-03-17 10:04:49 +00:00
elif (commandStr == 'repeat' or commandStr == 'replay' or
commandStr == 'rp' or commandStr == 'again' or
commandStr == 'say again'):
2021-03-11 11:09:33 +00:00
if screenreader and nameStr and \
gender and messageStr and content:
sayStr = 'Repeating ' + nameStr
2021-03-11 11:12:01 +00:00
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 12:37:44 +00:00
systemLanguage, espeak,
nameStr, gender)
time.sleep(2)
_sayCommand(content, messageStr, screenreader,
2021-03-10 12:40:17 +00:00
systemLanguage, espeak,
nameStr, gender)
print('')
2021-03-17 10:04:49 +00:00
elif (commandStr == 'sounds on' or
commandStr == 'sound on' or
commandStr == 'sound'):
2021-03-10 10:34:06 +00:00
sayStr = 'Notification sounds on'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:32:08 +00:00
systemLanguage, espeak)
notificationSounds = True
2021-03-17 10:04:49 +00:00
elif (commandStr == 'sounds off' or
commandStr == 'sound off' or
commandStr == 'nosound'):
2021-03-10 10:34:06 +00:00
sayStr = 'Notification sounds off'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:32:08 +00:00
systemLanguage, espeak)
notificationSounds = False
2021-03-17 10:04:49 +00:00
elif (commandStr == 'speak' or
commandStr == 'screen reader on' or
commandStr == 'speaker on' or
commandStr == 'talker on' or
commandStr == 'reader on'):
2021-03-10 10:25:41 +00:00
if originalScreenReader:
screenreader = originalScreenReader
2021-03-10 10:34:06 +00:00
sayStr = 'Screen reader on'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:25:41 +00:00
systemLanguage, espeak)
else:
print('No --screenreader option was specified')
2021-03-17 10:04:49 +00:00
elif (commandStr == 'mute' or
commandStr == 'screen reader off' or
commandStr == 'speaker off' or
commandStr == 'talker off' or
commandStr == 'reader off'):
2021-03-10 10:25:41 +00:00
if originalScreenReader:
screenreader = None
2021-03-10 10:34:06 +00:00
sayStr = 'Screen reader off'
_sayCommand(sayStr, sayStr, originalScreenReader,
2021-03-10 10:25:41 +00:00
systemLanguage, espeak)
else:
print('No --screenreader option was specified')
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('open'):
2021-03-15 17:00:23 +00:00
currIndex = 0
2021-03-17 10:04:49 +00:00
if ' ' in commandStr:
postIndex = commandStr.split(' ')[-1].strip()
2021-03-15 17:00:23 +00:00
if postIndex.isdigit():
currIndex = int(postIndex)
2021-03-18 19:04:58 +00:00
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,
signingPrivateKeyPem)
if postJsonObject2:
postJsonObject = postJsonObject2
2021-03-18 19:04:58 +00:00
if postJsonObject:
content = \
getBaseContentFromPost(postJsonObject, systemLanguage)
2021-03-18 19:04:58 +00:00
messageStr, detectedLinks = \
2021-03-25 14:51:41 +00:00
speakableText(baseDir, content, translate)
2021-03-18 19:04:58 +00:00
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('')
2021-05-05 09:53:00 +00:00
elif commandStr.startswith('pgp') or commandStr.startswith('gpg'):
if not hasLocalPGPkey():
print('No PGP public key was found')
else:
print(pgpLocalPublicKey())
print('')
2021-03-17 10:04:49 +00:00
elif commandStr.startswith('h'):
2021-03-16 23:10:14 +00:00
_desktopHelp()
2021-03-25 14:15:36 +00:00
sayStr = 'Press Enter to continue...'
2021-03-25 15:11:52 +00:00
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
2021-03-25 14:15:36 +00:00
screenreader, systemLanguage, espeak)
2021-03-25 12:21:04 +00:00
input()
2021-03-25 15:11:52 +00:00
prevTimelineFirstId = ''
refreshTimeline = True
2021-03-21 18:37:06 +00:00
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:
2021-03-21 18:37:06 +00:00
sayStr = 'You can only delete your own posts'
_sayCommand(sayStr, sayStr,
screenreader,
systemLanguage, espeak)
else:
2021-03-21 20:05:06 +00:00
print('')
if postJsonObject['object'].get('summary'):
print(postJsonObject['object']['summary'])
contentStr = getBaseContentFromPost(postJsonObject,
systemLanguage)
print(contentStr)
2021-03-21 20:05:06 +00:00
print('')
sayStr = 'Confirm delete, yes or no?'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-21 18:37:06 +00:00
systemLanguage, espeak)
2021-03-21 20:05:06 +00:00
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__,
signingPrivateKeyPem)
2021-03-21 20:05:06 +00:00
refreshTimeline = True
2021-03-21 18:37:06 +00:00
print('')
2021-03-21 13:29:56 +00:00
if refreshTimeline:
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
translate,
2021-03-21 13:29:56 +00:00
screenreader, systemLanguage,
espeak, pageNumber,
newRepliesExist, newDMsExist)