From 839f1a1a912f9d7a34fbd52efc1cbbb5d3200cab Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Mon, 13 Sep 2021 12:34:56 +0100 Subject: [PATCH] Convert announced peertube videos to Note --- desktop_client.py | 14 ++-- inbox.py | 4 +- posts.py | 25 +++++-- video.py | 176 ++++++++++++++++++++++++++++++++++++++++++++++ webapp_post.py | 4 +- 5 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 video.py diff --git a/desktop_client.py b/desktop_client.py index 67c94ef1d..ce5b54a37 100644 --- a/desktop_client.py +++ b/desktop_client.py @@ -664,7 +664,8 @@ def _readLocalBoxPost(session, nickname: str, domain: str, screenreader: str, espeak, translate: {}, yourActor: str, domainFull: str, personCache: {}, - signingPrivateKeyPem: str) -> {}: + signingPrivateKeyPem: str, + blockedCache: {}) -> {}: """Reads a post from the given timeline Returns the post json """ @@ -702,7 +703,8 @@ def _readLocalBoxPost(session, nickname: str, domain: str, recentPostsCache, False, systemLanguage, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) if postJsonObject2: if hasObjectDict(postJsonObject2): if postJsonObject2['object'].get('attributedTo') and \ @@ -1315,6 +1317,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, # TODO: this should probably be retrieved somehow from the server signingPrivateKeyPem = None + blockedCache = {} + indent = ' ' if showNewPosts: indent = '' @@ -1624,7 +1628,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, systemLanguage, screenreader, espeak, translate, yourActor, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) print('') sayStr = 'Press Enter to continue...' sayStr2 = _highlightText(sayStr) @@ -2381,7 +2386,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str, recentPostsCache, False, systemLanguage, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) if postJsonObject2: postJsonObject = postJsonObject2 if postJsonObject: diff --git a/inbox.py b/inbox.py index 1ccfad5d3..700f58245 100644 --- a/inbox.py +++ b/inbox.py @@ -1488,6 +1488,7 @@ def _receiveAnnounce(recentPostsCache: {}, messageJson['type']) return False + blockedCache = {} prefixes = getProtocolPrefixes() # is the domain of the announce actor blocked? objectDomain = messageJson['object'] @@ -1579,7 +1580,8 @@ def _receiveAnnounce(recentPostsCache: {}, recentPostsCache, debug, systemLanguage, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) if not postJsonObject: print('WARN: unable to download announce: ' + str(messageJson)) notInOnion = True diff --git a/posts.py b/posts.py index 7baa016c2..f0edef171 100644 --- a/posts.py +++ b/posts.py @@ -80,6 +80,7 @@ from filters import isFiltered from git import convertPostToPatch from linked_data_sig import generateJsonSignature from petnames import resolvePetnames +from video import convertVideoToNote def isModerator(baseDir: str, nickname: str) -> bool: @@ -3282,6 +3283,7 @@ def isImageMedia(session, baseDir: str, httpPrefix: str, """Returns true if the given post has attached image media """ if postJsonObject['type'] == 'Announce': + blockedCache = {} postJsonAnnounce = \ downloadAnnounce(session, baseDir, httpPrefix, nickname, domain, postJsonObject, @@ -3291,7 +3293,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str, recentPostsCache, debug, systemLanguage, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) if postJsonAnnounce: postJsonObject = postJsonAnnounce if postJsonObject['type'] != 'Create': @@ -4302,7 +4305,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str, recentPostsCache: {}, debug: bool, systemLanguage: str, domainFull: str, personCache: {}, - signingPrivateKeyPem: str) -> {}: + signingPrivateKeyPem: str, + blockedCache: {}) -> {}: """Download the post referenced by an announce """ if not postJsonObject.get('object'): @@ -4399,6 +4403,18 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str, baseDir, nickname, domain, postId, recentPostsCache) return None + if not announcedJson.get('type'): + _rejectAnnounce(announceFilename, + baseDir, nickname, domain, postId, + recentPostsCache) + return None + if announcedJson['type'] == 'Video': + convertedJson = \ + convertVideoToNote(baseDir, nickname, domain, + systemLanguage, + announcedJson, blockedCache) + if convertedJson: + announcedJson = convertedJson if '/statuses/' not in announcedJson['id']: _rejectAnnounce(announceFilename, baseDir, nickname, domain, postId, @@ -4409,11 +4425,6 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str, baseDir, nickname, domain, postId, recentPostsCache) return None - if not announcedJson.get('type'): - _rejectAnnounce(announceFilename, - baseDir, nickname, domain, postId, - recentPostsCache) - return None if announcedJson['type'] != 'Note' and \ announcedJson['type'] != 'Article': # You can only announce Note or Article types diff --git a/video.py b/video.py new file mode 100644 index 000000000..d910ca4d1 --- /dev/null +++ b/video.py @@ -0,0 +1,176 @@ +__filename__ = "video.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.2.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@libreserver.org" +__status__ = "Production" +__module_group__ = "Timeline" + +from utils import getFullDomain +from utils import getNicknameFromActor +from utils import getDomainFromActor +from blocking import isBlocked +from filters import isFiltered + + +def convertVideoToNote(baseDir: str, nickname: str, domain: str, + systemLanguage: str, + postJsonObject: {}, blockedCache: {}) -> {}: + """Converts a PeerTube Video ActivityPub(ish) object into + a Note, so that it can then be displayed in a timeline + """ + # check that the required fields are present + requiredFields = ( + 'type', '@context', 'id', 'published', 'to', 'cc', + 'attributedTo', 'commentsEnabled', 'content', 'sensitive', + 'name', 'url' + ) + for fieldName in requiredFields: + if not postJsonObject.get(fieldName): + return None + + if postJsonObject['type'] != 'Video': + return None + + # who is this attributed to ? + attributedTo = None + if isinstance(postJsonObject['attributedTo'], str): + attributedTo = postJsonObject['attributedTo'] + elif isinstance(postJsonObject['attributedTo'], list): + for entity in postJsonObject['attributedTo']: + if not isinstance(entity, dict): + continue + if not entity.get('type'): + continue + if entity['type'] != 'Person': + continue + if not entity.get('id'): + continue + attributedTo = entity['id'] + break + if not attributedTo: + return None + + # get the language of the video + postLanguage = systemLanguage + if postJsonObject.get('language'): + if isinstance(postJsonObject['language'], dict): + if postJsonObject['language'].get('identifier'): + postLanguage = postJsonObject['language']['identifier'] + + # check that the attributed actor is not blocked + postNickname = getNicknameFromActor(attributedTo) + if not postNickname: + return None + postDomain, postDomainPort = getDomainFromActor(attributedTo) + if not postDomain: + return None + postDomainFull = getFullDomain(postDomain, postDomainPort) + if isBlocked(baseDir, nickname, domain, + postNickname, postDomainFull, blockedCache): + return None + + # check that the content is valid + if isFiltered(baseDir, nickname, domain, postJsonObject['name']): + return None + if isFiltered(baseDir, nickname, domain, postJsonObject['content']): + return None + + # get the content + content = '

