Re-adding notifications to the desktop client

merge-requests/30/head
Bob Mottram 2021-03-22 13:09:17 +00:00
parent 5a9727ecf9
commit f6a07cde76
3 changed files with 166 additions and 17 deletions

View File

@ -13,6 +13,7 @@ import sys
import select import select
import webbrowser import webbrowser
import urllib.parse import urllib.parse
from pathlib import Path
from random import randint from random import randint
from utils import getFullDomain from utils import getFullDomain
from utils import isDM from utils import isDM
@ -103,6 +104,124 @@ def _desktopHelp() -> None:
print('') print('')
def _createDesktopConfig(actor: str) -> None:
"""Sets up directories for desktop client configuration
"""
homeDir = str(Path.home())
if not os.path.isdir(homeDir + '/.config'):
os.mkdir(homeDir + '/.config')
if not os.path.isdir(homeDir + '/.config/epicyon'):
os.mkdir(homeDir + '/.config/epicyon')
nickname = getNicknameFromActor(actor)
domain, port = getDomainFromActor(actor)
handle = nickname + '@' + domain
if port != 443 and port != 80:
handle += '_' + str(port)
readPostsDir = homeDir + '/.config/epicyon/' + handle
if not os.path.isdir(readPostsDir):
os.mkdir(readPostsDir)
def _markPostAsRead(actor: str, postId: str, postCategory: str) -> None:
"""Marks the given post as read by the given actor
"""
homeDir = str(Path.home())
_createDesktopConfig(actor)
nickname = getNicknameFromActor(actor)
domain, port = getDomainFromActor(actor)
handle = nickname + '@' + domain
if port != 443 and port != 80:
handle += '_' + str(port)
readPostsDir = homeDir + '/.config/epicyon/' + handle
readPostsFilename = readPostsDir + '/' + postCategory + '.txt'
if os.path.isfile(readPostsFilename):
if postId in open(readPostsFilename).read():
return
try:
# prepend to read posts file
postId += '\n'
with open(readPostsFilename, 'r+') as readFile:
content = readFile.read()
if postId not in content:
readFile.seek(0, 0)
readFile.write(postId + content)
except Exception as e:
print('WARN: Failed to mark post as read' + str(e))
else:
readFile = open(readPostsFilename, 'w+')
if readFile:
readFile.write(postId + '\n')
readFile.close()
def _hasReadPost(actor: str, postId: str, postCategory: str) -> bool:
"""Returns true if the given post has been read by the actor
"""
homeDir = str(Path.home())
_createDesktopConfig(actor)
nickname = getNicknameFromActor(actor)
domain, port = getDomainFromActor(actor)
handle = nickname + '@' + domain
if port != 443 and port != 80:
handle += '_' + str(port)
readPostsDir = homeDir + '/.config/epicyon/' + handle
readPostsFilename = readPostsDir + '/' + postCategory + '.txt'
if os.path.isfile(readPostsFilename):
if postId in open(readPostsFilename).read():
return True
return False
def _postIsToYou(actor: str, postJsonObject: {}) -> bool:
"""Returns true if the post is to the actor
"""
toYourActor = False
if postJsonObject.get('to'):
if actor in postJsonObject['to']:
toYourActor = True
if not toYourActor and postJsonObject.get('object'):
if isinstance(postJsonObject['object'], dict):
if postJsonObject['object'].get('to'):
if actor in postJsonObject['object']['to']:
toYourActor = True
return toYourActor
def _newDesktopNotifications(actor: str, inboxJson: {},
notifyJson: {}) -> None:
"""Looks for changes in the inbox and adds notifications
"""
if not inboxJson:
return
if not inboxJson.get('orderedItems'):
return
newDM = False
newReply = False
for postJsonObject in inboxJson['orderedItems']:
if not postJsonObject.get('id'):
continue
if not _postIsToYou(actor, postJsonObject):
continue
if 'dmNotify' not in notifyJson:
notifyJson['dmNotify'] = False
if isDM(postJsonObject):
if not newDM:
if not _hasReadPost(actor, postJsonObject['id'], 'dm'):
if notifyJson.get('dmPostId'):
if notifyJson['dmPostId'] != postJsonObject['id']:
notifyJson['dmNotify'] = True
newDM = True
notifyJson['dmPostId'] = postJsonObject['id']
else:
if not newReply:
if not _hasReadPost(actor, postJsonObject['id'], 'replies'):
if notifyJson.get('repliesPostId'):
if notifyJson['repliesPostId'] != postJsonObject['id']:
notifyJson['repliesNotify'] = True
newReply = True
notifyJson['repliesPostId'] = postJsonObject['id']
def _desktopClearScreen() -> None: def _desktopClearScreen() -> None:
"""Clears the screen """Clears the screen
""" """
@ -439,7 +558,7 @@ def _readLocalBoxPost(session, nickname: str, domain: str,
pageNumber: int, index: int, boxJson: {}, pageNumber: int, index: int, boxJson: {},
systemLanguage: str, systemLanguage: str,
screenreader: str, espeak, screenreader: str, espeak,
translate: {}) -> {}: translate: {}, yourActor: str) -> {}:
"""Reads a post from the given timeline """Reads a post from the given timeline
Returns the speaker json Returns the speaker json
""" """
@ -532,6 +651,14 @@ def _readLocalBoxPost(session, nickname: str, domain: str,
_sayCommand(content, messageStr, screenreader, _sayCommand(content, messageStr, screenreader,
systemLanguage, espeak, systemLanguage, espeak,
nameStr, gender) nameStr, gender)
# if the post is addressed to you then mark it as read
if _postIsToYou(yourActor, postJsonObject):
if isDM(postJsonObject['id']):
_markPostAsRead(yourActor, postJsonObject['id'], 'dm')
else:
_markPostAsRead(yourActor, postJsonObject['id'], 'replies')
return postJsonObject return postJsonObject
@ -920,8 +1047,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
# prevFollow = False # prevFollow = False
# prevLike = '' # prevLike = ''
# prevShare = False # prevShare = False
# dmSoundFilename = 'dm.ogg' dmSoundFilename = 'dm.ogg'
# replySoundFilename = 'reply.ogg' replySoundFilename = 'reply.ogg'
# calendarSoundFilename = 'calendar.ogg' # calendarSoundFilename = 'calendar.ogg'
# followSoundFilename = 'follow.ogg' # followSoundFilename = 'follow.ogg'
# likeSoundFilename = 'like.ogg' # likeSoundFilename = 'like.ogg'
@ -937,11 +1064,6 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
newDMsExist = False newDMsExist = False
pgpKeyUpload = False pgpKeyUpload = False
# NOTE: These are dummy calls to make unit tests pass
# they should be removed later
_desktopNotification("", "test", "message")
_playNotificationSound("test83639")
sayStr = indent + 'Loading translations file' sayStr = indent + 'Loading translations file'
_sayCommand(sayStr, sayStr, screenreader, _sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak) systemLanguage, espeak)
@ -956,6 +1078,11 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
sayStr = indent + '/q or /quit to exit' sayStr = indent + '/q or /quit to exit'
_sayCommand(sayStr, sayStr, screenreader, _sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak) systemLanguage, espeak)
domainFull = getFullDomain(domain, port)
yourActor = httpPrefix + '://' + domainFull + '/users/' + nickname
notifyJson = {}
prevTimelineFirstId = '' prevTimelineFirstId = ''
while (1): while (1):
if not pgpKeyUpload: if not pgpKeyUpload:
@ -978,6 +1105,27 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
currTimeline, pageNumber, currTimeline, pageNumber,
debug) debug)
if currTimeline != 'inbox':
# monitor the inbox to generate notifications
inboxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
'inbox', pageNumber,
debug)
else:
inboxJson = boxJson
if inboxJson:
_newDesktopNotifications(yourActor, inboxJson, notifyJson)
if notifyJson.get('dmNotify'):
_desktopNotification(notificationType,
"Epicyon", "New DM " + yourActor + '/dm')
_playNotificationSound(dmSoundFilename)
if notifyJson.get('repliesNotify'):
_desktopNotification(notificationType,
"Epicyon",
"New reply " + yourActor + '/replies')
_playNotificationSound(replySoundFilename)
if boxJson: if boxJson:
timelineFirstId = _getFirstItemId(boxJson) timelineFirstId = _getFirstItemId(boxJson)
if timelineFirstId != prevTimelineFirstId: if timelineFirstId != prevTimelineFirstId:
@ -1113,7 +1261,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
httpPrefix, baseDir, currTimeline, httpPrefix, baseDir, currTimeline,
pageNumber, postIndex, boxJson, pageNumber, postIndex, boxJson,
systemLanguage, screenreader, systemLanguage, screenreader,
espeak, translate) espeak, translate, yourActor)
print('') print('')
elif commandStr == 'reply' or commandStr == 'r': elif commandStr == 'reply' or commandStr == 'r':
if postJsonObject: if postJsonObject:
@ -1601,11 +1749,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
_desktopGetBoxPostObject(boxJson, currIndex) _desktopGetBoxPostObject(boxJson, currIndex)
if postJsonObject: if postJsonObject:
if postJsonObject.get('id'): if postJsonObject.get('id'):
domainFull = getFullDomain(domain, port)
actor = httpPrefix + '://' + \
domainFull + '/users/' + nickname
rmActor = postJsonObject['object']['attributedTo'] rmActor = postJsonObject['object']['attributedTo']
if rmActor != actor: if rmActor != yourActor:
sayStr = 'You can only delete your own posts' sayStr = 'You can only delete your own posts'
_sayCommand(sayStr, sayStr, _sayCommand(sayStr, sayStr,
screenreader, screenreader,

View File

@ -1612,12 +1612,14 @@ def populateReplies(baseDir: str, httpPrefix: str, domain: str,
return False return False
if messageId not in open(postRepliesFilename).read(): if messageId not in open(postRepliesFilename).read():
repliesFile = open(postRepliesFilename, 'a+') repliesFile = open(postRepliesFilename, 'a+')
repliesFile.write(messageId + '\n') if repliesFile:
repliesFile.close() repliesFile.write(messageId + '\n')
repliesFile.close()
else: else:
repliesFile = open(postRepliesFilename, 'w+') repliesFile = open(postRepliesFilename, 'w+')
repliesFile.write(messageId + '\n') if repliesFile:
repliesFile.close() repliesFile.write(messageId + '\n')
repliesFile.close()
return True return True

View File

@ -253,6 +253,7 @@ def removeHtml(content: str) -> str:
if '<' not in content: if '<' not in content:
return content return content
removing = False removing = False
content = content.replace('<a href', ' <a href')
content = content.replace('<q>', '"').replace('</q>', '"') content = content.replace('<q>', '"').replace('</q>', '"')
result = '' result = ''
for ch in content: for ch in content:
@ -262,6 +263,7 @@ def removeHtml(content: str) -> str:
removing = False removing = False
elif not removing: elif not removing:
result += ch result += ch
result = result.replace(' ', ' ')
return result return result