From 66056face50e61c800da3b31a6d7e7466d9bd434 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 9 Mar 2021 13:52:02 +0000 Subject: [PATCH] Add dm and reply notifications to speaker endpoint --- inbox.py | 12 ++++++---- posts.py | 62 -------------------------------------------------- speaker.py | 27 +++++++++++++++++----- utils.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ webapp_post.py | 5 ++-- 5 files changed, 94 insertions(+), 74 deletions(-) diff --git a/inbox.py b/inbox.py index 131647ef9..8acba772d 100644 --- a/inbox.py +++ b/inbox.py @@ -58,12 +58,12 @@ from filters import isFiltered from utils import updateAnnounceCollection from utils import undoAnnounceCollectionEntry from utils import dangerousMarkup +from utils import isDM +from utils import isReply from httpsig import messageContentDigest from posts import createDirectMessagePost from posts import validContentWarning from posts import downloadAnnounce -from posts import isDM -from posts import isReply from posts import isMuted from posts import isImageMedia from posts import sendSignedJson @@ -1408,7 +1408,9 @@ def _receiveAnnounce(recentPostsCache: {}, if isRecentPost(postJsonObject): if not os.path.isfile(postFilename + '.tts'): - updateSpeaker(baseDir, nickname, domain, + domainFull = getFullDomain(domain, port) + updateSpeaker(baseDir, httpPrefix, + nickname, domain, domainFull, postJsonObject, personCache, translate, lookupActor) ttsFile = open(postFilename + '.tts', "w+") @@ -2491,7 +2493,9 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, else: if boxname == 'inbox': if isRecentPost(postJsonObject): - updateSpeaker(baseDir, nickname, domain, + domainFull = getFullDomain(domain, port) + updateSpeaker(baseDir, httpPrefix, + nickname, domain, domainFull, postJsonObject, personCache, translate, None) if not unitTest: diff --git a/posts.py b/posts.py index 33959a7cc..fcfe3bce4 100644 --- a/posts.py +++ b/posts.py @@ -2921,34 +2921,6 @@ def createModeration(baseDir: str, nickname: str, domain: str, port: int, return boxItems -def isDM(postJsonObject: {}) -> bool: - """Returns true if the given post is a DM - """ - if postJsonObject['type'] != 'Create': - return False - if not postJsonObject.get('object'): - return False - if not isinstance(postJsonObject['object'], dict): - return False - if postJsonObject['object']['type'] != 'Note' and \ - postJsonObject['object']['type'] != 'Patch' and \ - postJsonObject['object']['type'] != 'EncryptedMessage' and \ - postJsonObject['object']['type'] != 'Article': - return False - if postJsonObject['object'].get('moderationStatus'): - return False - fields = ('to', 'cc') - for f in fields: - if not postJsonObject['object'].get(f): - continue - for toAddress in postJsonObject['object'][f]: - if toAddress.endswith('#Public'): - return False - if toAddress.endswith('followers'): - return False - return True - - def isImageMedia(session, baseDir: str, httpPrefix: str, nickname: str, domain: str, postJsonObject: {}, translate: {}, @@ -2992,40 +2964,6 @@ def isImageMedia(session, baseDir: str, httpPrefix: str, return False -def isReply(postJsonObject: {}, actor: str) -> bool: - """Returns true if the given post is a reply to the given actor - """ - if postJsonObject['type'] != 'Create': - return False - if not postJsonObject.get('object'): - return False - if not isinstance(postJsonObject['object'], dict): - return False - if postJsonObject['object'].get('moderationStatus'): - return False - if postJsonObject['object']['type'] != 'Note' and \ - postJsonObject['object']['type'] != 'EncryptedMessage' and \ - postJsonObject['object']['type'] != 'Article': - return False - if postJsonObject['object'].get('inReplyTo'): - if isinstance(postJsonObject['object']['inReplyTo'], str): - if postJsonObject['object']['inReplyTo'].startswith(actor): - return True - if not postJsonObject['object'].get('tag'): - return False - if not isinstance(postJsonObject['object']['tag'], list): - return False - for tag in postJsonObject['object']['tag']: - if not tag.get('type'): - continue - if tag['type'] == 'Mention': - if not tag.get('href'): - continue - if actor in tag['href']: - return True - return False - - def _addPostStringToTimeline(postStr: str, boxname: str, postsInBox: [], boxActor: str) -> bool: """ is this a valid timeline post? diff --git a/speaker.py b/speaker.py index 2b6ab90cc..b9314e36e 100644 --- a/speaker.py +++ b/speaker.py @@ -12,6 +12,8 @@ import random import urllib.parse from auth import createBasicAuthHeader from session import getJson +from utils import isDM +from utils import isReply from utils import camelCaseSplit from utils import getDomainFromActor from utils import getNicknameFromActor @@ -263,7 +265,8 @@ def getSpeakerFromServer(baseDir: str, session, def _speakerEndpointJson(displayName: str, summary: str, content: str, imageDescription: str, - links: [], gender: str, postId: str) -> {}: + links: [], gender: str, postId: str, + postDM: bool, postReply: bool) -> {}: """Returns a json endpoint for the TTS speaker """ speakerJson = { @@ -272,7 +275,11 @@ def _speakerEndpointJson(displayName: str, summary: str, "say": content, "imageDescription": imageDescription, "detectedLinks": links, - "id": postId + "id": postId, + "notify": { + "dm": postDM, + "reply": postReply + } } if gender: speakerJson['gender'] = gender @@ -360,7 +367,8 @@ def getSSMLbox(baseDir: str, path: str, instanceTitle, gender) -def _postToSpeakerJson(baseDir: str, nickname: str, domain: str, +def _postToSpeakerJson(baseDir: str, httpPrefix: str, + nickname: str, domain: str, domainFull: str, postJsonObject: {}, personCache: {}, translate: {}, announcingActor: str) -> {}: """Converts an ActivityPub post into some Json containing @@ -429,19 +437,26 @@ def _postToSpeakerJson(baseDir: str, nickname: str, domain: str, postId = None if postJsonObject['object'].get('id'): postId = postJsonObject['object']['id'] + + actor = httpPrefix + '://' + domainFull + '/users/' + nickname + postDM = isDM(postJsonObject) + postReply = isReply(postJsonObject, actor) return _speakerEndpointJson(speakerName, summary, content, imageDescription, - detectedLinks, gender, postId) + detectedLinks, gender, postId, + postDM, postReply) -def updateSpeaker(baseDir: str, nickname: str, domain: str, +def updateSpeaker(baseDir: str, httpPrefix: str, + nickname: str, domain: str, domainFull: str, postJsonObject: {}, personCache: {}, translate: {}, announcingActor: str) -> None: """ Generates a json file which can be used for TTS announcement of incoming inbox posts """ speakerJson = \ - _postToSpeakerJson(baseDir, nickname, domain, + _postToSpeakerJson(baseDir, httpPrefix, + nickname, domain, domainFull, postJsonObject, personCache, translate, announcingActor) speakerFilename = \ diff --git a/utils.py b/utils.py index 57d41662f..1a977d692 100644 --- a/utils.py +++ b/utils.py @@ -2068,3 +2068,65 @@ def rejectPostId(baseDir: str, nickname: str, domain: str, if rejectFile: rejectFile.write('\n') rejectFile.close() + + +def isDM(postJsonObject: {}) -> bool: + """Returns true if the given post is a DM + """ + if postJsonObject['type'] != 'Create': + return False + if not postJsonObject.get('object'): + return False + if not isinstance(postJsonObject['object'], dict): + return False + if postJsonObject['object']['type'] != 'Note' and \ + postJsonObject['object']['type'] != 'Patch' and \ + postJsonObject['object']['type'] != 'EncryptedMessage' and \ + postJsonObject['object']['type'] != 'Article': + return False + if postJsonObject['object'].get('moderationStatus'): + return False + fields = ('to', 'cc') + for f in fields: + if not postJsonObject['object'].get(f): + continue + for toAddress in postJsonObject['object'][f]: + if toAddress.endswith('#Public'): + return False + if toAddress.endswith('followers'): + return False + return True + + +def isReply(postJsonObject: {}, actor: str) -> bool: + """Returns true if the given post is a reply to the given actor + """ + if postJsonObject['type'] != 'Create': + return False + if not postJsonObject.get('object'): + return False + if not isinstance(postJsonObject['object'], dict): + return False + if postJsonObject['object'].get('moderationStatus'): + return False + if postJsonObject['object']['type'] != 'Note' and \ + postJsonObject['object']['type'] != 'EncryptedMessage' and \ + postJsonObject['object']['type'] != 'Article': + return False + if postJsonObject['object'].get('inReplyTo'): + if isinstance(postJsonObject['object']['inReplyTo'], str): + if postJsonObject['object']['inReplyTo'].startswith(actor): + return True + if not postJsonObject['object'].get('tag'): + return False + if not isinstance(postJsonObject['object']['tag'], list): + return False + for tag in postJsonObject['object']['tag']: + if not tag.get('type'): + continue + if tag['type'] == 'Mention': + if not tag.get('href'): + continue + if actor in tag['href']: + return True + return False diff --git a/webapp_post.py b/webapp_post.py index 532f535d2..aca73ad33 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -19,9 +19,9 @@ from like import noOfLikes from follow import isFollowingActor from posts import postIsMuted from posts import getPersonBox -from posts import isDM from posts import downloadAnnounce from posts import populateRepliesJson +from utils import isDM from utils import rejectPostId from utils import isRecentPost from utils import getConfigParam @@ -1304,7 +1304,8 @@ def individualPostAsHtml(allowDownloads: bool, postJsonObject['id']) if announceFilename and postJsonObject.get('actor'): if not os.path.isfile(announceFilename + '.tts'): - updateSpeaker(baseDir, nickname, domain, + updateSpeaker(baseDir, httpPrefix, + nickname, domain, domainFull, postJsonObject, personCache, translate, postJsonObject['actor']) ttsFile = open(announceFilename + '.tts', "w+")