' + postJsonObject['name'] + '

' + if postJsonObject.get('license'): + if isinstance(postJsonObject['license'], dict): + if postJsonObject['license'].get('name'): + if isFiltered(baseDir, nickname, domain, + postJsonObject['license']['name']): + return None + content += '

' + postJsonObject['license']['name'] + '

' + content += postJsonObject['content'] + + conversationId = postJsonObject['id'] + + mediaType = None + mediaUrl = None + mediaTorrent = None + mediaMagnet = None + for mediaLink in postJsonObject['url']: + if not isinstance(mediaLink, dict): + continue + if not mediaLink.get('mediaType'): + continue + if not mediaLink.get('href'): + continue + if mediaLink['mediaType'] == 'application/x-bittorrent': + mediaTorrent = mediaLink['href'] + if mediaLink['href'].startswith('magnet:'): + mediaMagnet = mediaLink['href'] + if mediaLink['mediaType'] != 'video/mp4' and \ + mediaLink['mediaType'] != 'video/ogv': + continue + if not mediaUrl: + mediaType = mediaLink['mediaType'] + mediaUrl = mediaLink['href'] + + if not mediaUrl: + return None + + attachment = [{ + 'mediaType': mediaType, + 'name': postJsonObject['content'], + 'type': 'Document', + 'url': mediaUrl + }] + + if mediaTorrent or mediaMagnet: + content += '

' + if mediaTorrent: + content += ' ' + if mediaMagnet: + content += '🧲' + content += '

' + + newPost = { + '@context': postJsonObject['@context'], + 'id': postJsonObject['id'] + '/activity', + 'type': 'Create', + 'actor': attributedTo, + 'published': postJsonObject['published'], + 'to': postJsonObject['to'], + 'cc': postJsonObject['cc'], + 'object': { + 'id': postJsonObject['id'], + 'conversation': conversationId, + 'type': 'Note', + 'summary': None, + 'inReplyTo': None, + 'published': postJsonObject['published'], + 'url': postJsonObject['id'], + 'attributedTo': attributedTo, + 'to': postJsonObject['to'], + 'cc': postJsonObject['cc'], + 'sensitive': postJsonObject['sensitive'], + 'atomUri': postJsonObject['id'], + 'inReplyToAtomUri': None, + 'commentsEnabled': postJsonObject['commentsEnabled'], + 'rejectReplies': not postJsonObject['commentsEnabled'], + 'mediaType': 'text/html', + 'content': content, + 'contentMap': { + postLanguage: content + }, + 'attachment': attachment, + 'tag': [], + 'replies': { + 'id': postJsonObject['id'] + '/replies', + 'type': 'Collection', + 'first': { + 'type': 'CollectionPage', + 'partOf': postJsonObject['id'] + '/replies', + 'items': [] + } + } + } + } + + return newPost diff --git a/webapp_post.py b/webapp_post.py index 26e8e020c..c7fd55690 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -1329,6 +1329,7 @@ def individualPostAsHtml(signingPrivateKeyPem: str, announceJsonObject = None if postJsonObject['type'] == 'Announce': announceJsonObject = postJsonObject.copy() + blockedCache = {} postJsonAnnounce = \ downloadAnnounce(session, baseDir, httpPrefix, nickname, domain, postJsonObject, @@ -1338,7 +1339,8 @@ def individualPostAsHtml(signingPrivateKeyPem: str, recentPostsCache, False, systemLanguage, domainFull, personCache, - signingPrivateKeyPem) + signingPrivateKeyPem, + blockedCache) if not postJsonAnnounce: # if the announce could not be downloaded then mark it as rejected rejectPostId(baseDir, nickname, domain, postJsonObject['id'],