From f51b7194f4154ab50f76c177d5b1ca1a4c07c79b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 17:05:50 +0100 Subject: [PATCH 01/17] Remove big dot diagram from tests --- tests.py | 56 -------------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/tests.py b/tests.py index c6ac118db..4e5621014 100644 --- a/tests.py +++ b/tests.py @@ -3384,62 +3384,6 @@ def _testFunctions(): _diagramGroups(['Core', 'Accessibility'], ['utils'], modules, modGroups, maxModuleCalls) - callGraphStr = 'digraph Epicyon {\n\n' - callGraphStr += ' size="8,6"; ratio=fill;\n' - callGraphStr += ' graph [fontsize=10 fontname="Verdana" compound=true];\n' - callGraphStr += ' node [shape=record fontsize=10 fontname="Verdana"];\n\n' - - for modName, modProperties in modules.items(): - callGraphStr += ' subgraph cluster_' + modName + ' {\n' - callGraphStr += ' label = "' + modName + '";\n' - callGraphStr += ' node [style=filled];\n' - moduleFunctionsStr = '' - for name in modProperties['functions']: - if name.startswith('test'): - continue - if name not in excludeFuncs: - if not functionProperties[name]['calls']: - moduleFunctionsStr += \ - ' "' + name + '" [fillcolor=yellow style=filled];\n' - continue - noOfCalls = len(functionProperties[name]['calls']) - if noOfCalls < int(maxFunctionCalls / 4): - moduleFunctionsStr += ' "' + name + \ - '" [fillcolor=orange style=filled];\n' - else: - moduleFunctionsStr += ' "' + name + \ - '" [fillcolor=red style=filled];\n' - - if moduleFunctionsStr: - callGraphStr += moduleFunctionsStr + '\n' - callGraphStr += ' color=blue;\n' - callGraphStr += ' }\n\n' - - for name, properties in functionProperties.items(): - if not properties['calls']: - continue - noOfCalls = len(properties['calls']) - if noOfCalls <= int(maxFunctionCalls / 8): - modColor = 'blue' - elif noOfCalls < int(maxFunctionCalls / 4): - modColor = 'green' - else: - modColor = 'red' - for calledFunc in properties['calls']: - if calledFunc.startswith('test'): - continue - if calledFunc not in excludeFuncs: - callGraphStr += ' "' + name + '" -> "' + calledFunc + \ - '" [color=' + modColor + '];\n' - - callGraphStr += '\n}\n' - with open('epicyon.dot', 'w+') as fp: - fp.write(callGraphStr) - print('Call graph saved to epicyon.dot') - print('Plot using: ' + - 'sfdp -x -Goverlap=prism -Goverlap_scaling=8 ' + - '-Gsep=+120 -Tx11 epicyon.dot') - def _testLinksWithinPost() -> None: baseDir = os.getcwd() From 0eb405ef5b864c4c0f8a0171d546facd2c4888c9 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 17:29:03 +0100 Subject: [PATCH 02/17] Unit test for word switching --- content.py | 50 +++++++++++++++++++++++++------------------------- tests.py | 25 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/content.py b/content.py index 6bf97dc80..960d3f033 100644 --- a/content.py +++ b/content.py @@ -202,35 +202,35 @@ def dangerousCSS(filename: str, allowLocalNetworkAccess: bool) -> bool: return False -def switchWords(baseDir: str, nickname: str, domain: str, content: str) -> str: +def switchWords(baseDir: str, nickname: str, domain: str, content: str, + rules: [] = []) -> str: """Performs word replacements. eg. Trump -> The Orange Menace """ if isPGPEncrypted(content) or containsPGPPublicKey(content): return content - switchWordsFilename = baseDir + '/accounts/' + \ - nickname + '@' + domain + '/replacewords.txt' - if not os.path.isfile(switchWordsFilename): - return content - with open(switchWordsFilename, 'r') as fp: - for line in fp: - replaceStr = line.replace('\n', '').replace('\r', '') - wordTransform = None - if '->' in replaceStr: - wordTransform = replaceStr.split('->') - elif ':' in replaceStr: - wordTransform = replaceStr.split(':') - elif ',' in replaceStr: - wordTransform = replaceStr.split(',') - elif ';' in replaceStr: - wordTransform = replaceStr.split(';') - elif '-' in replaceStr: - wordTransform = replaceStr.split('-') - if not wordTransform: - continue - if len(wordTransform) == 2: - replaceStr1 = wordTransform[0].strip().replace('"', '') - replaceStr2 = wordTransform[1].strip().replace('"', '') - content = content.replace(replaceStr1, replaceStr2) + + if not rules: + switchWordsFilename = baseDir + '/accounts/' + \ + nickname + '@' + domain + '/replacewords.txt' + if not os.path.isfile(switchWordsFilename): + return content + with open(switchWordsFilename, 'r') as fp: + rules = fp.readlines() + + for line in rules: + replaceStr = line.replace('\n', '').replace('\r', '') + splitters = ('->', ':', ',', ';', '-') + wordTransform = None + for splitStr in splitters: + if splitStr in replaceStr: + wordTransform = replaceStr.split(splitStr) + break + if not wordTransform: + continue + if len(wordTransform) == 2: + replaceStr1 = wordTransform[0].strip().replace('"', '') + replaceStr2 = wordTransform[1].strip().replace('"', '') + content = content.replace(replaceStr1, replaceStr2) return content diff --git a/tests.py b/tests.py index 4e5621014..9fe956d83 100644 --- a/tests.py +++ b/tests.py @@ -94,6 +94,7 @@ from inbox import jsonPostAllowsComments from inbox import validInbox from inbox import validInboxFilenames from categories import guessHashtagCategory +from content import switchWords from content import extractTextFieldsInPOST from content import validHashTag from content import htmlReplaceEmailQuote @@ -4117,9 +4118,33 @@ def _testUserAgentDomain() -> None: assert userAgentDomain(userAgent, False) is None +def _testSwitchWords() -> None: + print('testSwitchWords') + rules = [ + "rock -> hamster", + "orange -> lemon" + ] + baseDir = os.getcwd() + nickname = 'testuser' + domain = 'testdomain.com' + + content = 'This is a test' + result = switchWords(baseDir, nickname, domain, content, rules) + assert result == content + + content = 'This is orange test' + result = switchWords(baseDir, nickname, domain, content, rules) + assert result == 'This is lemon test' + + content = 'This is a test rock' + result = switchWords(baseDir, nickname, domain, content, rules) + assert result == 'This is a test hamster' + + def runAllTests(): print('Running tests...') updateDefaultThemesList(os.getcwd()) + _testSwitchWords() _testFunctions() _testUserAgentDomain() _testRoles() From 71c2794a19ea3d75bcfe9928611d21cfc064de36 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 21:38:08 +0100 Subject: [PATCH 03/17] Optional notifications for the arrival of posts from low frequency follows --- daemon.py | 30 ++++++++++ inbox.py | 23 ++++++++ notifyOnPost.py | 104 +++++++++++++++++++++++++++++++++++ scripts/epicyon-notification | 15 +++++ translations/ar.json | 3 +- translations/ca.json | 3 +- translations/cy.json | 3 +- translations/de.json | 3 +- translations/en.json | 3 +- translations/es.json | 3 +- translations/fr.json | 3 +- translations/ga.json | 3 +- translations/hi.json | 3 +- translations/it.json | 3 +- translations/ja.json | 3 +- translations/ku.json | 3 +- translations/oc.json | 3 +- translations/pt.json | 3 +- translations/ru.json | 3 +- translations/sw.json | 3 +- translations/zh.json | 3 +- webapp_person_options.py | 16 +++++- 22 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 notifyOnPost.py diff --git a/daemon.py b/daemon.py index aa4b55576..94d16939b 100644 --- a/daemon.py +++ b/daemon.py @@ -286,6 +286,8 @@ from bookmarks import undoBookmark from petnames import setPetName from followingCalendar import addPersonToCalendar from followingCalendar import removePersonFromCalendar +from notifyOnPost import addNotifyOnPost +from notifyOnPost import removeNotifyOnPost from devices import E2EEdevicesCollection from devices import E2EEvalidDevice from devices import E2EEaddDevice @@ -2087,6 +2089,34 @@ class PubServer(BaseHTTPRequestHandler): self.server.POSTbusy = False return + # person options screen, on notify checkbox + # See htmlPersonOptions + if '&submitNotifyOnPost=' in optionsConfirmParams: + notify = None + if 'notifyOnPost=' in optionsConfirmParams: + notify = optionsConfirmParams.split('notifyOnPost=')[1] + if '&' in notify: + notify = notify.split('&')[0] + if notify == 'on': + addNotifyOnPost(baseDir, + chooserNickname, + domain, + optionsNickname, + optionsDomainFull) + else: + removeNotifyOnPost(baseDir, + chooserNickname, + domain, + optionsNickname, + optionsDomainFull) + usersPathStr = \ + usersPath + '/' + self.server.defaultTimeline + \ + '?page=' + str(pageNumber) + self._redirect_headers(usersPathStr, cookie, + callingDomain) + self.server.POSTbusy = False + return + # person options screen, permission to post to newswire # See htmlPersonOptions if '&submitPostToNews=' in optionsConfirmParams: diff --git a/inbox.py b/inbox.py index 8fbaccf50..c972a9288 100644 --- a/inbox.py +++ b/inbox.py @@ -86,6 +86,7 @@ from categories import guessHashtagCategory from context import hasValidContext from speaker import updateSpeaker from announce import isSelfAnnounce +from notifyOnPost import notifyWhenPersonPosts def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None: @@ -1850,6 +1851,18 @@ def _likeNotify(baseDir: str, domain: str, onionDomain: str, pass +def _notifyPostArrival(baseDir: str, handle: str, url: str) -> None: + """Creates a notification that a new post has arrived + """ + accountDir = baseDir + '/accounts/' + handle + if not os.path.isdir(accountDir): + return + notifyFile = accountDir + '/.newNotifiedPost' + if not os.path.isfile(notifyFile): + with open(notifyFile, 'w+') as fp: + fp.write(url) + + def _replyNotify(baseDir: str, handle: str, url: str) -> None: """Creates a notification that a new reply has arrived """ @@ -2275,6 +2288,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, _updateLastSeen(baseDir, handle, actor) + postIsDM = False isGroup = _groupHandle(baseDir, handle) if _receiveLike(recentPostsCache, @@ -2512,6 +2526,15 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # save the post to file if saveJson(postJsonObject, destinationFilename): + if not postIsDM: + # should we notify that a post from this person has arrived? + handleNickname = handle.split('@')[0] + handleDomain = handle.split('@')[1] + if notifyWhenPersonPosts(baseDir, nickname, domain, + handleNickname, handleDomain): + postId = removeIdEnding(postJsonObject['id']) + _notifyPostArrival(baseDir, handle, postId) + # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring if isReplyToMutedPost: diff --git a/notifyOnPost.py b/notifyOnPost.py new file mode 100644 index 000000000..20bf927c2 --- /dev/null +++ b/notifyOnPost.py @@ -0,0 +1,104 @@ +__filename__ = "notifyOnPost.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.2.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" +__module_group__ = "Calendar" + +import os +from utils import removeDomainPort + + +def _notifyOnPostArrival(baseDir: str, nickname: str, domain: str, + followingNickname: str, + followingDomain: str, + add: bool) -> None: + """Adds or removes a handle from the following.txt list into a list + indicating whether to notify when a new post arrives from that account + """ + # check that a following file exists + domain = removeDomainPort(domain) + followingFilename = baseDir + '/accounts/' + \ + nickname + '@' + domain + '/following.txt' + if not os.path.isfile(followingFilename): + print("WARN: following.txt doesn't exist for " + + nickname + '@' + domain) + return + handle = followingNickname + '@' + followingDomain + + # check that you are following this handle + if handle + '\n' not in open(followingFilename).read(): + print('WARN: ' + handle + ' is not in ' + followingFilename) + return + + notifyOnPostFilename = baseDir + '/accounts/' + \ + nickname + '@' + domain + '/notifyOnPost.txt' + + # get the contents of the notifyOnPost file, which is + # a set of handles + followingHandles = '' + if os.path.isfile(notifyOnPostFilename): + print('notify file exists') + with open(notifyOnPostFilename, 'r') as calendarFile: + followingHandles = calendarFile.read() + else: + # create a new notifyOnPost file from the following file + print('Creating notifyOnPost file ' + notifyOnPostFilename) + followingHandles = '' + with open(followingFilename, 'r') as followingFile: + followingHandles = followingFile.read() + if add: + with open(notifyOnPostFilename, 'w+') as fp: + fp.write(followingHandles + handle + '\n') + + # already in the notifyOnPost file? + if handle + '\n' in followingHandles: + print(handle + ' exists in notifyOnPost.txt') + if add: + # already added + return + # remove from calendar file + followingHandles = followingHandles.replace(handle + '\n', '') + with open(notifyOnPostFilename, 'w+') as fp: + fp.write(followingHandles) + else: + print(handle + ' not in notifyOnPost.txt') + # not already in the notifyOnPost file + if add: + # append to the list of handles + followingHandles += handle + '\n' + with open(notifyOnPostFilename, 'w+') as fp: + fp.write(followingHandles) + + +def addNotifyOnPost(baseDir: str, nickname: str, domain: str, + followingNickname: str, + followingDomain: str) -> None: + _notifyOnPostArrival(baseDir, nickname, domain, + followingNickname, followingDomain, True) + + +def removeNotifyOnPost(baseDir: str, nickname: str, domain: str, + followingNickname: str, + followingDomain: str) -> None: + _notifyOnPostArrival(baseDir, nickname, domain, + followingNickname, followingDomain, False) + + +def notifyWhenPersonPosts(baseDir: str, nickname: str, domain: str, + followingNickname: str, + followingDomain: str) -> bool: + """Returns true if receiving notifications when the given publishes a post + """ + if followingNickname == nickname and followingDomain == domain: + return False + notifyOnPostFilename = baseDir + '/accounts/' + \ + nickname + '@' + domain + '/notifyOnPost.txt' + handle = followingNickname + '@' + followingDomain + if not os.path.isfile(notifyOnPostFilename): + # create a new notifyOnPost file + with open(notifyOnPostFilename, 'w+') as fp: + fp.write('') + return handle + '\n' in open(notifyOnPostFilename).read() diff --git a/scripts/epicyon-notification b/scripts/epicyon-notification index 5b4f4ec89..d8e0e54c6 100755 --- a/scripts/epicyon-notification +++ b/scripts/epicyon-notification @@ -215,6 +215,21 @@ function notifications { fi fi + # send notifications for posts arriving from a particular person + epicyonNotifyFile="$epicyonDir/.newNotifiedPost" + if [ -f "$epicyonNotifyFile" ]; then + if ! grep -q "##sent##" "$epicyonNotifyFile"; then + epicyonReplyMessage=$(notification_translate_text 'New post') + epicyonNotifyFileContent=$(echo "$epicyonReplyMessage")" "$(cat "$epicyonNotifyFile") + if [[ "$epicyonNotifyFileContent" == *':'* ]]; then + epicyonReplyMessage="Epicyon: $epicyonNotifyFileContent" + fi + sendNotification "$USERNAME" "Epicyon" "$epicyonReplyMessage" + echo "##sent##" > "$epicyonNotifyFile" + chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonNotifyFile" + fi + fi + # send notifications for replies to XMPP/email users epicyonReplyFile="$epicyonDir/.newReply" if [ -f "$epicyonReplyFile" ]; then diff --git a/translations/ar.json b/translations/ar.json index 270d3b503..50cca5a13 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -449,5 +449,6 @@ "Import Theme": "استيراد الموضوع", "Export Theme": "موضوع التصدير", "Custom post submit button text": "عرف نشر إرسال نص زر", - "Blocked User Agents": "عوامل المستخدم المحظورة" + "Blocked User Agents": "عوامل المستخدم المحظورة", + "Notify me when this account posts": "أعلمني عندما ينشر الحساب هذا" } diff --git a/translations/ca.json b/translations/ca.json index b445702f3..c9a15be88 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -449,5 +449,6 @@ "Import Theme": "Importació temàtica", "Export Theme": "Tema d'exportació", "Custom post submit button text": "Text de botó d'enviament de publicacions personalitzades", - "Blocked User Agents": "Agents d'usuari bloquejats" + "Blocked User Agents": "Agents d'usuari bloquejats", + "Notify me when this account posts": "Aviseu-me quan publiqui aquest compte" } diff --git a/translations/cy.json b/translations/cy.json index 680fa4c72..0cf0e9980 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -449,5 +449,6 @@ "Import Theme": "Thema Mewnforio", "Export Theme": "Thema Allforio", "Custom post submit button text": "Testun Post Post Post", - "Blocked User Agents": "Asiantau defnyddwyr wedi'u blocio" + "Blocked User Agents": "Asiantau defnyddwyr wedi'u blocio", + "Notify me when this account posts": "Rhoi gwybod i mi pan fydd y cyfrifon cyfrif hwn" } diff --git a/translations/de.json b/translations/de.json index 2ec7c24b6..df10a2e6e 100644 --- a/translations/de.json +++ b/translations/de.json @@ -449,5 +449,6 @@ "Import Theme": "Theme importieren", "Export Theme": "Theme exportieren", "Custom post submit button text": "Benutzerdefinierte Post-Senden Schaltfläche Text", - "Blocked User Agents": "Blockierte Benutzeragenten" + "Blocked User Agents": "Blockierte Benutzeragenten", + "Notify me when this account posts": "Benachrichtigen Sie mich, wenn dieses Konto postet" } diff --git a/translations/en.json b/translations/en.json index 25dd4a83a..31d1bbf40 100644 --- a/translations/en.json +++ b/translations/en.json @@ -449,5 +449,6 @@ "Import Theme": "Import Theme", "Export Theme": "Export Theme", "Custom post submit button text": "Custom post submit button text", - "Blocked User Agents": "Blocked User Agents" + "Blocked User Agents": "Blocked User Agents", + "Notify me when this account posts": "Notify me when this account posts" } diff --git a/translations/es.json b/translations/es.json index 1502da0e3..ac096a01d 100644 --- a/translations/es.json +++ b/translations/es.json @@ -449,5 +449,6 @@ "Import Theme": "Tema de importación", "Export Theme": "Tema de exportación", "Custom post submit button text": "POST POST PERSONALIZADO Botón Texto", - "Blocked User Agents": "Agentes de usuario bloqueados" + "Blocked User Agents": "Agentes de usuario bloqueados", + "Notify me when this account posts": "Notifíqueme cuando se publique esta cuenta" } diff --git a/translations/fr.json b/translations/fr.json index 57d4d369a..6defd9956 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -449,5 +449,6 @@ "Import Theme": "Import thème", "Export Theme": "Thème d'exportation", "Custom post submit button text": "Texte de bouton d'envoi postal personnalisé", - "Blocked User Agents": "Agents d'utilisateur bloqués" + "Blocked User Agents": "Agents d'utilisateur bloqués", + "Notify me when this account posts": "Avertissez-moi quand ce compte publie" } diff --git a/translations/ga.json b/translations/ga.json index ea04687fe..632b57ab0 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -449,5 +449,6 @@ "Import Theme": "Téama Iompórtáil", "Export Theme": "Téama Easpórtála", "Custom post submit button text": "Post saincheaptha Cuir isteach an cnaipe Téacs", - "Blocked User Agents": "Gníomhairí úsáideora blocáilte" + "Blocked User Agents": "Gníomhairí úsáideora blocáilte", + "Notify me when this account posts": "Cuir in iúl dom nuair a phostófar an cuntas seo" } diff --git a/translations/hi.json b/translations/hi.json index 00ea6df4d..2f3e13f0e 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -449,5 +449,6 @@ "Import Theme": "आयात विषय", "Export Theme": "निर्यात विषय", "Custom post submit button text": "कस्टम पोस्ट सबमिट बटन टेक्स्ट", - "Blocked User Agents": "अवरुद्ध उपयोगकर्ता एजेंट" + "Blocked User Agents": "अवरुद्ध उपयोगकर्ता एजेंट", + "Notify me when this account posts": "यह खाता पोस्ट होने पर मुझे सूचित करें" } diff --git a/translations/it.json b/translations/it.json index 12994e2ea..8406ccbda 100644 --- a/translations/it.json +++ b/translations/it.json @@ -449,5 +449,6 @@ "Import Theme": "Tema dell'importazione", "Export Theme": "Esportare tema", "Custom post submit button text": "Pulsante di invio del post personalizzato", - "Blocked User Agents": "Agenti utente bloccati" + "Blocked User Agents": "Agenti utente bloccati", + "Notify me when this account posts": "Avvisami quando questo account messaggi" } diff --git a/translations/ja.json b/translations/ja.json index fdb96a967..494661a86 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -449,5 +449,6 @@ "Import Theme": "輸入テーマ", "Export Theme": "テーマをエクスポートします", "Custom post submit button text": "カスタムポスト送信ボタンテキスト", - "Blocked User Agents": "ブロックされたユーザーエージェント" + "Blocked User Agents": "ブロックされたユーザーエージェント", + "Notify me when this account posts": "この口座投稿を通知する" } diff --git a/translations/ku.json b/translations/ku.json index cf71e2f19..8d99ddd72 100644 --- a/translations/ku.json +++ b/translations/ku.json @@ -449,5 +449,6 @@ "Import Theme": "Mijara Import", "Export Theme": "Mijara Export", "Custom post submit button text": "Nivîsa bişkojka paşîn a paşîn", - "Blocked User Agents": "Karmendên bikarhêner asteng kirin" + "Blocked User Agents": "Karmendên bikarhêner asteng kirin", + "Notify me when this account posts": "Dema ku ev postên hesabê min agahdar bikin" } diff --git a/translations/oc.json b/translations/oc.json index 7c2df85d6..411216f8a 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -445,5 +445,6 @@ "Import Theme": "Import Theme", "Export Theme": "Export Theme", "Custom post submit button text": "Custom post submit button text", - "Blocked User Agents": "Blocked User Agents" + "Blocked User Agents": "Blocked User Agents", + "Notify me when this account posts": "Notify me when this account posts" } diff --git a/translations/pt.json b/translations/pt.json index d7d303d1f..8d05812de 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -449,5 +449,6 @@ "Import Theme": "Importar tema", "Export Theme": "Exportar tema", "Custom post submit button text": "Texto de botão de envio de post personalizado", - "Blocked User Agents": "Agentes de usuário bloqueados" + "Blocked User Agents": "Agentes de usuário bloqueados", + "Notify me when this account posts": "Notifique-me quando esta conta posts" } diff --git a/translations/ru.json b/translations/ru.json index cfee8a392..4663cef78 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -449,5 +449,6 @@ "Import Theme": "Импортировать тему", "Export Theme": "Экспортная тема", "Custom post submit button text": "Пользовательский пост Отправить кнопку текста", - "Blocked User Agents": "Заблокированные пользовательские агенты" + "Blocked User Agents": "Заблокированные пользовательские агенты", + "Notify me when this account posts": "Сообщите мне, когда эта учетная запись" } diff --git a/translations/sw.json b/translations/sw.json index 0ac58b643..4f2fa1bb7 100644 --- a/translations/sw.json +++ b/translations/sw.json @@ -449,5 +449,6 @@ "Import Theme": "Ingiza mandhari", "Export Theme": "Tuma mandhari", "Custom post submit button text": "Ujumbe wa Desturi Wasilisha Nakala ya kifungo", - "Blocked User Agents": "Wakala wa watumiaji waliozuiwa" + "Blocked User Agents": "Wakala wa watumiaji waliozuiwa", + "Notify me when this account posts": "Nijulishe wakati akaunti hii ya akaunti." } diff --git a/translations/zh.json b/translations/zh.json index 07b9e10c1..eb143dc04 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -449,5 +449,6 @@ "Import Theme": "进口主题", "Export Theme": "出口主题", "Custom post submit button text": "自定义发布提交按钮文本", - "Blocked User Agents": "阻止用户代理商" + "Blocked User Agents": "阻止用户代理商", + "Notify me when this account posts": "此帐户帖子时通知我" } diff --git a/webapp_person_options.py b/webapp_person_options.py index 3c3309196..cebdba87d 100644 --- a/webapp_person_options.py +++ b/webapp_person_options.py @@ -23,6 +23,7 @@ from blocking import isBlocked from follow import isFollowerOfPerson from follow import isFollowingActor from followingCalendar import receivingCalendarEvents +from notifyOnPost import notifyWhenPersonPosts from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter from webapp_utils import getBrokenLinkSubstitute @@ -246,8 +247,21 @@ def htmlPersonOptions(defaultTimeline: str, 'name="submitPetname">' + \ translate['Submit'] + '
\n' - # checkbox for receiving calendar events + # Notify when a post arrives from this person if isFollowingActor(baseDir, nickname, domain, optionsActor): + checkboxStr = \ + ' 🔔' + \ + translate['Notify me when this account posts'] + \ + '\n
\n' + if not notifyWhenPersonPosts(baseDir, nickname, domain, + optionsNickname, + optionsDomainFull): + checkboxStr = checkboxStr.replace(' checked>', '>') + optionsStr += checkboxStr + checkboxStr = \ ' ' + \ From 6a73192c2490237f16e66cbad30d118fc567914b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 22:53:55 +0100 Subject: [PATCH 04/17] Tidying --- inbox.py | 2 +- notifyOnPost.py | 4 ++-- scripts/epicyon-notification | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/inbox.py b/inbox.py index c972a9288..0bf108c52 100644 --- a/inbox.py +++ b/inbox.py @@ -2526,8 +2526,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # save the post to file if saveJson(postJsonObject, destinationFilename): + # should we notify that a post from this person has arrived? if not postIsDM: - # should we notify that a post from this person has arrived? handleNickname = handle.split('@')[0] handleDomain = handle.split('@')[1] if notifyWhenPersonPosts(baseDir, nickname, domain, diff --git a/notifyOnPost.py b/notifyOnPost.py index 20bf927c2..2c40e7d2e 100644 --- a/notifyOnPost.py +++ b/notifyOnPost.py @@ -94,8 +94,8 @@ def notifyWhenPersonPosts(baseDir: str, nickname: str, domain: str, """ if followingNickname == nickname and followingDomain == domain: return False - notifyOnPostFilename = baseDir + '/accounts/' + \ - nickname + '@' + domain + '/notifyOnPost.txt' + notifyOnPostFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/notifyOnPost.txt' handle = followingNickname + '@' + followingDomain if not os.path.isfile(notifyOnPostFilename): # create a new notifyOnPost file diff --git a/scripts/epicyon-notification b/scripts/epicyon-notification index d8e0e54c6..d2f2c6301 100755 --- a/scripts/epicyon-notification +++ b/scripts/epicyon-notification @@ -219,12 +219,12 @@ function notifications { epicyonNotifyFile="$epicyonDir/.newNotifiedPost" if [ -f "$epicyonNotifyFile" ]; then if ! grep -q "##sent##" "$epicyonNotifyFile"; then - epicyonReplyMessage=$(notification_translate_text 'New post') - epicyonNotifyFileContent=$(echo "$epicyonReplyMessage")" "$(cat "$epicyonNotifyFile") + epicyonNotifyMessage=$(notification_translate_text 'New post') + epicyonNotifyFileContent=$(echo "$epicyonNotifyMessage")" "$(cat "$epicyonNotifyFile") if [[ "$epicyonNotifyFileContent" == *':'* ]]; then - epicyonReplyMessage="Epicyon: $epicyonNotifyFileContent" + epicyonNotifyMessage="Epicyon: $epicyonNotifyFileContent" fi - sendNotification "$USERNAME" "Epicyon" "$epicyonReplyMessage" + sendNotification "$USERNAME" "Epicyon" "$epicyonNotifyMessage" echo "##sent##" > "$epicyonNotifyFile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonNotifyFile" fi From a68ea9b2a4b8f1a246b776655a0ccec7280c17d1 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 23:10:26 +0100 Subject: [PATCH 05/17] Notifications handles --- inbox.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/inbox.py b/inbox.py index 0bf108c52..5f5d0efe9 100644 --- a/inbox.py +++ b/inbox.py @@ -2487,8 +2487,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # get the actor being replied to domainFull = getFullDomain(domain, port) - actor = httpPrefix + '://' + domainFull + \ - '/users/' + handle.split('@')[0] + actor = httpPrefix + '://' + domainFull + '/users/' + nickname # create a reply notification file if needed if not postIsDM and isReply(postJsonObject, actor): @@ -2528,12 +2527,14 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, if saveJson(postJsonObject, destinationFilename): # should we notify that a post from this person has arrived? if not postIsDM: - handleNickname = handle.split('@')[0] - handleDomain = handle.split('@')[1] + fromNickname = getNicknameFromActor(actor) + fromDomain, fromPort = getDomainFromActor(actor) + fromDomainFull = getFullDomain(fromDomain, fromPort) + fromHandle = fromNickname + '@' + fromDomainFull if notifyWhenPersonPosts(baseDir, nickname, domain, - handleNickname, handleDomain): + fromNickname, fromDomainFull): postId = removeIdEnding(postJsonObject['id']) - _notifyPostArrival(baseDir, handle, postId) + _notifyPostArrival(baseDir, fromHandle, postId) # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring From 523399881c07993846d87ae8674be9945a6648d6 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Tue, 6 Jul 2021 23:43:39 +0100 Subject: [PATCH 06/17] Actor for notifications --- inbox.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inbox.py b/inbox.py index 5f5d0efe9..cc61a70f3 100644 --- a/inbox.py +++ b/inbox.py @@ -2527,8 +2527,9 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, if saveJson(postJsonObject, destinationFilename): # should we notify that a post from this person has arrived? if not postIsDM: - fromNickname = getNicknameFromActor(actor) - fromDomain, fromPort = getDomainFromActor(actor) + fromNickname = getNicknameFromActor(postJsonObject['actor']) + fromDomain, fromPort = \ + getDomainFromActor(postJsonObject['actor']) fromDomainFull = getFullDomain(fromDomain, fromPort) fromHandle = fromNickname + '@' + fromDomainFull if notifyWhenPersonPosts(baseDir, nickname, domain, From 5bc7f28685d5d3d2d4a7bef08b5b0508c6ac3da3 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:06:59 +0100 Subject: [PATCH 07/17] Notifications about posts from particular accounts --- inbox.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/inbox.py b/inbox.py index cc61a70f3..8d9a03edd 100644 --- a/inbox.py +++ b/inbox.py @@ -2531,11 +2531,10 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, fromDomain, fromPort = \ getDomainFromActor(postJsonObject['actor']) fromDomainFull = getFullDomain(fromDomain, fromPort) - fromHandle = fromNickname + '@' + fromDomainFull if notifyWhenPersonPosts(baseDir, nickname, domain, fromNickname, fromDomainFull): postId = removeIdEnding(postJsonObject['id']) - _notifyPostArrival(baseDir, fromHandle, postId) + _notifyPostArrival(baseDir, handle, postId) # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring From c4175666755a4481160c6347293cbdc7ded4be56 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:30:15 +0100 Subject: [PATCH 08/17] Check that the same notification is not repeatedly sent --- inbox.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inbox.py b/inbox.py index 8d9a03edd..6d4de0261 100644 --- a/inbox.py +++ b/inbox.py @@ -1858,9 +1858,14 @@ def _notifyPostArrival(baseDir: str, handle: str, url: str) -> None: if not os.path.isdir(accountDir): return notifyFile = accountDir + '/.newNotifiedPost' - if not os.path.isfile(notifyFile): - with open(notifyFile, 'w+') as fp: - fp.write(url) + if os.path.isfile(notifyFile): + # check that the same notification is not repeatedly sent + with open(notifyFile, 'r') as fp: + existingNotificationMessage = fp.read() + if url in existingNotificationMessage: + return + with open(notifyFile, 'w+') as fp: + fp.write(url) def _replyNotify(baseDir: str, handle: str, url: str) -> None: From 804c045a7527f202ace320ef71e431d64af0dfac Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:32:48 +0100 Subject: [PATCH 09/17] Comments --- inbox.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inbox.py b/inbox.py index 6d4de0261..cd64f391c 100644 --- a/inbox.py +++ b/inbox.py @@ -1852,7 +1852,9 @@ def _likeNotify(baseDir: str, domain: str, onionDomain: str, def _notifyPostArrival(baseDir: str, handle: str, url: str) -> None: - """Creates a notification that a new post has arrived + """Creates a notification that a new post has arrived. + This is for followed accounts with the notify checkbox enabled + on the person options screen """ accountDir = baseDir + '/accounts/' + handle if not os.path.isdir(accountDir): From 21a9e06c267d0dfb8120c7b3ba033dde00adb59b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:36:53 +0100 Subject: [PATCH 10/17] Notifications should only be for posts written by the selected account --- inbox.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/inbox.py b/inbox.py index cd64f391c..8db1ee4bf 100644 --- a/inbox.py +++ b/inbox.py @@ -2533,15 +2533,18 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # save the post to file if saveJson(postJsonObject, destinationFilename): # should we notify that a post from this person has arrived? - if not postIsDM: - fromNickname = getNicknameFromActor(postJsonObject['actor']) - fromDomain, fromPort = \ - getDomainFromActor(postJsonObject['actor']) - fromDomainFull = getFullDomain(fromDomain, fromPort) - if notifyWhenPersonPosts(baseDir, nickname, domain, - fromNickname, fromDomainFull): - postId = removeIdEnding(postJsonObject['id']) - _notifyPostArrival(baseDir, handle, postId) + # This is for cases where the notify checkbox is enabled + # on the person options screen + if not postIsDM and postJsonObject.get('attributedTo'): + attributedTo = postJsonObject['attributedTo'] + if isinstance(attributedTo, str): + fromNickname = getNicknameFromActor(attributedTo) + fromDomain, fromPort = getDomainFromActor(attributedTo) + fromDomainFull = getFullDomain(fromDomain, fromPort) + if notifyWhenPersonPosts(baseDir, nickname, domain, + fromNickname, fromDomainFull): + postId = removeIdEnding(postJsonObject['id']) + _notifyPostArrival(baseDir, handle, postId) # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring From 7b6c939b92e5ea2f45aafb40b744c114fff5eaf0 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:49:46 +0100 Subject: [PATCH 11/17] Attributed within object --- inbox.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/inbox.py b/inbox.py index 8db1ee4bf..2a4600cc1 100644 --- a/inbox.py +++ b/inbox.py @@ -2535,16 +2535,17 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # should we notify that a post from this person has arrived? # This is for cases where the notify checkbox is enabled # on the person options screen - if not postIsDM and postJsonObject.get('attributedTo'): - attributedTo = postJsonObject['attributedTo'] - if isinstance(attributedTo, str): - fromNickname = getNicknameFromActor(attributedTo) - fromDomain, fromPort = getDomainFromActor(attributedTo) - fromDomainFull = getFullDomain(fromDomain, fromPort) - if notifyWhenPersonPosts(baseDir, nickname, domain, - fromNickname, fromDomainFull): - postId = removeIdEnding(postJsonObject['id']) - _notifyPostArrival(baseDir, handle, postId) + if not postIsDM and hasObjectDict(postJsonObject): + if postJsonObject['object'].get('attributedTo'): + attributedTo = postJsonObject['object']['attributedTo'] + if isinstance(attributedTo, str): + fromNickname = getNicknameFromActor(attributedTo) + fromDomain, fromPort = getDomainFromActor(attributedTo) + fromDomainFull = getFullDomain(fromDomain, fromPort) + if notifyWhenPersonPosts(baseDir, nickname, domain, + fromNickname, fromDomainFull): + postId = removeIdEnding(postJsonObject['id']) + _notifyPostArrival(baseDir, handle, postId) # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring From f294fbd356e9943c4dde7f8130ea9bb9bac9d41f Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 10:52:05 +0100 Subject: [PATCH 12/17] Tidying --- inbox.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/inbox.py b/inbox.py index 2a4600cc1..8203f1f1e 100644 --- a/inbox.py +++ b/inbox.py @@ -2413,6 +2413,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, postJsonObject = messageJson nickname = handle.split('@')[0] + jsonObj = None if _validPostContent(baseDir, nickname, domain, postJsonObject, maxMentions, maxEmoji, allowLocalNetworkAccess, debug): @@ -2535,16 +2536,16 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, # should we notify that a post from this person has arrived? # This is for cases where the notify checkbox is enabled # on the person options screen - if not postIsDM and hasObjectDict(postJsonObject): - if postJsonObject['object'].get('attributedTo'): - attributedTo = postJsonObject['object']['attributedTo'] + if not postIsDM and jsonObj: + if jsonObj.get('attributedTo') and jsonObj.get('id'): + attributedTo = jsonObj['attributedTo'] if isinstance(attributedTo, str): fromNickname = getNicknameFromActor(attributedTo) fromDomain, fromPort = getDomainFromActor(attributedTo) fromDomainFull = getFullDomain(fromDomain, fromPort) if notifyWhenPersonPosts(baseDir, nickname, domain, fromNickname, fromDomainFull): - postId = removeIdEnding(postJsonObject['id']) + postId = removeIdEnding(jsonObj['id']) _notifyPostArrival(baseDir, handle, postId) # If this is a reply to a muted post then also mute it. From e65ad3bcc203c72ea35901b17c183e527504c9d6 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 12:03:16 +0100 Subject: [PATCH 13/17] Tidying --- daemon.py | 167 ++++++++++++++++++++++-------------------------------- 1 file changed, 69 insertions(+), 98 deletions(-) diff --git a/daemon.py b/daemon.py index 94d16939b..18600857b 100644 --- a/daemon.py +++ b/daemon.py @@ -7832,108 +7832,79 @@ class PubServer(BaseHTTPRequestHandler): statusNumber = postSections[2] if len(statusNumber) <= 10 or (not statusNumber.isdigit()): return False - postFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + \ - domain + '/outbox/' + \ - httpPrefix + ':##' + \ - domainFull + '#users#' + \ - nickname + '#statuses#' + \ - statusNumber + '.json' - if os.path.isfile(postFilename): - postJsonObject = loadJson(postFilename) - if not postJsonObject: - self.send_response(429) - self.end_headers() - self.server.GETbusy = False - return True - else: - # Only authorized viewers get to see likes - # on posts - # Otherwize marketers could gain more social - # graph info - if not authorized: - pjo = postJsonObject - if not isPublicPost(pjo): - self._404() - self.server.GETbusy = False - return True - removePostInteractions(pjo, True) - if self._requestHTTP(): - recentPostsCache = \ - self.server.recentPostsCache - maxRecentPosts = \ - self.server.maxRecentPosts - translate = \ - self.server.translate - cachedWebfingers = \ - self.server.cachedWebfingers - personCache = \ - self.server.personCache - projectVersion = \ - self.server.projectVersion - ytDomain = \ - self.server.YTReplacementDomain - showPublishedDateOnly = \ - self.server.showPublishedDateOnly - peertubeInstances = \ - self.server.peertubeInstances - allowLocalNetworkAccess = \ - self.server.allowLocalNetworkAccess - themeName = \ - self.server.themeName - msg = \ - htmlIndividualPost(self.server.cssCache, - recentPostsCache, - maxRecentPosts, - translate, - baseDir, - self.server.session, - cachedWebfingers, - personCache, - nickname, - domain, - port, - authorized, - postJsonObject, - httpPrefix, - projectVersion, - likedBy, - ytDomain, - showPublishedDateOnly, - peertubeInstances, - allowLocalNetworkAccess, - themeName) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('text/html', msglen, - cookie, callingDomain) - self._write(msg) - self._benchmarkGETtimings(GETstartTime, - GETtimings, - 'show skills ' + - 'done', - 'show status') - else: - if self._fetchAuthenticated(): - msg = json.dumps(postJsonObject, - ensure_ascii=False) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('application/json', - msglen, - None, callingDomain) - self._write(msg) - else: - self._404() - self.server.GETbusy = False - return True - else: + postFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \ + httpPrefix + ':##' + domainFull + '#users#' + nickname + \ + '#statuses#' + statusNumber + '.json' + if not os.path.isfile(postFilename): self._404() self.server.GETbusy = False return True - return False + + postJsonObject = loadJson(postFilename) + if not postJsonObject: + self.send_response(429) + self.end_headers() + self.server.GETbusy = False + return True + + # Only authorized viewers get to see likes + # on posts + # Otherwize marketers could gain more social + # graph info + if not authorized: + pjo = postJsonObject + if not isPublicPost(pjo): + self._404() + self.server.GETbusy = False + return True + removePostInteractions(pjo, True) + if self._requestHTTP(): + msg = \ + htmlIndividualPost(self.server.cssCache, + self.server.recentPostsCache, + self.server.maxRecentPosts, + self.server.translate, + baseDir, + self.server.session, + self.server.cachedWebfingers, + self.server.personCache, + nickname, domain, port, + authorized, + postJsonObject, + httpPrefix, + self.server.projectVersion, + likedBy, + self.server.YTReplacementDomain, + self.server.showPublishedDateOnly, + self.server.peertubeInstances, + self.server.allowLocalNetworkAccess, + self.server.themeName) + msg = msg.encode('utf-8') + msglen = len(msg) + self._set_headers('text/html', msglen, + cookie, callingDomain) + self._write(msg) + self._benchmarkGETtimings(GETstartTime, + GETtimings, + 'show skills ' + + 'done', + 'show status') + else: + if self._fetchAuthenticated(): + msg = json.dumps(postJsonObject, + ensure_ascii=False) + msg = msg.encode('utf-8') + msglen = len(msg) + self._set_headers('application/json', + msglen, + None, callingDomain) + self._write(msg) + else: + self._404() + self.server.GETbusy = False + return True def _showInbox(self, authorized: bool, callingDomain: str, path: str, From f6355632ec5752a8b7151eb7d4628983029ff2c2 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 12:31:43 +0100 Subject: [PATCH 14/17] Less indentation --- daemon.py | 182 +++++++++++++++++++++++------------------------------- 1 file changed, 78 insertions(+), 104 deletions(-) diff --git a/daemon.py b/daemon.py index 18600857b..4f21b00f4 100644 --- a/daemon.py +++ b/daemon.py @@ -7700,111 +7700,85 @@ class PubServer(BaseHTTPRequestHandler): if '/' not in namedStatus: # show actor nickname = namedStatus + return False + + postSections = namedStatus.split('/') + if len(postSections) != 2: + return False + nickname = postSections[0] + statusNumber = postSections[1] + if len(statusNumber) <= 10 or not statusNumber.isdigit(): + return False + + postFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \ + httpPrefix + ':##' + domainFull + '#users#' + nickname + \ + '#statuses#' + statusNumber + '.json' + if not os.path.isfile(postFilename): + self._404() + self.server.GETbusy = False + return True + + postJsonObject = loadJson(postFilename) + loadedPost = False + if postJsonObject: + loadedPost = True else: - postSections = namedStatus.split('/') - if len(postSections) == 2: - nickname = postSections[0] - statusNumber = postSections[1] - if len(statusNumber) > 10 and statusNumber.isdigit(): - postFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + \ - domain + '/outbox/' + \ - httpPrefix + ':##' + \ - domainFull + '#users#' + \ - nickname + '#statuses#' + \ - statusNumber + '.json' - if os.path.isfile(postFilename): - postJsonObject = loadJson(postFilename) - loadedPost = False - if postJsonObject: - loadedPost = True - else: - postJsonObject = {} - if loadedPost: - # Only authorized viewers get to see likes - # on posts. Otherwize marketers could gain - # more social graph info - if not authorized: - pjo = postJsonObject - if not isPublicPost(pjo): - self._404() - self.server.GETbusy = False - return True - removePostInteractions(pjo, True) - if self._requestHTTP(): - recentPostsCache = \ - self.server.recentPostsCache - maxRecentPosts = \ - self.server.maxRecentPosts - translate = \ - self.server.translate - cachedWebfingers = \ - self.server.cachedWebfingers - personCache = \ - self.server.personCache - projectVersion = \ - self.server.projectVersion - ytDomain = \ - self.server.YTReplacementDomain - showPublishedDateOnly = \ - self.server.showPublishedDateOnly - peertubeInstances = \ - self.server.peertubeInstances - cssCache = self.server.cssCache - allowLocalNetworkAccess = \ - self.server.allowLocalNetworkAccess - themeName = \ - self.server.themeName - msg = \ - htmlIndividualPost(cssCache, - recentPostsCache, - maxRecentPosts, - translate, - self.server.baseDir, - self.server.session, - cachedWebfingers, - personCache, - nickname, - domain, - port, - authorized, - postJsonObject, - httpPrefix, - projectVersion, - likedBy, - ytDomain, - showPublishedDateOnly, - peertubeInstances, - allowLocalNetworkAccess, - themeName) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('text/html', msglen, - cookie, callingDomain) - self._write(msg) - else: - if self._fetchAuthenticated(): - msg = json.dumps(postJsonObject, - ensure_ascii=False) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('application/json', - msglen, - None, callingDomain) - self._write(msg) - else: - self._404() - self.server.GETbusy = False - self._benchmarkGETtimings(GETstartTime, GETtimings, - 'new post done', - 'individual post shown') - return True - else: - self._404() - self.server.GETbusy = False - return True - return False + postJsonObject = {} + if loadedPost: + # Only authorized viewers get to see likes + # on posts. Otherwize marketers could gain + # more social graph info + if not authorized: + pjo = postJsonObject + if not isPublicPost(pjo): + self._404() + self.server.GETbusy = False + return True + removePostInteractions(pjo, True) + if self._requestHTTP(): + msg = \ + htmlIndividualPost(self.server.cssCache, + self.server.recentPostsCache, + self.server.maxRecentPosts, + self.server.translate, + self.server.baseDir, + self.server.session, + self.server.cachedWebfingers, + self.server.personCache, + nickname, domain, port, + authorized, + postJsonObject, + httpPrefix, + self.server.projectVersion, + likedBy, + self.server.YTReplacementDomain, + self.server.showPublishedDateOnly, + self.server.peertubeInstances, + self.server.allowLocalNetworkAccess, + self.server.themeName) + msg = msg.encode('utf-8') + msglen = len(msg) + self._set_headers('text/html', msglen, + cookie, callingDomain) + self._write(msg) + else: + if self._fetchAuthenticated(): + msg = json.dumps(postJsonObject, + ensure_ascii=False) + msg = msg.encode('utf-8') + msglen = len(msg) + self._set_headers('application/json', + msglen, + None, callingDomain) + self._write(msg) + else: + self._404() + self.server.GETbusy = False + self._benchmarkGETtimings(GETstartTime, GETtimings, + 'new post done', + 'individual post shown') + return True def _showIndividualPost(self, authorized: bool, callingDomain: str, path: str, From 5f09f52c9f6d92cd8d9678b95e8514a667f37e5c Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 12:52:25 +0100 Subject: [PATCH 15/17] Tidying of individual post functions --- daemon.py | 151 +++++++++++++++++++++--------------------------------- 1 file changed, 57 insertions(+), 94 deletions(-) diff --git a/daemon.py b/daemon.py index 4f21b00f4..8e92e37e5 100644 --- a/daemon.py +++ b/daemon.py @@ -7714,103 +7714,26 @@ class PubServer(BaseHTTPRequestHandler): baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \ httpPrefix + ':##' + domainFull + '#users#' + nickname + \ '#statuses#' + statusNumber + '.json' - if not os.path.isfile(postFilename): - self._404() - self.server.GETbusy = False - return True - postJsonObject = loadJson(postFilename) - loadedPost = False - if postJsonObject: - loadedPost = True - else: - postJsonObject = {} - if loadedPost: - # Only authorized viewers get to see likes - # on posts. Otherwize marketers could gain - # more social graph info - if not authorized: - pjo = postJsonObject - if not isPublicPost(pjo): - self._404() - self.server.GETbusy = False - return True - removePostInteractions(pjo, True) - if self._requestHTTP(): - msg = \ - htmlIndividualPost(self.server.cssCache, - self.server.recentPostsCache, - self.server.maxRecentPosts, - self.server.translate, - self.server.baseDir, - self.server.session, - self.server.cachedWebfingers, - self.server.personCache, - nickname, domain, port, - authorized, - postJsonObject, - httpPrefix, - self.server.projectVersion, - likedBy, - self.server.YTReplacementDomain, - self.server.showPublishedDateOnly, - self.server.peertubeInstances, - self.server.allowLocalNetworkAccess, - self.server.themeName) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('text/html', msglen, - cookie, callingDomain) - self._write(msg) - else: - if self._fetchAuthenticated(): - msg = json.dumps(postJsonObject, - ensure_ascii=False) - msg = msg.encode('utf-8') - msglen = len(msg) - self._set_headers('application/json', - msglen, - None, callingDomain) - self._write(msg) - else: - self._404() - self.server.GETbusy = False - self._benchmarkGETtimings(GETstartTime, GETtimings, - 'new post done', - 'individual post shown') - return True + return self._showPostFromFile(postFilename, likedBy, + authorized, callingDomain, path, + baseDir, httpPrefix, nickname, + domain, domainFull, port, + onionDomain, i2pDomain, + GETstartTime, GETtimings, + proxyType, cookie, debug) - def _showIndividualPost(self, authorized: bool, - callingDomain: str, path: str, - baseDir: str, httpPrefix: str, - domain: str, domainFull: str, port: int, - onionDomain: str, i2pDomain: str, - GETstartTime, GETtimings: {}, - proxyType: str, cookie: str, - debug: str) -> bool: - """Shows an individual post + def _showPostFromFile(self, postFilename: str, likedBy: str, + authorized: bool, + callingDomain: str, path: str, + baseDir: str, httpPrefix: str, nickname: str, + domain: str, domainFull: str, port: int, + onionDomain: str, i2pDomain: str, + GETstartTime, GETtimings: {}, + proxyType: str, cookie: str, + debug: str) -> bool: + """Shows an individual post from its filename """ - likedBy = None - if '?likedBy=' in path: - likedBy = path.split('?likedBy=')[1].strip() - if '?' in likedBy: - likedBy = likedBy.split('?')[0] - path = path.split('?likedBy=')[0] - namedStatus = path.split('/users/')[1] - if '/' not in namedStatus: - return False - postSections = namedStatus.split('/') - if len(postSections) < 3: - return False - nickname = postSections[0] - statusNumber = postSections[2] - if len(statusNumber) <= 10 or (not statusNumber.isdigit()): - return False - - postFilename = \ - baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \ - httpPrefix + ':##' + domainFull + '#users#' + nickname + \ - '#statuses#' + statusNumber + '.json' if not os.path.isfile(postFilename): self._404() self.server.GETbusy = False @@ -7880,6 +7803,46 @@ class PubServer(BaseHTTPRequestHandler): self.server.GETbusy = False return True + def _showIndividualPost(self, authorized: bool, + callingDomain: str, path: str, + baseDir: str, httpPrefix: str, + domain: str, domainFull: str, port: int, + onionDomain: str, i2pDomain: str, + GETstartTime, GETtimings: {}, + proxyType: str, cookie: str, + debug: str) -> bool: + """Shows an individual post + """ + likedBy = None + if '?likedBy=' in path: + likedBy = path.split('?likedBy=')[1].strip() + if '?' in likedBy: + likedBy = likedBy.split('?')[0] + path = path.split('?likedBy=')[0] + namedStatus = path.split('/users/')[1] + if '/' not in namedStatus: + return False + postSections = namedStatus.split('/') + if len(postSections) < 3: + return False + nickname = postSections[0] + statusNumber = postSections[2] + if len(statusNumber) <= 10 or (not statusNumber.isdigit()): + return False + + postFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \ + httpPrefix + ':##' + domainFull + '#users#' + nickname + \ + '#statuses#' + statusNumber + '.json' + + return self._showPostFromFile(postFilename, likedBy, + authorized, callingDomain, path, + baseDir, httpPrefix, nickname, + domain, domainFull, port, + onionDomain, i2pDomain, + GETstartTime, GETtimings, + proxyType, cookie, debug) + def _showInbox(self, authorized: bool, callingDomain: str, path: str, baseDir: str, httpPrefix: str, From d512b23216bd97db2209cca781be616e0f60353c Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 13:00:35 +0100 Subject: [PATCH 16/17] Comments --- daemon.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/daemon.py b/daemon.py index 8e92e37e5..ecc699efb 100644 --- a/daemon.py +++ b/daemon.py @@ -7746,10 +7746,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.GETbusy = False return True - # Only authorized viewers get to see likes - # on posts - # Otherwize marketers could gain more social - # graph info + # Only authorized viewers get to see likes on posts + # Otherwize marketers could gain more social graph info if not authorized: pjo = postJsonObject if not isPublicPost(pjo): From 19bd991fee9ecd9b63c876848ad2c8eecfdf12ad Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 7 Jul 2021 13:26:38 +0100 Subject: [PATCH 17/17] Show local post on notification --- daemon.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ inbox.py | 7 ++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/daemon.py b/daemon.py index ecc699efb..4b8f7354f 100644 --- a/daemon.py +++ b/daemon.py @@ -7841,6 +7841,38 @@ class PubServer(BaseHTTPRequestHandler): GETstartTime, GETtimings, proxyType, cookie, debug) + def _showNotifyPost(self, authorized: bool, + callingDomain: str, path: str, + baseDir: str, httpPrefix: str, + domain: str, domainFull: str, port: int, + onionDomain: str, i2pDomain: str, + GETstartTime, GETtimings: {}, + proxyType: str, cookie: str, + debug: str) -> bool: + """Shows an individual post from an account which you are following + and where you have the notify checkbox set on person options + """ + likedBy = None + postId = path.split('?notifypost=')[1].strip() + postId = postId.replace('-', '/') + path = path.split('?notifypost=')[0] + nickname = path.split('/users/')[1] + if '/' in nickname: + return False + replies = False + + postFilename = locatePost(baseDir, nickname, domain, postId, replies) + if not postFilename: + return False + + return self._showPostFromFile(postFilename, likedBy, + authorized, callingDomain, path, + baseDir, httpPrefix, nickname, + domain, domainFull, port, + onionDomain, i2pDomain, + GETstartTime, GETtimings, + proxyType, cookie, debug) + def _showInbox(self, authorized: bool, callingDomain: str, path: str, baseDir: str, httpPrefix: str, @@ -12419,6 +12451,21 @@ class PubServer(BaseHTTPRequestHandler): 'post roles done', 'show skills done') + if '?notifypost=' in self.path and usersInPath and authorized: + if self._showNotifyPost(authorized, + callingDomain, self.path, + self.server.baseDir, + self.server.httpPrefix, + self.server.domain, + self.server.domainFull, + self.server.port, + self.server.onionDomain, + self.server.i2pDomain, + GETstartTime, GETtimings, + self.server.proxyType, + cookie, self.server.debug): + return + # get an individual post from the path # /users/nickname/statuses/number if '/statuses/' in self.path and usersInPath: diff --git a/inbox.py b/inbox.py index 8203f1f1e..24ed00f90 100644 --- a/inbox.py +++ b/inbox.py @@ -2546,7 +2546,12 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int, if notifyWhenPersonPosts(baseDir, nickname, domain, fromNickname, fromDomainFull): postId = removeIdEnding(jsonObj['id']) - _notifyPostArrival(baseDir, handle, postId) + postLink = \ + httpPrefix + '://' + \ + getFullDomain(domain, port) + \ + '/users/' + nickname + \ + '?notifypost=' + postId.replace('/', '-') + _notifyPostArrival(baseDir, handle, postLink) # If this is a reply to a muted post then also mute it. # This enables you to ignore a threat that's getting boring