epicyon/notifications_client.py

1088 lines
46 KiB
Python
Raw Normal View History

__filename__ = "notifications_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"
import os
import html
import time
2021-03-04 15:21:38 +00:00
import sys
import select
2021-03-12 10:06:24 +00:00
from pathlib import Path
from random import randint
from utils import getStatusNumber
from utils import loadJson
2021-03-12 10:06:24 +00:00
from utils import saveJson
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import getFullDomain
2021-03-12 12:04:34 +00:00
from utils import isPGPEncrypted
2021-03-04 13:57:30 +00:00
from session import createSession
from speaker import getSpeakerFromServer
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 sendFollowRequestViaServer
from follow import sendUnfollowRequestViaServer
from posts import sendPostViaServer
from announce import sendAnnounceViaServer
2021-03-11 17:15:32 +00:00
from pgp import pgpDecrypt
from pgp import hasLocalPGPkey
from pgp import pgpEncryptToActor
2021-03-04 14:36:24 +00:00
def _waitForKeypress(timeout: int, debug: bool) -> str:
"""Waits for a keypress with a timeout
Returns the key pressed, or None on timeout
"""
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:
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'
speakerCmd = 'picospeaker ' + \
'-l ' + speakerLang + \
' -r ' + str(rate) + \
' -p ' + str(pitch) + ' "' + \
2021-03-10 12:26:10 +00:00
html.unescape(sayText) + '" 2> /dev/null'
2021-03-04 14:54:30 +00:00
os.system(speakerCmd)
2021-03-09 19:52:10 +00:00
def _playNotificationSound(soundFilename: str, player='ffplay') -> None:
"""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,
speakerName='screen reader',
speakerGender='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
def _notificationReplyToPost(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:
2021-03-10 18:30:00 +00:00
"""Use the notification 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,
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
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
sayStr = 'Sending reply'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 19:31:33 +00:00
if sendPostViaServer(__version__,
baseDir, session, nickname, password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
httpPrefix, replyMessage, followersOnly,
commentsEnabled, attach, mediaType,
attachedImageDescription,
cachedWebfingers, personCache, isArticle,
debug, postId, postId, subject) == 0:
sayStr = 'Reply sent'
else:
sayStr = 'Reply failed'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-10 18:30:00 +00:00
def _notificationNewPost(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 notification 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
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
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,
cachedWebfingers, personCache, isArticle,
debug, None, None, subject) == 0:
sayStr = 'Post sent'
else:
sayStr = 'Post failed'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-12 19:55:16 +00:00
def _readLocalBoxPost(boxName: str, index: int,
systemLanguage: str,
screenreader: str, espeak) -> None:
"""Reads a post from the given timeline
"""
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')
msgDir = homeDir + '/.config/epicyon/' + boxName
if not os.path.isdir(msgDir):
os.mkdir(msgDir)
indexList = []
for subdir, dirs, files in os.walk(msgDir):
for f in files:
if not f.endswith('.json'):
continue
indexList.append(f)
indexList.sort(reverse=True)
index -= 1
if index <= 0:
index = 0
if len(indexList) <= index:
return
2021-03-15 11:30:41 +00:00
publishedYear = indexList[index].split('-')[0]
publishedMonth = indexList[index].split('-')[1]
speakerJsonFilename = \
os.path.join(msgDir,
publishedYear + '/' +
publishedMonth + '/' +
indexList[index])
2021-03-12 19:55:16 +00:00
speakerJson = loadJson(speakerJsonFilename)
nameStr = speakerJson['name']
gender = 'They/Them'
if speakerJson.get('gender'):
gender = speakerJson['gender']
# append image description if needed
if not speakerJson.get('imageDescription'):
messageStr = speakerJson['say']
else:
messageStr = speakerJson['say'] + '. ' + \
speakerJson['imageDescription']
content = messageStr
if speakerJson.get('content'):
content = speakerJson['content']
2021-03-12 20:51:04 +00:00
sayStr = 'Reading ' + boxName + ' post ' + str(index + 1) + '.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
time.sleep(2)
2021-03-12 19:55:16 +00:00
# say the speaker's name
_sayCommand(nameStr, nameStr, screenreader,
systemLanguage, espeak,
nameStr, gender)
time.sleep(2)
# speak the post content
_sayCommand(content, messageStr, screenreader,
systemLanguage, espeak,
nameStr, gender)
2021-03-12 20:38:36 +00:00
def _showLocalBox(boxName: str,
screenreader: str, systemLanguage: str, espeak,
startPostIndex=0, noOfPosts=10) -> None:
"""Shows locally stored posts for a given subdirectory
"""
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')
msgDir = homeDir + '/.config/epicyon/' + boxName
if not os.path.isdir(msgDir):
os.mkdir(msgDir)
index = []
for subdir, dirs, files in os.walk(msgDir):
for f in files:
if not f.endswith('.json'):
continue
index.append(f)
2021-03-12 20:32:15 +00:00
if not index:
2021-03-12 20:38:36 +00:00
sayStr = 'You have no ' + boxName + ' posts yet.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
2021-03-14 10:47:01 +00:00
print('')
2021-03-14 10:45:33 +00:00
return
maxPostIndex = len(index)
2021-03-12 18:18:24 +00:00
index.sort(reverse=True)
2021-03-12 20:38:36 +00:00
ctr = 0
for pos in range(startPostIndex, startPostIndex + noOfPosts):
if pos >= maxPostIndex:
break
2021-03-15 11:30:41 +00:00
publishedYear = index[pos].split('-')[0]
publishedMonth = index[pos].split('-')[1]
speakerJsonFilename = \
os.path.join(msgDir,
publishedYear + '/' +
publishedMonth + '/' + index[pos])
speakerJson = loadJson(speakerJsonFilename)
if not speakerJson.get('published'):
continue
published = speakerJson['published'].replace('T', ' ')
2021-03-12 19:13:01 +00:00
posStr = str(pos + 1) + '.'
2021-03-12 18:20:56 +00:00
while len(posStr) < 3:
posStr += ' '
2021-03-12 18:13:51 +00:00
if speakerJson.get('name'):
name = speakerJson['name']
else:
name = ''
while len(name) < 16:
name += ' '
2021-03-12 18:29:22 +00:00
name = (name[:16]) if len(name) > 16 else name
content = speakerJson['content']
2021-03-12 18:26:24 +00:00
while len(content) < 40:
content += ' '
2021-03-12 18:29:22 +00:00
content = (content[:40]) if len(content) > 40 else content
2021-03-12 18:13:51 +00:00
print(str(posStr) + ' | ' + str(name) + ' | ' +
2021-03-12 18:16:27 +00:00
str(published) + ' | ' + str(content) + ' |')
2021-03-12 20:41:20 +00:00
ctr += 1
2021-03-12 20:38:36 +00:00
2021-03-12 20:44:40 +00:00
print('')
2021-03-12 20:46:45 +00:00
sayStr = boxName + ' posts ' + str(startPostIndex + 1) + \
2021-03-12 20:44:40 +00:00
' to ' + str(startPostIndex + ctr) + '. '
sayStr += 'Use the next and prev commands to navigate.'
2021-03-12 20:38:36 +00:00
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
print('')
2021-03-11 12:24:20 +00:00
def _notificationNewDM(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 notification 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()
_notificationNewDMbase(session, handle,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak)
def _storeMessage(speakerJson: {}, boxName: str) -> None:
"""Stores a message in your home directory for later reading
"""
if not speakerJson.get('published'):
return
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')
msgDir = homeDir + '/.config/epicyon/' + boxName
if not os.path.isdir(msgDir):
os.mkdir(msgDir)
publishedYear = speakerJson['published'].split('-')[0]
yearDir = msgDir + '/' + publishedYear
if not os.path.isdir(yearDir):
os.mkdir(yearDir)
publishedMonth = speakerJson['published'].split('-')[1]
monthDir = yearDir + '/' + publishedMonth
if not os.path.isdir(monthDir):
os.mkdir(monthDir)
msgFilename = monthDir + '/' + speakerJson['published'] + '.json'
saveJson(speakerJson, msgFilename)
2021-03-14 10:28:48 +00:00
def _notificationNewDMbase(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 notification client to create a new direct message
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
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
newMessageOriginal = newMessage
if hasLocalPGPkey():
sayStr = \
'Local PGP key detected...' + \
'Fetching PGP public key for ' + toHandle
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
paddedMessage = newMessage
if len(paddedMessage) < 32:
# add some padding before and after
# This is to guard against cribs based on small messages, like "Hi"
for before in range(randint(1, 16)):
paddedMessage = ' ' + paddedMessage
for after in range(randint(1, 16)):
paddedMessage += ' '
cipherText = \
pgpEncryptToActor(paddedMessage, toHandle)
if not cipherText:
sayStr = \
toHandle + ' has no PGP public key. ' + \
'Your message will be sent in clear text'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
else:
newMessage = cipherText
sayStr = 'Message encrypted'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
sayStr = 'Send this direct message, yes or no?'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
yesno = input()
if 'y' not in yesno.lower():
sayStr = 'Abandoning new direct message'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
return
2021-03-11 12:24:20 +00:00
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,
cachedWebfingers, personCache, isArticle,
debug, None, None, subject) == 0:
# store the DM locally
statusNumber, published = getStatusNumber()
postId = \
httpPrefix + '://' + getFullDomain(domain, port) + \
'/users/' + nickname + '/statuses/' + statusNumber
speakerJson = {
"name": nickname,
"summary": "",
"content": newMessageOriginal,
"say": newMessageOriginal,
"published": published,
"imageDescription": "",
"detectedLinks": [],
"id": postId,
"direct": True
}
_storeMessage(speakerJson, 'sent')
2021-03-11 12:24:20 +00:00
sayStr = 'Direct message sent'
else:
sayStr = 'Direct message failed'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
def runNotificationsClient(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,
debug: bool) -> None:
"""Runs the notifications and screen reader client,
which announces new inbox items
2021-03-04 13:57:30 +00:00
"""
2021-03-14 10:41:21 +00:00
bannerFilename = 'theme/default/banner.txt'
if os.path.isfile(bannerFilename):
with open(bannerFilename, 'r') as bannerFile:
banner = bannerFile.read()
if banner:
print(banner + '\n')
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-10 10:49:45 +00:00
sayStr = '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:
print('Running desktop notifications for ' + nickname + '@' + domain)
if notificationSounds:
2021-03-10 10:49:45 +00:00
sayStr = 'Notification sounds on'
else:
2021-03-10 10:49:45 +00:00
sayStr = 'Notification sounds off'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:49:45 +00:00
systemLanguage, espeak)
sayStr = '/q or /quit to exit'
_sayCommand(sayStr, sayStr, screenreader,
2021-03-10 10:49:45 +00:00
systemLanguage, espeak)
2021-03-10 12:41:26 +00:00
print('')
2021-03-10 10:51:06 +00:00
keyPress = _waitForKeypress(2, debug)
2021-03-10 10:25:41 +00:00
originalScreenReader = screenreader
domainFull = getFullDomain(domain, port)
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
2021-03-04 13:57:30 +00:00
prevSay = ''
2021-03-09 19:52:10 +00:00
prevDM = False
prevReply = False
prevCalendar = False
prevFollow = False
prevLike = ''
prevShare = False
dmSoundFilename = 'dm.ogg'
replySoundFilename = 'reply.ogg'
calendarSoundFilename = 'calendar.ogg'
followSoundFilename = 'follow.ogg'
likeSoundFilename = 'like.ogg'
shareSoundFilename = '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 = {}
2021-03-12 19:13:01 +00:00
currDMIndex = 0
currSentIndex = 0
currInboxIndex = 0
currTimeline = ''
2021-03-04 13:57:30 +00:00
while (1):
session = createSession(proxyType)
speakerJson = \
getSpeakerFromServer(baseDir, session, nickname, password,
domain, port, httpPrefix, True, __version__)
if speakerJson:
2021-03-09 19:52:10 +00:00
if speakerJson.get('notify'):
title = 'Epicyon'
if speakerJson['notify'].get('title'):
title = speakerJson['notify']['title']
2021-03-09 19:52:10 +00:00
soundsDir = 'theme/default/sounds'
if speakerJson['notify'].get('theme'):
2021-03-09 23:22:48 +00:00
if isinstance(speakerJson['notify']['theme'], str):
soundsDir = \
'theme/' + \
speakerJson['notify']['theme'] + '/sounds'
if not os.path.isdir(soundsDir):
soundsDir = 'theme/default/sounds'
2021-03-09 22:47:10 +00:00
if speakerJson['notify']['dm'] != prevDM:
if speakerJson['notify']['dm'] is True:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
dmSoundFilename, player)
2021-03-09 22:47:10 +00:00
_desktopNotification(notificationType, title,
'New direct message ' +
actor + '/dm')
2021-03-09 22:47:10 +00:00
prevDM = speakerJson['notify']['dm']
elif speakerJson['notify']['reply'] != prevReply:
if speakerJson['notify']['reply'] is True:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
replySoundFilename,
player)
2021-03-09 22:47:10 +00:00
_desktopNotification(notificationType, title,
'New reply ' +
actor + '/tlreplies')
2021-03-09 22:35:06 +00:00
prevReply = speakerJson['notify']['reply']
2021-03-09 22:47:10 +00:00
elif speakerJson['notify']['calendar'] != prevCalendar:
if speakerJson['notify']['calendar'] is True:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
calendarSoundFilename,
player)
2021-03-09 22:47:10 +00:00
_desktopNotification(notificationType, title,
'New calendar event ' +
actor + '/calendar')
2021-03-09 22:47:10 +00:00
prevCalendar = speakerJson['notify']['calendar']
elif speakerJson['notify']['followRequests'] != prevFollow:
if speakerJson['notify']['followRequests'] is True:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
followSoundFilename,
player)
2021-03-09 22:47:10 +00:00
_desktopNotification(notificationType, title,
'New follow request ' +
actor + '/followers#buttonheader')
2021-03-09 22:47:10 +00:00
prevFollow = speakerJson['notify']['followRequests']
elif speakerJson['notify']['likedBy'] != prevLike:
2021-03-12 20:28:04 +00:00
if '##sent##' not in speakerJson['notify']['likedBy']:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
likeSoundFilename, player)
_desktopNotification(notificationType, title,
'New like ' +
speakerJson['notify']['likedBy'])
2021-03-09 22:47:10 +00:00
prevLike = speakerJson['notify']['likedBy']
elif speakerJson['notify']['share'] != prevShare:
if speakerJson['notify']['share'] is True:
if notificationSounds:
_playNotificationSound(soundsDir + '/' +
shareSoundFilename,
player)
2021-03-09 22:47:10 +00:00
_desktopNotification(notificationType, title,
'New shared item ' +
actor + '/shares')
2021-03-09 22:47:10 +00:00
prevShare = speakerJson['notify']['share']
2021-03-09 19:52:10 +00:00
if speakerJson.get('say'):
if speakerJson['say'] != prevSay:
if speakerJson.get('name'):
nameStr = speakerJson['name']
gender = 'They/Them'
if speakerJson.get('gender'):
gender = speakerJson['gender']
# append image description if needed
if not speakerJson.get('imageDescription'):
2021-03-10 12:37:44 +00:00
messageStr = speakerJson['say']
2021-03-09 19:52:10 +00:00
else:
2021-03-10 12:37:44 +00:00
messageStr = speakerJson['say'] + '. ' + \
2021-03-09 19:52:10 +00:00
speakerJson['imageDescription']
encryptedMessage = False
if speakerJson.get('id') and \
isPGPEncrypted(messageStr):
encryptedMessage = True
messageStr = pgpDecrypt(messageStr,
speakerJson['id'])
2021-03-10 12:15:08 +00:00
content = messageStr
if speakerJson.get('content'):
if not encryptedMessage:
content = speakerJson['content']
2021-03-12 11:43:32 +00:00
else:
content = '🔓 ' + messageStr
2021-03-10 12:11:42 +00:00
# say the speaker's name
_sayCommand(nameStr, nameStr, screenreader,
2021-03-10 12:11:42 +00:00
systemLanguage, espeak,
nameStr, gender)
time.sleep(2)
2021-03-09 19:52:10 +00:00
# speak the post content
_sayCommand(content, messageStr, screenreader,
2021-03-10 12:11:42 +00:00
systemLanguage, espeak,
nameStr, gender)
2021-03-09 19:52:10 +00:00
2021-03-12 10:06:24 +00:00
if encryptedMessage:
speakerJson['content'] = content
speakerJson['say'] = messageStr
speakerJson['decrypted'] = True
_storeMessage(speakerJson, 'dm')
elif speakerJson.get('direct'):
speakerJson['decrypted'] = False
_storeMessage(speakerJson, 'dm')
else:
speakerJson['decrypted'] = False
if storeInboxPosts:
_storeMessage(speakerJson, 'inbox')
2021-03-12 10:06:24 +00:00
2021-03-10 12:40:17 +00:00
print('')
2021-03-09 19:52:10 +00:00
prevSay = speakerJson['say']
2021-03-04 13:57:30 +00:00
# wait for a while, or until a key is pressed
if noKeyPress:
time.sleep(10)
else:
keyPress = _waitForKeypress(30, debug)
2021-03-04 18:05:46 +00:00
if keyPress:
if keyPress.startswith('/'):
keyPress = keyPress[1:]
if keyPress == 'q' or keyPress == 'quit' or keyPress == '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:
keyPress = _waitForKeypress(2, debug)
2021-03-04 18:05:46 +00:00
break
elif keyPress.startswith('show dm'):
2021-03-12 19:16:23 +00:00
currDMIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('dm',
screenreader, systemLanguage, espeak,
currDMIndex, 10)
2021-03-12 19:13:01 +00:00
currTimeline = 'dm'
elif keyPress.startswith('show sen'):
2021-03-12 19:16:23 +00:00
currSentIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('sent',
screenreader, systemLanguage, espeak,
currSentIndex, 10)
2021-03-12 19:13:01 +00:00
currTimeline = 'sent'
2021-03-12 19:16:23 +00:00
elif keyPress == 'show' or keyPress.startswith('show in'):
currInboxIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('inbox',
screenreader, systemLanguage, espeak,
currInboxIndex, 10)
2021-03-12 19:13:01 +00:00
currTimeline = 'inbox'
2021-03-12 19:15:06 +00:00
elif keyPress.startswith('next'):
2021-03-12 19:13:01 +00:00
if currTimeline == 'dm':
currDMIndex += 10
2021-03-12 20:38:36 +00:00
_showLocalBox('dm',
screenreader, systemLanguage, espeak,
currDMIndex, 10)
2021-03-12 19:13:01 +00:00
elif currTimeline == 'sent':
currSentIndex += 10
2021-03-12 20:38:36 +00:00
_showLocalBox('sent',
screenreader, systemLanguage, espeak,
currSentIndex, 10)
2021-03-12 19:13:01 +00:00
elif currTimeline == 'inbox':
currInboxIndex += 10
2021-03-12 20:38:36 +00:00
_showLocalBox('inbox',
screenreader, systemLanguage, espeak,
currInboxIndex, 10)
2021-03-12 19:13:01 +00:00
elif keyPress.startswith('prev'):
if currTimeline == 'dm':
currDMIndex -= 10
if currDMIndex < 0:
currDMIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('dm',
screenreader, systemLanguage, espeak,
currDMIndex, 10)
2021-03-12 19:13:01 +00:00
elif currTimeline == 'sent':
currSentIndex -= 10
if currSentIndex < 0:
currSentIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('sent',
screenreader, systemLanguage, espeak,
currSentIndex, 10)
2021-03-12 19:13:01 +00:00
elif currTimeline == 'inbox':
currInboxIndex -= 10
if currInboxIndex < 0:
currInboxIndex = 0
2021-03-12 20:38:36 +00:00
_showLocalBox('inbox',
screenreader, systemLanguage, espeak,
currInboxIndex, 10)
2021-03-12 19:55:16 +00:00
elif keyPress.startswith('read '):
postIndexStr = keyPress.split('read ')[1]
if postIndexStr.isdigit():
postIndex = int(postIndexStr)
_readLocalBoxPost(currTimeline, postIndex,
systemLanguage, screenreader, espeak)
print('')
2021-03-10 18:30:00 +00:00
elif keyPress == 'reply' or keyPress == 'r':
if speakerJson.get('id'):
postId = speakerJson['id']
subject = None
if speakerJson.get('summary'):
subject = speakerJson['summary']
sessionReply = createSession(proxyType)
_notificationReplyToPost(sessionReply, postId,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug, subject,
screenreader, systemLanguage,
espeak)
print('')
2021-03-11 12:24:20 +00:00
elif (keyPress == 'post' or keyPress == 'p' or
keyPress == 'send' or
2021-03-11 12:53:00 +00:00
keyPress.startswith('dm ') or
keyPress.startswith('direct message ') or
2021-03-11 12:24:20 +00:00
keyPress.startswith('post ') or
keyPress.startswith('send ')):
sessionPost = createSession(proxyType)
2021-03-11 12:53:00 +00:00
if keyPress.startswith('dm ') or \
keyPress.startswith('direct message ') or \
keyPress.startswith('post ') or \
2021-03-11 12:24:20 +00:00
keyPress.startswith('send '):
2021-03-11 12:32:31 +00:00
keyPress = keyPress.replace(' to ', ' ')
2021-03-11 12:54:36 +00:00
keyPress = keyPress.replace(' dm ', ' ')
keyPress = keyPress.replace(' DM ', ' ')
2021-03-11 12:24:20 +00:00
# direct message
2021-03-11 12:30:29 +00:00
toHandle = None
2021-03-11 12:24:20 +00:00
if keyPress.startswith('post '):
toHandle = keyPress.split('post ', 1)[1]
2021-03-11 12:30:29 +00:00
elif keyPress.startswith('send '):
2021-03-11 12:24:20 +00:00
toHandle = keyPress.split('send ', 1)[1]
2021-03-11 12:53:00 +00:00
elif keyPress.startswith('dm '):
toHandle = keyPress.split('dm ', 1)[1]
elif keyPress.startswith('direct message '):
toHandle = keyPress.split('direct message ', 1)[1]
2021-03-11 12:30:29 +00:00
if toHandle:
_notificationNewDM(sessionPost, toHandle,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak)
2021-03-11 12:24:20 +00:00
else:
# public post
_notificationNewPost(sessionPost,
baseDir, nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
espeak)
2021-03-10 18:30:00 +00:00
print('')
2021-03-10 13:04:22 +00:00
elif keyPress == 'like':
if nameStr and gender and messageStr:
sayStr = 'Liking post by ' + nameStr
_sayCommand(sayStr, sayStr,
2021-03-10 13:04:22 +00:00
screenreader,
systemLanguage, espeak)
2021-03-10 16:56:27 +00:00
sessionLike = createSession(proxyType)
sendLikeViaServer(baseDir, sessionLike,
2021-03-10 13:04:22 +00:00
nickname, password,
domain, port,
httpPrefix, speakerJson['id'],
cachedWebfingers, personCache,
True, __version__)
2021-03-10 13:13:54 +00:00
print('')
2021-03-10 13:07:24 +00:00
elif keyPress == 'unlike' or keyPress == 'undo like':
if nameStr and gender and messageStr:
sayStr = 'Undoing like of post by ' + nameStr
_sayCommand(sayStr, sayStr,
2021-03-10 13:07:24 +00:00
screenreader,
systemLanguage, espeak)
2021-03-10 16:56:27 +00:00
sessionUnlike = createSession(proxyType)
sendUndoLikeViaServer(baseDir, sessionUnlike,
2021-03-10 13:07:24 +00:00
nickname, password,
domain, port,
httpPrefix, speakerJson['id'],
cachedWebfingers, personCache,
True, __version__)
2021-03-10 13:13:54 +00:00
print('')
elif (keyPress == 'announce' or
keyPress == 'boost' or
keyPress == 'retweet'):
if speakerJson.get('id'):
if nameStr and gender and messageStr:
postId = speakerJson['id']
sayStr = 'Announcing post by ' + nameStr
_sayCommand(sayStr, sayStr,
screenreader,
systemLanguage, espeak)
sessionAnnounce = createSession(proxyType)
sendAnnounceViaServer(baseDir, sessionAnnounce,
nickname, password,
domain, port,
httpPrefix, postId,
cachedWebfingers, personCache,
True, __version__)
print('')
elif keyPress.startswith('follow '):
followHandle = keyPress.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)
sendFollowRequestViaServer(baseDir, sessionFollow,
nickname, password,
domain, port,
followNickname,
followDomain,
followPort,
httpPrefix,
cachedWebfingers,
personCache,
debug, __version__)
else:
sayStr = followHandle + ' is not valid'
_sayCommand(sayStr,
screenreader, systemLanguage, espeak)
print('')
elif (keyPress.startswith('unfollow ') or
keyPress.startswith('stop following ')):
followHandle = keyPress.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__)
else:
sayStr = followHandle + ' is not valid'
_sayCommand(sayStr, sayStr,
screenreader, systemLanguage, espeak)
print('')
2021-03-10 13:13:54 +00:00
elif (keyPress == 'repeat' or keyPress == 'replay' or
2021-03-11 11:15:41 +00:00
keyPress == 'rp' or keyPress == 'again' or
keyPress == '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-11 11:15:41 +00:00
elif (keyPress == 'sounds on' or
keyPress == 'sound on' or
keyPress == '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-11 11:15:41 +00:00
elif (keyPress == 'sounds off' or
keyPress == 'sound off' or
keyPress == '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-10 10:32:08 +00:00
elif (keyPress == 'speak' or
keyPress == 'screen reader on' or
keyPress == 'speaker on' or
keyPress == 'talker on' or
keyPress == '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-10 10:32:08 +00:00
elif (keyPress == 'mute' or
keyPress == 'screen reader off' or
keyPress == 'speaker off' or
keyPress == 'talker off' or
keyPress == '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')