From 0c4aa23fad33488be45002537ec1ae9285824b6f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 19 Aug 2021 23:16:32 +0100
Subject: [PATCH 001/385] Define summary
---
inbox.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/inbox.py b/inbox.py
index 3decbf5ba..811d42f4a 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1598,6 +1598,7 @@ def _validPostContent(baseDir: str, nickname: str, domain: str,
if not validPostDate(messageJson['object']['published'], 90, debug):
return False
+ summary = None
if messageJson['object'].get('summary'):
summary = messageJson['object']['summary']
if not isinstance(summary, str):
@@ -1609,7 +1610,7 @@ def _validPostContent(baseDir: str, nickname: str, domain: str,
if isGitPatch(baseDir, nickname, domain,
messageJson['object']['type'],
- messageJson['object']['summary'],
+ summary,
messageJson['object']['content']):
return True
From 6f1bdfb1b19e8efae924f1fbe18e4c01f087125a Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 19 Aug 2021 23:31:25 +0100
Subject: [PATCH 002/385] Show whole message
---
inbox.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/inbox.py b/inbox.py
index 811d42f4a..d6b190cd9 100644
--- a/inbox.py
+++ b/inbox.py
@@ -254,6 +254,7 @@ def inboxMessageHasParams(messageJson: {}) -> bool:
if not isinstance(messageJson['actor'], str):
print('WARN: actor should be a string, but is actually: ' +
str(messageJson['actor']))
+ pprint(messageJson)
return False
# type should be a string
From 9e46a45eb14e818df1d4a46926284f6c8a013cd6 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 20 Aug 2021 12:22:20 +0100
Subject: [PATCH 003/385] Fix network unit tests
---
inbox.py | 9 ++++
tests.py | 128 +++++++++++++++++++++++++++++++++++++------------------
2 files changed, 96 insertions(+), 41 deletions(-)
diff --git a/inbox.py b/inbox.py
index d6b190cd9..dfe5bc929 100644
--- a/inbox.py
+++ b/inbox.py
@@ -225,17 +225,26 @@ def validInboxFilenames(baseDir: str, nickname: str, domain: str,
return True
expectedStr = expectedDomain + ':' + str(expectedPort)
expectedFound = False
+ ctr = 0
for subdir, dirs, files in os.walk(inboxDir):
for f in files:
filename = os.path.join(subdir, f)
+ ctr += 1
if not os.path.isfile(filename):
print('filename: ' + filename)
return False
if expectedStr in filename:
expectedFound = True
break
+ if ctr == 0:
+ return True
if not expectedFound:
print('Expected file was not found: ' + expectedStr)
+ for subdir, dirs, files in os.walk(inboxDir):
+ for f in files:
+ filename = os.path.join(subdir, f)
+ print(filename)
+ break
return False
return True
diff --git a/tests.py b/tests.py
index 4064b1a8a..13e1dc29c 100644
--- a/tests.py
+++ b/tests.py
@@ -2391,6 +2391,18 @@ def _testCreatePerson():
shutil.rmtree(baseDir)
+def showTestBoxes(name: str, inboxPath: str, outboxPath: str) -> None:
+ inboxPosts = \
+ len([name for name in os.listdir(inboxPath)
+ if os.path.isfile(os.path.join(inboxPath, name))])
+ outboxPosts = \
+ len([name for name in os.listdir(outboxPath)
+ if os.path.isfile(os.path.join(outboxPath, name))])
+ print('EVENT: ' + name +
+ ' inbox has ' + str(inboxPosts) + ' posts and ' +
+ str(outboxPosts) + ' outbox posts')
+
+
def _testAuthentication():
print('testAuthentication')
currDir = os.getcwd()
@@ -2432,7 +2444,7 @@ def _testAuthentication():
def testClientToServer():
- print('Testing sending a post via c2s')
+ print('EVENT: Testing sending a post via c2s')
global testServerAliceRunning
global testServerBobRunning
@@ -2509,7 +2521,7 @@ def testClientToServer():
time.sleep(1)
print('\n\n*******************************************************')
- print('Alice sends to Bob via c2s')
+ print('EVENT: Alice sends to Bob via c2s')
sessionAlice = createSession(proxyType)
followersOnly = False
@@ -2522,12 +2534,25 @@ def testClientToServer():
personCache = {}
password = 'alicepass'
conversationId = None
+
+ aliceInboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/inbox'
+ aliceOutboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox'
+ bobInboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox'
+ bobOutboxPath = bobDir + '/accounts/bob@' + bobDomain + '/outbox'
+
outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox'
inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox'
- assert len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))]) == 0
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 0
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0
+ assert len([name for name in os.listdir(aliceOutboxPath)
+ if os.path.isfile(os.path.join(aliceOutboxPath, name))]) == 0
+ assert len([name for name in os.listdir(bobInboxPath)
+ if os.path.isfile(os.path.join(bobInboxPath, name))]) == 0
+ assert len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 0
+ print('EVENT: all inboxes and outboxes are empty')
sendResult = \
sendPostViaServer(__version__,
aliceDir, sessionAlice, 'alice', password,
@@ -2550,23 +2575,32 @@ def testClientToServer():
break
time.sleep(1)
- assert len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))]) == 1
- print(">>> c2s post arrived in Alice's outbox")
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0
+ assert len([name for name in os.listdir(aliceOutboxPath)
+ if os.path.isfile(os.path.join(aliceOutboxPath, name))]) == 1
+ print(">>> c2s post arrived in Alice's outbox\n\n\n")
for i in range(30):
if os.path.isdir(inboxPath):
- if len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 1:
+ if len([name for name in os.listdir(bobInboxPath)
+ if os.path.isfile(os.path.join(bobInboxPath, name))]) == 1:
break
time.sleep(1)
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 1
- print(">>> s2s post arrived in Bob's inbox")
- print("c2s send success")
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(bobInboxPath)
+ if os.path.isfile(os.path.join(bobInboxPath, name))]) == 1
+ assert len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 0
- print('\n\nGetting message id for the post')
+ print(">>> s2s post arrived in Bob's inbox")
+ print("c2s send success\n\n\n")
+
+ print('\n\nEVENT: Getting message id for the post')
statusNumber = 0
outboxPostFilename = None
outboxPostId = None
@@ -2626,7 +2660,7 @@ def testClientToServer():
assert validInboxFilenames(bobDir, 'bob', bobDomain,
aliceDomain, alicePort)
- print('\n\nBob follows Alice')
+ print('\n\nEVENT: Bob follows Alice')
sendFollowRequestViaServer(aliceDir, sessionAlice,
'bob', 'bobpass',
bobDomain, bobPort,
@@ -2666,19 +2700,23 @@ def testClientToServer():
assert 'alice@' + aliceDomain + ':' + str(alicePort) in \
open(bobDir + '/accounts/bob@' + bobDomain + '/following.txt').read()
- print('\n\nBob likes the post')
sessionBob = createSession(proxyType)
password = 'bobpass'
outboxPath = bobDir + '/accounts/bob@' + bobDomain + '/outbox'
inboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/inbox'
- print(str(len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))])))
- assert len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))]) == 1
- print(str(len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))])))
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 1
+ print(str(len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))])))
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 1
+ print(str(len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))])))
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0
+ print('\n\nEVENT: Bob likes the post')
sendLikeViaServer(bobDir, sessionBob,
'bob', 'bobpass',
bobDomain, bobPort,
@@ -2694,21 +2732,27 @@ def testClientToServer():
if test == 1:
break
time.sleep(1)
- assert len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))]) == 2
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 1
- print('Post liked')
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 2
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0
+ print('EVENT: Post liked')
- print('\n\nBob repeats the post')
print(str(len([name for name in os.listdir(outboxPath)
if os.path.isfile(os.path.join(outboxPath, name))])))
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
assert len([name for name in os.listdir(outboxPath)
if os.path.isfile(os.path.join(outboxPath, name))]) == 2
print(str(len([name for name in os.listdir(inboxPath)
if os.path.isfile(os.path.join(inboxPath, name))])))
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 1
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ print('\n\nEVENT: Bob repeats the post')
sendAnnounceViaServer(bobDir, sessionBob, 'bob', password,
bobDomain, bobPort,
httpPrefix, outboxPostId,
@@ -2724,18 +2768,20 @@ def testClientToServer():
break
time.sleep(1)
- assert len([name for name in os.listdir(outboxPath)
- if os.path.isfile(os.path.join(outboxPath, name))]) == 3
- assert len([name for name in os.listdir(inboxPath)
- if os.path.isfile(os.path.join(inboxPath, name))]) == 2
- print('Post repeated')
+ showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
+ showTestBoxes('bob', bobInboxPath, bobOutboxPath)
+ assert len([name for name in os.listdir(bobOutboxPath)
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 3
+ assert len([name for name in os.listdir(aliceInboxPath)
+ if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 1
+ print('EVENT: Post repeated')
inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox'
outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox'
postsBefore = \
len([name for name in os.listdir(inboxPath)
if os.path.isfile(os.path.join(inboxPath, name))])
- print('\n\nAlice deletes her post: ' + outboxPostId + ' ' +
+ print('\n\nEVENT: Alice deletes her post: ' + outboxPostId + ' ' +
str(postsBefore))
password = 'alicepass'
sendDeleteViaServer(aliceDir, sessionAlice, 'alice', password,
@@ -2759,7 +2805,7 @@ def testClientToServer():
assert validInboxFilenames(bobDir, 'bob', bobDomain,
aliceDomain, alicePort)
- print('\n\nAlice unfollows Bob')
+ print('\n\nEVENT: Alice unfollows Bob')
password = 'alicepass'
sendUnfollowRequestViaServer(baseDir, sessionAlice,
'alice', password,
From 609811e633be273c735742d5d45ef6044687385e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 20 Aug 2021 12:23:46 +0100
Subject: [PATCH 004/385] Tidying
---
tests.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests.py b/tests.py
index 13e1dc29c..323b2bd2d 100644
--- a/tests.py
+++ b/tests.py
@@ -2711,7 +2711,7 @@ def testClientToServer():
assert len([name for name in os.listdir(bobOutboxPath)
if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 1
print(str(len([name for name in os.listdir(aliceInboxPath)
- if os.path.isfile(os.path.join(aliceInboxPath, name))])))
+ if os.path.isfile(os.path.join(aliceInboxPath, name))])))
showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
showTestBoxes('bob', bobInboxPath, bobOutboxPath)
assert len([name for name in os.listdir(aliceInboxPath)
From dd5dccad4680693478ba7443b09ad94fb25e8595 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 20 Aug 2021 14:45:42 +0100
Subject: [PATCH 005/385] Handle html in moveTo
---
daemon.py | 2 ++
webapp_profile.py | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/daemon.py b/daemon.py
index 3cd3036d3..c6e6771bd 100644
--- a/daemon.py
+++ b/daemon.py
@@ -6207,6 +6207,8 @@ class PubServer(BaseHTTPRequestHandler):
if actorJson:
if actorJson.get('movedTo'):
movedTo = actorJson['movedTo']
+ if '"' in movedTo:
+ movedTo = movedTo.split('"')[1]
lockedAccount = getLockedAccount(actorJson)
donateUrl = getDonationUrl(actorJson)
websiteUrl = getWebsite(actorJson, self.server.translate)
diff --git a/webapp_profile.py b/webapp_profile.py
index ddb9be18e..6c94a6803 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -125,6 +125,8 @@ def htmlProfileAfterSearch(cssCache: {},
movedTo = ''
if profileJson.get('movedTo'):
movedTo = profileJson['movedTo']
+ if '"' in movedTo:
+ movedTo = movedTo.split('"')[1]
displayName += ' ⌂'
followsYou = \
@@ -702,6 +704,8 @@ def htmlProfile(rssIconAtTop: bool,
movedTo = ''
if profileJson.get('movedTo'):
movedTo = profileJson['movedTo']
+ if '"' in movedTo:
+ movedTo = movedTo.split('"')[1]
alsoKnownAs = None
if profileJson.get('alsoKnownAs'):
From 8edc3ac7efbb2978a1d83daff9438e62eb713df1 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 20 Aug 2021 14:51:33 +0100
Subject: [PATCH 006/385] Remove space
---
webapp_profile.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webapp_profile.py b/webapp_profile.py
index 6c94a6803..a2e824c73 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -432,7 +432,7 @@ def _getProfileHeaderAfterSearch(baseDir: str,
if newNickname and newDomain:
newHandle = newNickname + '@' + newDomainFull
htmlStr += ' ' + translate['New account'] + \
- ': < a href="' + movedTo + '">@' + newHandle + '
\n'
+ ': @' + newHandle + '
\n'
elif alsoKnownAs:
otherAccountshtml = \
' ' + translate['Other accounts'] + ': '
From 2e323a4641b51b3ebd7ecf7452e77a1fe982a8f9 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 20 Aug 2021 14:56:52 +0100
Subject: [PATCH 007/385] Show blog address on profile
---
webapp_profile.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/webapp_profile.py b/webapp_profile.py
index a2e824c73..2d5005255 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -552,6 +552,7 @@ def htmlProfile(rssIconAtTop: bool,
donateSection = ''
donateUrl = getDonationUrl(profileJson)
websiteUrl = getWebsite(profileJson, translate)
+ blogAddress = getBlogAddress(profileJson)
PGPpubKey = getPGPpubKey(profileJson)
PGPfingerprint = getPGPfingerprint(profileJson)
emailAddress = getEmailAddress(profileJson)
@@ -581,6 +582,10 @@ def htmlProfile(rssIconAtTop: bool,
donateSection += \
'' + translate['Email'] + ': ' + emailAddress + '
\n'
+ if blogAddress:
+ donateSection += \
+ 'Blog: ' + blogAddress + '
\n'
if xmppAddress:
donateSection += \
'' + translate['XMPP'] + ': []:
"""Returns the possible shares files
"""
return ('shares', 'wanted')
+
+
+def replaceUsersWithAt(actor: str) -> str:
+ """ https://domain/users/nick becomes https://domain/@nick
+ """
+ uPaths = getUserPaths()
+ for path in uPaths:
+ if path in actor:
+ actor = actor.replace(path, '/@')
+ break
+ return actor
diff --git a/webapp_calendar.py b/webapp_calendar.py
index 81e4e65d8..26a0be727 100644
--- a/webapp_calendar.py
+++ b/webapp_calendar.py
@@ -22,6 +22,7 @@ from utils import getAltPath
from utils import removeDomainPort
from utils import acctDir
from utils import localActorUrl
+from utils import replaceUsersWithAt
from happening import getTodaysEvents
from happening import getCalendarEvents
from webapp_utils import htmlHeaderWithExternalStyle
@@ -175,7 +176,7 @@ def _htmlCalendarDay(personCache: {}, cssCache: {}, translate: {},
if senderName and eventDescription:
# if the sender is also mentioned within the event
# description then this is a reminder
- senderActor2 = senderActor.replace('/users/', '/@')
+ senderActor2 = replaceUsersWithAt(senderActor)
if senderActor not in eventDescription and \
senderActor2 not in eventDescription:
eventDescription = senderName + eventDescription
From d7b26f5cb118eaa38d7f67e77851372aabd57d8c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 23 Aug 2021 09:41:04 +0100
Subject: [PATCH 018/385] Set nickname for block
---
daemon.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 6183e7ff8..723c8fe93 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10954,7 +10954,8 @@ class PubServer(BaseHTTPRequestHandler):
'to': [toUrl],
'cc': [ccUrl]
}
- self._postToOutbox(blockJson, self.server.projectVersion)
+ self._postToOutbox(blockJson, self.server.projectVersion,
+ blockerNickname)
return True
def do_GET(self):
From 047ba3f174e95713788a156fa740a24a98a2a472 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 23 Aug 2021 10:32:21 +0100
Subject: [PATCH 019/385] Explicit third argument
---
daemon.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/daemon.py b/daemon.py
index 723c8fe93..6616ef557 100644
--- a/daemon.py
+++ b/daemon.py
@@ -1131,7 +1131,7 @@ class PubServer(BaseHTTPRequestHandler):
return True
def _postToOutbox(self, messageJson: {}, version: str,
- postToNickname: str = None) -> bool:
+ postToNickname: str) -> bool:
"""post is received by the outbox
Client to server message post
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
@@ -7105,7 +7105,7 @@ class PubServer(BaseHTTPRequestHandler):
print('WARN: unable to locate file for liked post ' +
likeUrl)
# send out the like to followers
- self._postToOutbox(likeJson, self.server.projectVersion)
+ self._postToOutbox(likeJson, self.server.projectVersion, None)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7202,7 +7202,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.iconsCache.get('like_inactive.png'):
del self.server.iconsCache['like_inactive.png']
# send out the undo like to followers
- self._postToOutbox(undoLikeJson, self.server.projectVersion)
+ self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7289,7 +7289,7 @@ class PubServer(BaseHTTPRequestHandler):
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('bookmark.png'):
del self.server.iconsCache['bookmark.png']
- # self._postToOutbox(bookmarkJson, self.server.projectVersion)
+ # self._postToOutbox(bookmarkJson, self.server.projectVersion, None)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7375,7 +7375,8 @@ class PubServer(BaseHTTPRequestHandler):
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('bookmark_inactive.png'):
del self.server.iconsCache['bookmark_inactive.png']
- # self._postToOutbox(undoBookmarkJson, self.server.projectVersion)
+ # self._postToOutbox(undoBookmarkJson,
+ # self.server.projectVersion, None)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -15438,7 +15439,7 @@ class PubServer(BaseHTTPRequestHandler):
# https://www.w3.org/TR/activitypub/#object-without-create
if self.outboxAuthenticated:
- if self._postToOutbox(messageJson, __version__):
+ if self._postToOutbox(messageJson, __version__, None):
if messageJson.get('id'):
locnStr = removeIdEnding(messageJson['id'])
self.headers['Location'] = locnStr
From 6433e1e89f250dc41250a39a2f324a8ee30d7d9e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 23 Aug 2021 13:30:16 +0100
Subject: [PATCH 020/385] Check that announced object is not blocked
---
inbox.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/inbox.py b/inbox.py
index dfe5bc929..8e094b616 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1338,6 +1338,16 @@ def _receiveAnnounce(recentPostsCache: {},
actorNickname + '@' + actorDomain)
return False
+ # also check the actor for the url being announced
+ announcedActorNickname = getNicknameFromActor(messageJson['object'])
+ announcedActorDomain, announcedActorPort = \
+ getDomainFromActor(messageJson['object'])
+ if isBlocked(baseDir, nickname, domain,
+ announcedActorNickname, announcedActorDomain):
+ print('Receive announce blocked for actor: ' +
+ announcedActorNickname + '@' + announcedActorDomain)
+ return False
+
# is this post in the outbox of the person?
postFilename = locatePost(baseDir, nickname, domain,
messageJson['object'])
From 73751d19e78610d1287d71c49d6fce996dfbb862 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 23 Aug 2021 13:31:37 +0100
Subject: [PATCH 021/385] Different debug string
---
inbox.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inbox.py b/inbox.py
index 8e094b616..1ca740a2f 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1344,7 +1344,7 @@ def _receiveAnnounce(recentPostsCache: {},
getDomainFromActor(messageJson['object'])
if isBlocked(baseDir, nickname, domain,
announcedActorNickname, announcedActorDomain):
- print('Receive announce blocked for actor: ' +
+ print('Receive announce object blocked for actor: ' +
announcedActorNickname + '@' + announcedActorDomain)
return False
From c3d1ae504bd8fa98aded71c8976764277ec8af4c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 25 Aug 2021 11:37:13 +0100
Subject: [PATCH 022/385] Missing parameter
---
daemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 6616ef557..ba175e4e5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -1193,7 +1193,7 @@ class PubServer(BaseHTTPRequestHandler):
print('Creating outbox thread')
self.server.outboxThread[accountOutboxThreadName] = \
threadWithTrace(target=self._postToOutbox,
- args=(messageJson.copy(), __version__),
+ args=(messageJson.copy(), __version__, None),
daemon=True)
print('Starting outbox thread')
self.server.outboxThread[accountOutboxThreadName].start()
From a408685a1328d684c61710465b95eff213222fed Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 26 Aug 2021 14:25:26 +0100
Subject: [PATCH 023/385] Bicycle tools
---
ontology/toolTypes.json | 154 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 154 insertions(+)
diff --git a/ontology/toolTypes.json b/ontology/toolTypes.json
index 94d17e455..8b6bd50ff 100644
--- a/ontology/toolTypes.json
+++ b/ontology/toolTypes.json
@@ -12909,6 +12909,160 @@
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#cable",
"@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-pump",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Pump",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-chain-tool",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-tire-lever",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-cleaner",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-repair-stand",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#torque-wrench",
+ "rdfs:label": [
+ {
+ "@value": "Torque Wrench",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#y-wrench",
+ "rdfs:label": [
+ {
+ "@value": "Y Wrench",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-chain-wear-checker",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#cable-cutter",
+ "rdfs:label": [
+ {
+ "@value": "Cable Cutter",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#cutting-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-chain-cleaner",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#tape-measure",
+ "rdfs:label": [
+ {
+ "@value": "Tape Measure",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#measuring-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-chain-whip",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#cassette-wrench",
+ "rdfs:label": [
+ {
+ "@value": "Cassette Wrench",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#bicycle-chain-keeper",
+ "rdfs:label": [
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "en"
+ },
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
+ "@type": "dfc-p:ProductType"
}
]
}
From 7b496707fa5ba46e206becc19e1898ea6d2f6603 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 26 Aug 2021 14:42:29 +0100
Subject: [PATCH 024/385] Tool type translations
---
ontology/clothesTypes.json | 150 +++++++
ontology/toolTypes.json | 896 +++++++++++++++++++++++++++++++++++++
2 files changed, 1046 insertions(+)
diff --git a/ontology/clothesTypes.json b/ontology/clothesTypes.json
index 7f0eb517f..67e8e3e3c 100644
--- a/ontology/clothesTypes.json
+++ b/ontology/clothesTypes.json
@@ -2859,6 +2859,156 @@
],
"dfc-p:specialize": "https://clothes/data/toolTypes.rdf#kilt",
"@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://clothes/data/clothesTypes.rdf#apron",
+ "rdfs:label": [
+ {
+ "@value": "Apron",
+ "@language": "en"
+ },
+ {
+ "@value": "Apron",
+ "@language": "ar"
+ },
+ {
+ "@value": "Apron",
+ "@language": "ku"
+ },
+ {
+ "@value": "Apron",
+ "@language": "es"
+ },
+ {
+ "@value": "Grembiule",
+ "@language": "it"
+ },
+ {
+ "@value": "Apres",
+ "@language": "de"
+ },
+ {
+ "@value": "Apron",
+ "@language": "sw"
+ },
+ {
+ "@value": "Avental",
+ "@language": "pt"
+ },
+ {
+ "@value": "Apron",
+ "@language": "oc"
+ },
+ {
+ "@value": "Абон",
+ "@language": "ru"
+ },
+ {
+ "@value": "Apron",
+ "@language": "cy"
+ },
+ {
+ "@value": "エプロン",
+ "@language": "ja"
+ },
+ {
+ "@value": "An tAthrú",
+ "@language": "ga"
+ },
+ {
+ "@value": "एप्रन",
+ "@language": "hi"
+ },
+ {
+ "@value": "环境",
+ "@language": "zh"
+ },
+ {
+ "@value": "Apron",
+ "@language": "fr"
+ },
+ {
+ "@value": "Apron",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://clothes/data/toolTypes.rdf#apron",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://clothes/data/clothesTypes.rdf#corset",
+ "rdfs:label": [
+ {
+ "@value": "Corset",
+ "@language": "en"
+ },
+ {
+ "@value": "Corset",
+ "@language": "ar"
+ },
+ {
+ "@value": "Corset",
+ "@language": "ku"
+ },
+ {
+ "@value": "Corset",
+ "@language": "es"
+ },
+ {
+ "@value": "Corse",
+ "@language": "it"
+ },
+ {
+ "@value": "Korsett",
+ "@language": "de"
+ },
+ {
+ "@value": "Corset",
+ "@language": "sw"
+ },
+ {
+ "@value": "Espartilho",
+ "@language": "pt"
+ },
+ {
+ "@value": "Corset",
+ "@language": "oc"
+ },
+ {
+ "@value": "Корсет",
+ "@language": "ru"
+ },
+ {
+ "@value": "Corset",
+ "@language": "cy"
+ },
+ {
+ "@value": "コルセット",
+ "@language": "ja"
+ },
+ {
+ "@value": "Sraith",
+ "@language": "ga"
+ },
+ {
+ "@value": "कोर्सेट",
+ "@language": "hi"
+ },
+ {
+ "@value": "Cset",
+ "@language": "zh"
+ },
+ {
+ "@value": "Corset",
+ "@language": "fr"
+ },
+ {
+ "@value": "Corset",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://clothes/data/toolTypes.rdf#corset",
+ "@type": "dfc-p:ProductType"
}
]
}
diff --git a/ontology/toolTypes.json b/ontology/toolTypes.json
index 8b6bd50ff..c08f87e56 100644
--- a/ontology/toolTypes.json
+++ b/ontology/toolTypes.json
@@ -12917,6 +12917,70 @@
"@value": "Bicycle Pump",
"@language": "en"
},
+ {
+ "@value": "مضخة دراجة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Pump",
+ "@language": "ku"
+ },
+ {
+ "@value": "Bomba de bicicletas",
+ "@language": "es"
+ },
+ {
+ "@value": "Pompa per bicicletta",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradpumpe",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Pump",
+ "@language": "sw"
+ },
+ {
+ "@value": "Bomba de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Pump",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипедный насос",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Pump",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車ポンプ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Caidéal Rothar",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल पम्प",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 自行车",
+ "@language": "zh"
+ },
+ {
+ "@value": "Pompe à vélo",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Pump",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -12928,6 +12992,70 @@
"@value": "Bicycle Chain Tool",
"@language": "en"
},
+ {
+ "@value": "دراجة شاين تول",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "ku"
+ },
+ {
+ "@value": "Herramienta de cadena de bicicletas",
+ "@language": "es"
+ },
+ {
+ "@value": "Strumento per la catena della bicicletta",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradkette Werkzeug",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "sw"
+ },
+ {
+ "@value": "Ferramenta de cadeia de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипедная цепь Инструмент",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車チェーンツール",
+ "@language": "ja"
+ },
+ {
+ "@value": "Rothar Slabhra Uirlis",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल चेन उपकरण",
+ "@language": "hi"
+ },
+ {
+ "@value": "拜多尔",
+ "@language": "zh"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Chain Tool",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -12939,6 +13067,70 @@
"@value": "Bicycle Tire Lever",
"@language": "en"
},
+ {
+ "@value": "إطار الدراجة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "ku"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "es"
+ },
+ {
+ "@value": "Bicicletta Tire Lever",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradreifen Lever",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "sw"
+ },
+ {
+ "@value": "Alavanca de pneus de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипед Tire Lever",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車タイヤレバー",
+ "@language": "ja"
+ },
+ {
+ "@value": "Rothar Tire Lever",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल टायर लीवर",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 拜 循环",
+ "@language": "zh"
+ },
+ {
+ "@value": "Pneus de vélo Lever",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Tire Lever",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -12950,6 +13142,70 @@
"@value": "Bicycle Cleaner",
"@language": "en"
},
+ {
+ "@value": "منظف الدراجات",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "ku"
+ },
+ {
+ "@value": "Limpiador de bicicletas",
+ "@language": "es"
+ },
+ {
+ "@value": "Pulizia della bicicletta",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradreiniger",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "sw"
+ },
+ {
+ "@value": "Limpeza de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "oc"
+ },
+ {
+ "@value": "Уборка велосипедов",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車クリーナー",
+ "@language": "ja"
+ },
+ {
+ "@value": "Glantóir Rothar",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल क्लीनर",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 双周期清洁",
+ "@language": "zh"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Cleaner",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -12961,6 +13217,70 @@
"@value": "Bicycle Repair Stand",
"@language": "en"
},
+ {
+ "@value": "اعادة تدوير الدراجات",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "ku"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "es"
+ },
+ {
+ "@value": "Stand di riparazione della bicicletta",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrrad-Reparaturständer",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "sw"
+ },
+ {
+ "@value": "Suporte de reparação de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипедный ремонтный стенд",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車修理スタンド",
+ "@language": "ja"
+ },
+ {
+ "@value": "Staid Deisiúchán Rothar",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल मरम्मत स्टैंड",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 双周期内",
+ "@language": "zh"
+ },
+ {
+ "@value": "Support de réparation de vélos",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Repair Stand",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -12972,6 +13292,70 @@
"@value": "Torque Wrench",
"@language": "en"
},
+ {
+ "@value": "Torque Wrench",
+ "@language": "ar"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "ku"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "es"
+ },
+ {
+ "@value": "Avvolgitore di coppia",
+ "@language": "it"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "de"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "sw"
+ },
+ {
+ "@value": "Chave de torção",
+ "@language": "pt"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "oc"
+ },
+ {
+ "@value": "Торке Воскрес",
+ "@language": "ru"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "cy"
+ },
+ {
+ "@value": "トルクレンチ",
+ "@language": "ja"
+ },
+ {
+ "@value": "riachtanais uisce: measartha",
+ "@language": "ga"
+ },
+ {
+ "@value": "टॉर्क रिंच",
+ "@language": "hi"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "zh"
+ },
+ {
+ "@value": "Clé de torsion",
+ "@language": "fr"
+ },
+ {
+ "@value": "Torque Wrench",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
"@type": "dfc-p:ProductType"
@@ -12983,6 +13367,70 @@
"@value": "Y Wrench",
"@language": "en"
},
+ {
+ "@value": "Y Wrench",
+ "@language": "ar"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "ku"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "es"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "it"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "de"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "sw"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "pt"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "oc"
+ },
+ {
+ "@value": "Y Воланч",
+ "@language": "ru"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "cy"
+ },
+ {
+ "@value": "Yレンチ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "ga"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "hi"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "zh"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "fr"
+ },
+ {
+ "@value": "Y Wrench",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
"@type": "dfc-p:ProductType"
@@ -12994,6 +13442,70 @@
"@value": "Bicycle Chain Wear Checker",
"@language": "en"
},
+ {
+ "@value": "دفتر أحذية",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "ku"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "es"
+ },
+ {
+ "@value": "Biciclette a catena",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradkettenbekleidung Checker",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "sw"
+ },
+ {
+ "@value": "Verificador de desgaste de cadeia de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипед Цепь Носить Checker",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車チェーン ウェア チェッカー",
+ "@language": "ja"
+ },
+ {
+ "@value": "Rothar Seain Sheiceáil Caith",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल चेन पहनें चेकर",
+ "@language": "hi"
+ },
+ {
+ "@value": "比拜多·查宁·韦尔·查克尔",
+ "@language": "zh"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Chain Wear Checker",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -13005,6 +13517,70 @@
"@value": "Cable Cutter",
"@language": "en"
},
+ {
+ "@value": "Cable Cutter",
+ "@language": "ar"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "ku"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "es"
+ },
+ {
+ "@value": "Cutter del cavo",
+ "@language": "it"
+ },
+ {
+ "@value": "Kabelschneider",
+ "@language": "de"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "sw"
+ },
+ {
+ "@value": "Cortador de cabo",
+ "@language": "pt"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "oc"
+ },
+ {
+ "@value": "Кабель Cutter",
+ "@language": "ru"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "cy"
+ },
+ {
+ "@value": "ケーブルカッター",
+ "@language": "ja"
+ },
+ {
+ "@value": "Cábla cutter",
+ "@language": "ga"
+ },
+ {
+ "@value": "केबल कटर",
+ "@language": "hi"
+ },
+ {
+ "@value": "物质",
+ "@language": "zh"
+ },
+ {
+ "@value": "Cutter de câble",
+ "@language": "fr"
+ },
+ {
+ "@value": "Cable Cutter",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#cutting-tool",
"@type": "dfc-p:ProductType"
@@ -13016,6 +13592,70 @@
"@value": "Bicycle Chain Cleaner",
"@language": "en"
},
+ {
+ "@value": "منظف الدراجات النارية",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "ku"
+ },
+ {
+ "@value": "Limpiador de cadena de bicicletas",
+ "@language": "es"
+ },
+ {
+ "@value": "Pulitore di catena per biciclette",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrradkettenreiniger",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "sw"
+ },
+ {
+ "@value": "Limpador de corrente de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипед Цепной Уборка",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車チェーンクリーナー",
+ "@language": "ja"
+ },
+ {
+ "@value": "Gluaisrothar Glantóir Slabhra",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल चेन क्लीनर",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 比循环 Chain 清洁",
+ "@language": "zh"
+ },
+ {
+ "@value": "Nettoyeur de chaîne de vélo",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Chain Cleaner",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -13027,6 +13667,70 @@
"@value": "Tape Measure",
"@language": "en"
},
+ {
+ "@value": "قياس التايب",
+ "@language": "ar"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "ku"
+ },
+ {
+ "@value": "Medida de cinta",
+ "@language": "es"
+ },
+ {
+ "@value": "Misura del nastro",
+ "@language": "it"
+ },
+ {
+ "@value": "Bandmessung",
+ "@language": "de"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "sw"
+ },
+ {
+ "@value": "Medida de fita",
+ "@language": "pt"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "oc"
+ },
+ {
+ "@value": "Лента Measure",
+ "@language": "ru"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "cy"
+ },
+ {
+ "@value": "テープ測定",
+ "@language": "ja"
+ },
+ {
+ "@value": "Tomhais Téip",
+ "@language": "ga"
+ },
+ {
+ "@value": "टेप उपाय",
+ "@language": "hi"
+ },
+ {
+ "@value": "塔佩克测量",
+ "@language": "zh"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "fr"
+ },
+ {
+ "@value": "Tape Measure",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#measuring-tool",
"@type": "dfc-p:ProductType"
@@ -13038,6 +13742,70 @@
"@value": "Bicycle Chain Whip",
"@language": "en"
},
+ {
+ "@value": "درّاجة (شاين ويب)",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "ku"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "es"
+ },
+ {
+ "@value": "Bicicletta catena Whip",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrrad-Kette Whip",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "sw"
+ },
+ {
+ "@value": "Balope de corrente de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипедная цепь Whip",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車チェーンホイップ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Aoire Slabhra Rothar",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल चेन विप",
+ "@language": "hi"
+ },
+ {
+ "@value": "比 循环 Chain Whip",
+ "@language": "zh"
+ },
+ {
+ "@value": "chaîne de vélo Whip",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Chain Whip",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
@@ -13049,6 +13817,70 @@
"@value": "Cassette Wrench",
"@language": "en"
},
+ {
+ "@value": "Kaette Wrench",
+ "@language": "ar"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "ku"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "es"
+ },
+ {
+ "@value": "Avvolgica per cassette",
+ "@language": "it"
+ },
+ {
+ "@value": "Das ist ein toller",
+ "@language": "de"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "sw"
+ },
+ {
+ "@value": "Chave de fenda",
+ "@language": "pt"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "oc"
+ },
+ {
+ "@value": "Кассета Wrench",
+ "@language": "ru"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "cy"
+ },
+ {
+ "@value": "カセットレンチ",
+ "@language": "ja"
+ },
+ {
+ "@value": "cliceáil grianghraf a mhéadú",
+ "@language": "ga"
+ },
+ {
+ "@value": "कैसेट रिंच",
+ "@language": "hi"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "zh"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "fr"
+ },
+ {
+ "@value": "Cassette Wrench",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
"@type": "dfc-p:ProductType"
@@ -13060,6 +13892,70 @@
"@value": "Bicycle Chain Keeper",
"@language": "en"
},
+ {
+ "@value": "حامض الدراجات النارية",
+ "@language": "ar"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "ku"
+ },
+ {
+ "@value": "Ciclo de cadena de mantenimiento",
+ "@language": "es"
+ },
+ {
+ "@value": "Cuscinetto della catena della bicicletta",
+ "@language": "it"
+ },
+ {
+ "@value": "Fahrrad Kettenhalter",
+ "@language": "de"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "sw"
+ },
+ {
+ "@value": "Guardador de corrente de bicicleta",
+ "@language": "pt"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "oc"
+ },
+ {
+ "@value": "Велосипедная цепь Keeper",
+ "@language": "ru"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "cy"
+ },
+ {
+ "@value": "自転車チェーン Keeper",
+ "@language": "ja"
+ },
+ {
+ "@value": "Rothar Slabhra Coimeádaí",
+ "@language": "ga"
+ },
+ {
+ "@value": "साइकिल चेन कीपर",
+ "@language": "hi"
+ },
+ {
+ "@value": "比周期",
+ "@language": "zh"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "fr"
+ },
+ {
+ "@value": "Bicycle Chain Keeper",
+ "@language": "ca"
+ }
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
From e56504faf4cd0c3b470c45a2263dd6ead27e0b0d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 26 Aug 2021 14:57:58 +0100
Subject: [PATCH 025/385] Boating tool types
---
ontology/toolTypes.json | 675 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 675 insertions(+)
diff --git a/ontology/toolTypes.json b/ontology/toolTypes.json
index c08f87e56..efef6d8da 100644
--- a/ontology/toolTypes.json
+++ b/ontology/toolTypes.json
@@ -13959,6 +13959,681 @@
],
"dfc-p:specialize": "https://tools/data/toolTypes.rdf#bicycle",
"@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#battery-terminal-puller",
+ "rdfs:label": [
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "en"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "ar"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "ku"
+ },
+ {
+ "@value": "Terminal de batería",
+ "@language": "es"
+ },
+ {
+ "@value": "Caricabatterie",
+ "@language": "it"
+ },
+ {
+ "@value": "Batterie Klemmen Puller",
+ "@language": "de"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "sw"
+ },
+ {
+ "@value": "Puxador de terminal de bateria",
+ "@language": "pt"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "oc"
+ },
+ {
+ "@value": "Батарея Терминал Puller",
+ "@language": "ru"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "cy"
+ },
+ {
+ "@value": "バッテリーターミナルプルア",
+ "@language": "ja"
+ },
+ {
+ "@value": "Uirlisí ilchuspóireacha",
+ "@language": "ga"
+ },
+ {
+ "@value": "बैटरी टर्मिनल पुलर",
+ "@language": "hi"
+ },
+ {
+ "@value": "Bttery用语Puller",
+ "@language": "zh"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "fr"
+ },
+ {
+ "@value": "Battery Terminal Puller",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#battery",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#jumper-cables",
+ "rdfs:label": [
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "en"
+ },
+ {
+ "@value": "الكابلات/الرحلات",
+ "@language": "ar"
+ },
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "ku"
+ },
+ {
+ "@value": "Cables de salto / cuentas",
+ "@language": "es"
+ },
+ {
+ "@value": "Cavi di salto/cavo",
+ "@language": "it"
+ },
+ {
+ "@value": "Jumper Kabel/Stecker",
+ "@language": "de"
+ },
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "sw"
+ },
+ {
+ "@value": "Cabos de ligação/mangueiras",
+ "@language": "pt"
+ },
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "oc"
+ },
+ {
+ "@value": "Прыжок кабели/лиды",
+ "@language": "ru"
+ },
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "cy"
+ },
+ {
+ "@value": "ジャンパーケーブル/鉛",
+ "@language": "ja"
+ },
+ {
+ "@value": "cáblaí / luaidhe Léim",
+ "@language": "ga"
+ },
+ {
+ "@value": "जम्पर केबल/लीड",
+ "@language": "hi"
+ },
+ {
+ "@value": "Jumper电缆/铅",
+ "@language": "zh"
+ },
+ {
+ "@value": "Câbles de saut/perles",
+ "@language": "fr"
+ },
+ {
+ "@value": "Jumper cables/leads",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#battery",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#tube-cutter",
+ "rdfs:label": [
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "en"
+ },
+ {
+ "@value": "Hose/ Tube Cutter",
+ "@language": "ar"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "ku"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "es"
+ },
+ {
+ "@value": "Tubo / Tubo",
+ "@language": "it"
+ },
+ {
+ "@value": "Schlauch / Rohrschneider",
+ "@language": "de"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "sw"
+ },
+ {
+ "@value": "Mangueira / Cortador de tubos",
+ "@language": "pt"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "oc"
+ },
+ {
+ "@value": "Шланг / Tube Cutter",
+ "@language": "ru"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "cy"
+ },
+ {
+ "@value": "ホース/管カッター",
+ "@language": "ja"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "ga"
+ },
+ {
+ "@value": "नली / ट्यूब कटर",
+ "@language": "hi"
+ },
+ {
+ "@value": "Hose/ Tube Cutter",
+ "@language": "zh"
+ },
+ {
+ "@value": "Cutter à l ' ossature",
+ "@language": "fr"
+ },
+ {
+ "@value": "Hose / Tube Cutter",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#cutting-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#moisture-meter",
+ "rdfs:label": [
+ {
+ "@value": "Moisture Meter",
+ "@language": "en"
+ },
+ {
+ "@value": "قياس الحركة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "ku"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "es"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "it"
+ },
+ {
+ "@value": "Feuchtemesser",
+ "@language": "de"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "sw"
+ },
+ {
+ "@value": "Medidor de umidade",
+ "@language": "pt"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "oc"
+ },
+ {
+ "@value": "Метр влаги",
+ "@language": "ru"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "cy"
+ },
+ {
+ "@value": "湿気のメートル",
+ "@language": "ja"
+ },
+ {
+ "@value": "Méadar taise",
+ "@language": "ga"
+ },
+ {
+ "@value": "नमी मीटर",
+ "@language": "hi"
+ },
+ {
+ "@value": "摩尔·梅雷",
+ "@language": "zh"
+ },
+ {
+ "@value": "Metteur en température",
+ "@language": "fr"
+ },
+ {
+ "@value": "Moisture Meter",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#measuring-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#retrieval-tool",
+ "rdfs:label": [
+ {
+ "@value": "Retrieval Tool",
+ "@language": "en"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "ar"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "ku"
+ },
+ {
+ "@value": "Herramienta de recuperación",
+ "@language": "es"
+ },
+ {
+ "@value": "Strumento di recupero",
+ "@language": "it"
+ },
+ {
+ "@value": "Abrufwerkzeug",
+ "@language": "de"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "sw"
+ },
+ {
+ "@value": "Ferramenta de recuperação",
+ "@language": "pt"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "oc"
+ },
+ {
+ "@value": "Инструмент для поиска",
+ "@language": "ru"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "cy"
+ },
+ {
+ "@value": "リトリバルツール",
+ "@language": "ja"
+ },
+ {
+ "@value": "Uirlisí Retrival",
+ "@language": "ga"
+ },
+ {
+ "@value": "पुनर्प्राप्ति उपकरण",
+ "@language": "hi"
+ },
+ {
+ "@value": "雷瓦尔·托维斯",
+ "@language": "zh"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "fr"
+ },
+ {
+ "@value": "Retrieval Tool",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#retrieval-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#hatchet",
+ "rdfs:label": [
+ {
+ "@value": "Hatchet",
+ "@language": "en"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "ar"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "ku"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "es"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "it"
+ },
+ {
+ "@value": "Hasche",
+ "@language": "de"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "sw"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "pt"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "oc"
+ },
+ {
+ "@value": "Хатчет",
+ "@language": "ru"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "cy"
+ },
+ {
+ "@value": "ハチェット",
+ "@language": "ja"
+ },
+ {
+ "@value": "cineál gas: in airde",
+ "@language": "ga"
+ },
+ {
+ "@value": "हैचेट",
+ "@language": "hi"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "zh"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "fr"
+ },
+ {
+ "@value": "Hatchet",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#cutting-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#top-snapper",
+ "rdfs:label": [
+ {
+ "@value": "Top Snapper",
+ "@language": "en"
+ },
+ {
+ "@value": "أعلى",
+ "@language": "ar"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "ku"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "es"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "it"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "de"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "sw"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "pt"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "oc"
+ },
+ {
+ "@value": "Топ Snapper",
+ "@language": "ru"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "cy"
+ },
+ {
+ "@value": "トップスナッパー",
+ "@language": "ja"
+ },
+ {
+ "@value": "Barr an leathanaigh",
+ "@language": "ga"
+ },
+ {
+ "@value": "शीर्ष स्नैपर",
+ "@language": "hi"
+ },
+ {
+ "@value": "例 例",
+ "@language": "zh"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "fr"
+ },
+ {
+ "@value": "Top Snapper",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#top-snapper",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#rope-cutter",
+ "rdfs:label": [
+ {
+ "@value": "Rope Cutter",
+ "@language": "en"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "ar"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "ku"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "es"
+ },
+ {
+ "@value": "Cutter di corda",
+ "@language": "it"
+ },
+ {
+ "@value": "Der Kopf",
+ "@language": "de"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "sw"
+ },
+ {
+ "@value": "Cortador de corda",
+ "@language": "pt"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "oc"
+ },
+ {
+ "@value": "Веревка Cutter",
+ "@language": "ru"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "cy"
+ },
+ {
+ "@value": "ロープカッター",
+ "@language": "ja"
+ },
+ {
+ "@value": "Rope cutter",
+ "@language": "ga"
+ },
+ {
+ "@value": "रस्सी कटर",
+ "@language": "hi"
+ },
+ {
+ "@value": "求 力",
+ "@language": "zh"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "fr"
+ },
+ {
+ "@value": "Rope Cutter",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#cutting-tool",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://tools/data/toolTypes.rdf#adjustable-spanner-wrench",
+ "rdfs:label": [
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "en"
+ },
+ {
+ "@value": "Spanner Wrench",
+ "@language": "ar"
+ },
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "ku"
+ },
+ {
+ "@value": "Wrench de Spanner ajustable",
+ "@language": "es"
+ },
+ {
+ "@value": "Avvitatore di ricambio regolabile",
+ "@language": "it"
+ },
+ {
+ "@value": "Einstellbare Spannschraube",
+ "@language": "de"
+ },
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "sw"
+ },
+ {
+ "@value": "Chave de spanner ajustável",
+ "@language": "pt"
+ },
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "oc"
+ },
+ {
+ "@value": "Регулируемый Spanner Wrench",
+ "@language": "ru"
+ },
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "cy"
+ },
+ {
+ "@value": "調節可能なスパナーレンチ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Inoiriúnaithe Spanner Wrench",
+ "@language": "ga"
+ },
+ {
+ "@value": "समायोज्य स्पैनर रिंच",
+ "@language": "hi"
+ },
+ {
+ "@value": "A. 理应",
+ "@language": "zh"
+ },
+ {
+ "@value": "Ajustable Spanner Wrench",
+ "@language": "fr"
+ },
+ {
+ "@value": "Adjustable Spanner Wrench",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://tools/data/toolTypes.rdf#wrench",
+ "@type": "dfc-p:ProductType"
}
]
}
From bd497b6d20e643fb09ad15f2bfb0cc61c225582e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 30 Aug 2021 19:06:04 +0100
Subject: [PATCH 026/385] Another easy block decision
---
utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/utils.py b/utils.py
index 795d4cadb..cd83eee44 100644
--- a/utils.py
+++ b/utils.py
@@ -24,7 +24,7 @@ from cryptography.hazmat.primitives import hashes
# both incoming and outgoing.
# Could include dubious clacks or admin dogwhistles
invalidCharacters = (
- '卐', '卍', '࿕', '࿖', '࿗', '࿘'
+ '卐', '卍', '࿕', '࿖', '࿗', '࿘', 'ϟϟ'
)
From 06315138d4a40abc3e56052d3aeb1a4a1cbe87cf Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 30 Aug 2021 19:15:51 +0100
Subject: [PATCH 027/385] More invalid characters
---
utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/utils.py b/utils.py
index cd83eee44..063f48977 100644
--- a/utils.py
+++ b/utils.py
@@ -24,7 +24,7 @@ from cryptography.hazmat.primitives import hashes
# both incoming and outgoing.
# Could include dubious clacks or admin dogwhistles
invalidCharacters = (
- '卐', '卍', '࿕', '࿖', '࿗', '࿘', 'ϟϟ'
+ '卐', '卍', '࿕', '࿖', '࿗', '࿘', 'ϟϟ', '🏳️🌈🚫', '⚡⚡'
)
From ea980b078fc67062b5bd061b8a1cde5cf77e3ebe Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 30 Aug 2021 22:43:23 +0100
Subject: [PATCH 028/385] More descriptive
---
session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/session.py b/session.py
index f0e60db0b..abe851923 100644
--- a/session.py
+++ b/session.py
@@ -116,7 +116,7 @@ def getJson(session, url: str, headers: {}, params: {}, debug: bool,
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- print('WARN: getJson Unauthorized url: ' + url)
+ print("WARN: getJson requires secure fetch. url: " + url)
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
From c5597272794e0edd27a8f898796f9fde123ea4ed Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 30 Aug 2021 22:44:22 +0100
Subject: [PATCH 029/385] Consistent syntax
---
session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/session.py b/session.py
index abe851923..62bee2671 100644
--- a/session.py
+++ b/session.py
@@ -116,7 +116,7 @@ def getJson(session, url: str, headers: {}, params: {}, debug: bool,
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- print("WARN: getJson requires secure fetch. url: " + url)
+ print("WARN: getJson requires secure fetch url: " + url)
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
From 746f58fd7fae611aca4f0089ac73d81ec53d7df5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 30 Aug 2021 23:21:14 +0100
Subject: [PATCH 030/385] Placeholder for signed GET
---
session.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/session.py b/session.py
index 62bee2671..932a15c4a 100644
--- a/session.py
+++ b/session.py
@@ -84,6 +84,14 @@ def urlExists(session, url: str, timeoutSec: int = 3,
return False
+def _getJsonSigned(url: str, sessionHeaders: {},
+ sessionParams: {}, timeoutSec: int) -> {}:
+ """Authorized fetch
+ """
+ # TODO
+ return None
+
+
def getJson(session, url: str, headers: {}, params: {}, debug: bool,
version: str = '1.2.0', httpPrefix: str = 'https',
domain: str = 'testdomain',
@@ -117,6 +125,8 @@ def getJson(session, url: str, headers: {}, params: {}, debug: bool,
if result.status_code != 200:
if result.status_code == 401:
print("WARN: getJson requires secure fetch url: " + url)
+ return _getJsonSigned(url, sessionHeaders,
+ sessionParams, timeoutSec)
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
From 894a8ce3fce9a08e31687557a4129a32dc4aabeb Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 10:10:49 +0100
Subject: [PATCH 031/385] Refactoring for signed GET
---
session.py | 141 +++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 104 insertions(+), 37 deletions(-)
diff --git a/session.py b/session.py
index 932a15c4a..d82241d1b 100644
--- a/session.py
+++ b/session.py
@@ -11,6 +11,7 @@ import os
import requests
from utils import urlPermitted
from utils import isImageFile
+from httpsig import createSignedHeader
import json
from socket import error as SocketError
import errno
@@ -84,49 +85,23 @@ def urlExists(session, url: str, timeoutSec: int = 3,
return False
-def _getJsonSigned(url: str, sessionHeaders: {},
- sessionParams: {}, timeoutSec: int) -> {}:
- """Authorized fetch
+def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
+ sessionParams: {}, timeoutSec: int,
+ privateKeyPem: str, quiet: bool, debug: bool) -> {}:
+ """http GET for json
"""
- # TODO
- return None
-
-
-def getJson(session, url: str, headers: {}, params: {}, debug: bool,
- version: str = '1.2.0', httpPrefix: str = 'https',
- domain: str = 'testdomain',
- timeoutSec: int = 20, quiet: bool = False) -> {}:
- if not isinstance(url, str):
- if debug and not quiet:
- print('url: ' + str(url))
- print('ERROR: getJson failed, url should be a string')
- return None
- sessionParams = {}
- sessionHeaders = {}
- if headers:
- sessionHeaders = headers
- if params:
- sessionParams = params
- sessionHeaders['User-Agent'] = 'Epicyon/' + version
- if domain:
- sessionHeaders['User-Agent'] += \
- '; +' + httpPrefix + '://' + domain + '/'
- if not session:
- if not quiet:
- print('WARN: getJson failed, no session specified for getJson')
- return None
-
- if debug:
- HTTPConnection.debuglevel = 1
-
try:
result = session.get(url, headers=sessionHeaders,
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- print("WARN: getJson requires secure fetch url: " + url)
- return _getJsonSigned(url, sessionHeaders,
- sessionParams, timeoutSec)
+ if not privateKeyPem:
+ print("WARN: getJson requires secure fetch url: " + url)
+ else:
+ return _getJsonSigned(session, url, domainFull,
+ sessionHeaders, sessionParams,
+ timeoutSec, privateKeyPem,
+ quiet, debug)
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
@@ -161,6 +136,98 @@ def getJson(session, url: str, headers: {}, params: {}, debug: bool,
return None
+def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
+ sessionParams: {}, timeoutSec: int,
+ privateKeyPem: str, quiet: bool, debug: bool) -> {}:
+ """Authorized fetch
+ """
+ if not domainFull:
+ if debug:
+ print('No sending domain for signed GET')
+ return None
+ if '://' not in url:
+ return None
+ httpPrefix = url.split('://')[0]
+ toDomainFull = url.split('://')[1]
+ if '/' in toDomainFull:
+ toDomainFull = toDomainFull.split('/')[0]
+
+ if ':' in domainFull:
+ domain = domainFull.split(':')[0]
+ port = domainFull.split(':')[1]
+ else:
+ domain = domainFull
+ if httpPrefix == 'https':
+ port = 443
+ else:
+ port = 80
+
+ if ':' in toDomainFull:
+ toDomain = toDomainFull.split(':')[0]
+ toPort = toDomainFull.split(':')[1]
+ else:
+ toDomain = toDomainFull
+ if httpPrefix == 'https':
+ toPort = 443
+ else:
+ toPort = 80
+ # instance actor
+ nickname = domain
+
+ if debug:
+ print('Signed GET privateKeyPem: ' + privateKeyPem)
+ print('Signed GET nickname: ' + nickname)
+ print('Signed GET domain: ' + domain + ' ' + str(port))
+ print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
+ print('Signed GET url: ' + url)
+ print('Signed GET httpPrefix: ' + httpPrefix)
+ signatureHeaderJson = \
+ createSignedHeader(privateKeyPem, nickname, domain, port,
+ toDomain, toPort,
+ url, httpPrefix, False, '')
+ for key, value in signatureHeaderJson.items():
+ if key == 'Accept' or key == 'User-Agent':
+ continue
+ sessionHeaders[key] = value
+
+ return _getJsonRequest(session, url, domainFull, sessionHeaders,
+ sessionParams, timeoutSec,
+ None, quiet, debug)
+
+
+def getJson(session, url: str, headers: {}, params: {}, debug: bool,
+ version: str = '1.2.0', httpPrefix: str = 'https',
+ domain: str = 'testdomain',
+ timeoutSec: int = 20, quiet: bool = False) -> {}:
+ if not isinstance(url, str):
+ if debug and not quiet:
+ print('url: ' + str(url))
+ print('ERROR: getJson failed, url should be a string')
+ return None
+ sessionParams = {}
+ sessionHeaders = {}
+ if headers:
+ sessionHeaders = headers
+ if params:
+ sessionParams = params
+ sessionHeaders['User-Agent'] = 'Epicyon/' + version
+ if domain:
+ sessionHeaders['User-Agent'] += \
+ '; +' + httpPrefix + '://' + domain + '/'
+ if not session:
+ if not quiet:
+ print('WARN: getJson failed, no session specified for getJson')
+ return None
+
+ if debug:
+ HTTPConnection.debuglevel = 1
+
+ privateKeyPem = 'TODO instance actor private key'
+ return _getJsonRequest(session, url, domain, sessionHeaders,
+ sessionParams, timeoutSec,
+ privateKeyPem, quiet, debug)
+
+
def postJson(httpPrefix: str, domainFull: str,
session, postJsonObject: {}, federationList: [],
inboxUrl: str, headers: {}, timeoutSec: int = 60,
From 6c0a39c64d6df53045f962e5160ebb6db0b116fc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 15:17:11 +0100
Subject: [PATCH 032/385] Support for authorized fetch using instance actor
signing key
---
announce.py | 30 ++++--
availability.py | 9 +-
bookmarks.py | 18 ++--
cache.py | 6 +-
daemon.py | 136 +++++++++++++++++++--------
delete.py | 9 +-
desktop_client.py | 143 ++++++++++++++++++-----------
epicyon.py | 127 ++++++++++++++++++--------
follow.py | 73 +++++++++------
inbox.py | 72 +++++++++------
like.py | 29 ++++--
manualapprove.py | 12 ++-
migrate.py | 18 ++--
outbox.py | 12 ++-
person.py | 7 +-
pgp.py | 31 +++++--
posts.py | 208 +++++++++++++++++++++++++++---------------
schedule.py | 3 +-
session.py | 24 +++--
shares.py | 43 ++++++---
skills.py | 9 +-
socnet.py | 11 ++-
tests.py | 76 +++++++++------
webapp_confirm.py | 5 +-
webapp_frontscreen.py | 12 ++-
webapp_moderation.py | 14 ++-
webapp_post.py | 41 ++++++---
webapp_profile.py | 47 ++++++----
webapp_search.py | 12 ++-
webapp_timeline.py | 61 ++++++++-----
webapp_utils.py | 14 ++-
webfinger.py | 8 +-
32 files changed, 860 insertions(+), 460 deletions(-)
diff --git a/announce.py b/announce.py
index 4e9261e98..8a1fd261b 100644
--- a/announce.py
+++ b/announce.py
@@ -122,7 +122,8 @@ def createAnnounce(session, baseDir: str, federationList: [],
clientToServer: bool,
sendThreads: [], postLog: [],
personCache: {}, cachedWebfingers: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates an announce message
Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
and ccUrl might be a specific person favorited or repeated and the
@@ -179,7 +180,8 @@ def createAnnounce(session, baseDir: str, federationList: [],
announceNickname, announceDomain, announcePort, None,
httpPrefix, True, clientToServer, federationList,
sendThreads, postLog, cachedWebfingers, personCache,
- debug, projectVersion, None, groupAccount)
+ debug, projectVersion, None, groupAccount,
+ signingPrivateKeyPem)
return newAnnounce
@@ -189,7 +191,8 @@ def announcePublic(session, baseDir: str, federationList: [],
objectUrl: str, clientToServer: bool,
sendThreads: [], postLog: [],
personCache: {}, cachedWebfingers: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Makes a public announcement
"""
fromDomain = getFullDomain(domain, port)
@@ -202,7 +205,8 @@ def announcePublic(session, baseDir: str, federationList: [],
objectUrl, True, clientToServer,
sendThreads, postLog,
personCache, cachedWebfingers,
- debug, projectVersion)
+ debug, projectVersion,
+ signingPrivateKeyPem)
def sendAnnounceViaServer(baseDir: str, session,
@@ -210,7 +214,8 @@ def sendAnnounceViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, repeatObjectUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates an announce message via c2s
"""
if not session:
@@ -242,7 +247,8 @@ def sendAnnounceViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: announce webfinger failed for ' + handle)
@@ -257,7 +263,8 @@ def sendAnnounceViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId,
sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
fromNickname, fromDomain,
@@ -298,7 +305,8 @@ def sendUndoAnnounceViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, repeatObjectUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Undo an announce message via c2s
"""
if not session:
@@ -322,7 +330,8 @@ def sendUndoAnnounceViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: undo announce webfinger failed for ' + handle)
@@ -337,7 +346,8 @@ def sendUndoAnnounceViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId,
sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain,
diff --git a/availability.py b/availability.py
index 35ba9164e..a565c6f5e 100644
--- a/availability.py
+++ b/availability.py
@@ -82,7 +82,8 @@ def sendAvailabilityViaServer(baseDir: str, session,
httpPrefix: str,
status: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Sets the availability for a person via c2s
"""
if not session:
@@ -107,7 +108,8 @@ def sendAvailabilityViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: availability webfinger failed for ' + handle)
@@ -122,7 +124,8 @@ def sendAvailabilityViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, nickname,
domain, postToBox, 57262)
diff --git a/bookmarks.py b/bookmarks.py
index 921c057e4..0e45edce8 100644
--- a/bookmarks.py
+++ b/bookmarks.py
@@ -348,7 +348,8 @@ def sendBookmarkViaServer(baseDir: str, session,
domain: str, fromPort: int,
httpPrefix: str, bookmarkUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a bookmark via c2s
"""
if not session:
@@ -377,7 +378,8 @@ def sendBookmarkViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: bookmark webfinger failed for ' + handle)
@@ -391,7 +393,8 @@ def sendBookmarkViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain,
@@ -433,7 +436,8 @@ def sendUndoBookmarkViaServer(baseDir: str, session,
domain: str, fromPort: int,
httpPrefix: str, bookmarkUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Removes a bookmark via c2s
"""
if not session:
@@ -462,7 +466,8 @@ def sendUndoBookmarkViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unbookmark webfinger failed for ' + handle)
@@ -476,7 +481,8 @@ def sendUndoBookmarkViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain,
diff --git a/cache.py b/cache.py
index 9ba0111fb..c6bb925ff 100644
--- a/cache.py
+++ b/cache.py
@@ -139,7 +139,8 @@ def getWebfingerFromCache(handle: str, cachedWebfingers: {}) -> {}:
def getPersonPubKey(baseDir: str, session, personUrl: str,
personCache: {}, debug: bool,
projectVersion: str, httpPrefix: str,
- domain: str, onionDomain: str) -> str:
+ domain: str, onionDomain: str,
+ signingPrivateKeyPem: str) -> str:
if not personUrl:
return None
personUrl = personUrl.replace('#main-key', '')
@@ -165,7 +166,8 @@ def getPersonPubKey(baseDir: str, session, personUrl: str,
'Accept': 'application/activity+json; profile="' + profileStr + '"'
}
personJson = \
- getJson(session, personUrl, asHeader, None, debug,
+ getJson(signingPrivateKeyPem,
+ session, personUrl, asHeader, None, debug,
projectVersion, httpPrefix, personDomain)
if not personJson:
return None
diff --git a/daemon.py b/daemon.py
index ba175e4e5..2fe6bfa80 100644
--- a/daemon.py
+++ b/daemon.py
@@ -623,7 +623,8 @@ class PubServer(BaseHTTPRequestHandler):
getPersonPubKey(self.server.baseDir, self.server.session, keyId,
self.server.personCache, self.server.debug,
__version__, self.server.httpPrefix,
- self.server.domain, self.server.onionDomain)
+ self.server.domain, self.server.onionDomain,
+ self.server.signingPrivateKeyPem)
if not pubKey:
if self.server.debug:
print('DEBUG: Authenticated fetch failed to ' +
@@ -1171,7 +1172,8 @@ class PubServer(BaseHTTPRequestHandler):
city, self.server.systemLanguage,
self.server.sharedItemsFederatedDomains,
self.server.sharedItemFederationTokens,
- self.server.lowBandwidth)
+ self.server.lowBandwidth,
+ self.server.signingPrivateKeyPem)
def _postToOutboxThread(self, messageJson: {}) -> bool:
"""Creates a thread to send a post
@@ -1777,7 +1779,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port,
searchHandle,
self.server.debug,
- self.server.systemLanguage)
+ self.server.systemLanguage,
+ self.server.signingPrivateKeyPem)
else:
msg = \
htmlModerationInfo(self.server.cssCache,
@@ -2420,6 +2423,7 @@ class PubServer(BaseHTTPRequestHandler):
if isModerator(self.server.baseDir, chooserNickname):
if debug:
print('Showing info for ' + optionsActor)
+ signingPrivateKeyPem = self.server.signingPrivateKeyPem
msg = \
htmlAccountInfo(self.server.cssCache,
self.server.translate,
@@ -2430,7 +2434,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port,
optionsActor,
self.server.debug,
- self.server.systemLanguage).encode('utf-8')
+ self.server.systemLanguage,
+ signingPrivateKeyPem).encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
cookie, callingDomain, False)
@@ -2703,7 +2708,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cachedWebfingers,
self.server.personCache,
debug,
- self.server.projectVersion)
+ self.server.projectVersion,
+ self.server.signingPrivateKeyPem)
if callingDomain.endswith('.onion') and onionDomain:
originPathStr = 'http://' + onionDomain + usersPath
elif (callingDomain.endswith('.i2p') and i2pDomain):
@@ -2962,7 +2968,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
if hashtagStr:
msg = hashtagStr.encode('utf-8')
msglen = len(msg)
@@ -3018,7 +3025,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName, 'outbox',
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
if historyStr:
msg = historyStr.encode('utf-8')
msglen = len(msg)
@@ -3054,7 +3062,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName, 'bookmarks',
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
if bookmarksStr:
msg = bookmarksStr.encode('utf-8')
msglen = len(msg)
@@ -3106,7 +3115,8 @@ class PubServer(BaseHTTPRequestHandler):
baseDir, httpPrefix,
actor,
self.server.personCache,
- None, True)
+ None, True,
+ self.server.signingPrivateKeyPem)
profilePathStr += \
'?options=' + actor + ';1;' + avatarUrl
@@ -3126,6 +3136,8 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
+ signingPrivateKeyPem = \
+ self.server.signingPrivateKeyPem
profileStr = \
htmlProfileAfterSearch(self.server.cssCache,
self.server.recentPostsCache,
@@ -3151,7 +3163,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName,
accessKeys,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ signingPrivateKeyPem)
if profileStr:
msg = profileStr.encode('utf-8')
msglen = len(msg)
@@ -6530,7 +6543,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
if hashtagStr:
msg = hashtagStr.encode('utf-8')
msglen = len(msg)
@@ -6685,7 +6699,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache,
self.server.cachedWebfingers,
debug,
- self.server.projectVersion)
+ self.server.projectVersion,
+ self.server.signingPrivateKeyPem)
if announceJson:
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('repeat.png'):
@@ -6846,7 +6861,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cachedWebfingers,
self.server.personCache,
debug,
- self.server.projectVersion)
+ self.server.projectVersion,
+ self.server.signingPrivateKeyPem)
originPathStrAbsolute = \
httpPrefix + '://' + domainFull + originPathStr
if callingDomain.endswith('.onion') and onionDomain:
@@ -7003,7 +7019,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.cachedWebfingers,
self.server.personCache,
debug,
- self.server.projectVersion)
+ self.server.projectVersion,
+ self.server.signingPrivateKeyPem)
originPathStrAbsolute = \
httpPrefix + '://' + domainFull + originPathStr
if callingDomain.endswith('.onion') and onionDomain:
@@ -7473,7 +7490,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
if deleteStr:
deleteStrLen = len(deleteStr)
self._set_headers('text/html', deleteStrLen,
@@ -7683,7 +7701,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7772,7 +7791,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -7848,7 +7868,8 @@ class PubServer(BaseHTTPRequestHandler):
getSpoofedCity(self.server.city,
baseDir, nickname, domain)
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(self.server.signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
iconsAsButtons,
defaultTimeline,
@@ -7951,8 +7972,11 @@ class PubServer(BaseHTTPRequestHandler):
nickname, domain)
sharedItemsFederatedDomains = \
self.server.sharedItemsFederatedDomains
+ signingPrivateKeyPem = \
+ self.server.signingPrivateKeyPem
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
iconsAsButtons,
defaultTimeline,
@@ -8113,7 +8137,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.allowLocalNetworkAccess,
self.server.themeName,
self.server.systemLanguage,
- self.server.maxLikeCount)
+ self.server.maxLikeCount,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8327,7 +8352,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
if GETstartTime:
self._benchmarkGETtimings(GETstartTime, GETtimings,
'show status done',
@@ -8468,7 +8494,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8602,7 +8629,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8735,7 +8763,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -8868,7 +8897,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9010,7 +9040,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9150,7 +9181,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9249,7 +9281,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9330,7 +9363,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9448,7 +9482,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9578,7 +9613,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- self.server.sharedItemsFederatedDomains)
+ self.server.sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9698,7 +9734,8 @@ class PubServer(BaseHTTPRequestHandler):
accessKeys,
self.server.systemLanguage,
self.server.maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._set_headers('text/html', msglen,
@@ -9790,7 +9827,8 @@ class PubServer(BaseHTTPRequestHandler):
city = getSpoofedCity(self.server.city,
baseDir, nickname, domain)
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(self.server.signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
self.server.iconsAsButtons,
self.server.defaultTimeline,
@@ -9906,7 +9944,8 @@ class PubServer(BaseHTTPRequestHandler):
city = getSpoofedCity(self.server.city,
baseDir, nickname, domain)
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(self.server.signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
self.server.iconsAsButtons,
self.server.defaultTimeline,
@@ -10021,7 +10060,8 @@ class PubServer(BaseHTTPRequestHandler):
city = getSpoofedCity(self.server.city,
baseDir, nickname, domain)
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(self.server.signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
self.server.iconsAsButtons,
self.server.defaultTimeline,
@@ -10160,7 +10200,8 @@ class PubServer(BaseHTTPRequestHandler):
city = getSpoofedCity(self.server.city,
baseDir, nickname, domain)
msg = \
- htmlProfile(self.server.rssIconAtTop,
+ htmlProfile(self.server.signingPrivateKeyPem,
+ self.server.rssIconAtTop,
self.server.cssCache,
self.server.iconsAsButtons,
self.server.defaultTimeline,
@@ -13505,7 +13546,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port,
searchHandle,
self.server.debug,
- self.server.systemLanguage)
+ self.server.systemLanguage,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._login_headers('text/html',
@@ -13540,7 +13582,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port,
searchHandle,
self.server.debug,
- self.server.systemLanguage)
+ self.server.systemLanguage,
+ self.server.signingPrivateKeyPem)
msg = msg.encode('utf-8')
msglen = len(msg)
self._login_headers('text/html',
@@ -15717,6 +15760,9 @@ def runDaemon(lowBandwidth: bool,
print('serverAddress: ' + str(serverAddress))
return False
+ # initialize authenticated fetch key
+ httpd.signingPrivateKeyPem = None
+
httpd.showNodeInfoAccounts = showNodeInfoAccounts
httpd.showNodeInfoVersion = showNodeInfoVersion
@@ -16090,7 +16136,8 @@ def runDaemon(lowBandwidth: bool,
verifyAllSignatures,
httpd.themeName,
httpd.systemLanguage,
- httpd.maxLikeCount), daemon=True)
+ httpd.maxLikeCount,
+ httpd.signingPrivateKeyPem), daemon=True)
print('Creating scheduled post thread')
httpd.thrPostSchedule = \
@@ -16121,6 +16168,17 @@ def runDaemon(lowBandwidth: bool,
print('Adding hashtag categories for language ' + httpd.systemLanguage)
loadHashtagCategories(baseDir, httpd.systemLanguage)
+ # signing key used for authorized fetch
+ # this is the instance actor private key
+ instanceActorPrivateKeyFilename = \
+ baseDir + '/keys/private/inbox@' + domain + '.key'
+ if not os.path.isfile(instanceActorPrivateKeyFilename):
+ print('ERROR: no instance actor private key for authorized fetch ' +
+ instanceActorPrivateKeyFilename)
+ return
+ with open(instanceActorPrivateKeyFilename) as fp:
+ httpd.signingPrivateKeyPem = fp.read()
+
if not unitTest:
print('Creating inbox queue watchdog')
httpd.thrWatchdog = \
diff --git a/delete.py b/delete.py
index ef042d8a3..2b7536542 100644
--- a/delete.py
+++ b/delete.py
@@ -30,7 +30,8 @@ def sendDeleteViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, deleteObjectUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a delete request message via c2s
"""
if not session:
@@ -57,7 +58,8 @@ def sendDeleteViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: delete webfinger failed for ' + handle)
@@ -72,7 +74,8 @@ def sendDeleteViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 53036)
diff --git a/desktop_client.py b/desktop_client.py
index 353a682c1..62aa3acc8 100644
--- a/desktop_client.py
+++ b/desktop_client.py
@@ -418,7 +418,8 @@ def _desktopReplyToPost(session, postId: str,
debug: bool, subject: str,
screenreader: str, systemLanguage: str,
espeak, conversationId: str,
- lowBandwidth: bool) -> None:
+ lowBandwidth: bool,
+ signingPrivateKeyPem: str) -> None:
"""Use the desktop client to send a reply to the most recent post
"""
if '://' not in postId:
@@ -463,7 +464,7 @@ def _desktopReplyToPost(session, postId: str,
city = 'London, England'
sayStr = 'Sending reply'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
- if sendPostViaServer(__version__,
+ if sendPostViaServer(signingPrivateKeyPem, __version__,
baseDir, session, nickname, password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
@@ -486,7 +487,8 @@ def _desktopNewPost(session,
cachedWebfingers: {}, personCache: {},
debug: bool,
screenreader: str, systemLanguage: str,
- espeak, lowBandwidth: bool) -> None:
+ espeak, lowBandwidth: bool,
+ signingPrivateKeyPem: str) -> None:
"""Use the desktop client to create a new post
"""
conversationId = None
@@ -527,7 +529,7 @@ def _desktopNewPost(session,
subject = None
sayStr = 'Sending'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
- if sendPostViaServer(__version__,
+ if sendPostViaServer(signingPrivateKeyPem, __version__,
baseDir, session, nickname, password,
domain, port,
None, '#Public', port, ccUrl,
@@ -661,7 +663,8 @@ def _readLocalBoxPost(session, nickname: str, domain: str,
systemLanguage: str,
screenreader: str, espeak,
translate: {}, yourActor: str,
- domainFull: str, personCache: {}) -> {}:
+ domainFull: str, personCache: {},
+ signingPrivateKeyPem: str) -> {}:
"""Reads a post from the given timeline
Returns the post json
"""
@@ -698,7 +701,8 @@ def _readLocalBoxPost(session, nickname: str, domain: str,
allowLocalNetworkAccess,
recentPostsCache, False,
systemLanguage,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
if postJsonObject2:
if hasObjectDict(postJsonObject2):
if postJsonObject2['object'].get('attributedTo') and \
@@ -742,7 +746,7 @@ def _readLocalBoxPost(session, nickname: str, domain: str,
if isPGPEncrypted(content):
sayStr = 'Encrypted message. Please enter your passphrase.'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
- content = pgpDecrypt(domain, content, actor)
+ content = pgpDecrypt(domain, content, actor, signingPrivateKeyPem)
if isPGPEncrypted(content):
sayStr = 'Message could not be decrypted'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
@@ -823,7 +827,7 @@ def _desktopShowProfile(session, nickname: str, domain: str,
systemLanguage: str,
screenreader: str, espeak,
translate: {}, yourActor: str,
- postJsonObject: {}) -> {}:
+ postJsonObject: {}, signingPrivateKeyPem: str) -> {}:
"""Shows the profile of the actor for the given post
Returns the actor json
"""
@@ -854,7 +858,8 @@ def _desktopShowProfile(session, nickname: str, domain: str,
if 'http://' in actor:
isHttp = True
actorJson, asHeader = \
- getActorJson(domain, actor, isHttp, False, False, True)
+ getActorJson(domain, actor, isHttp, False, False, True,
+ signingPrivateKeyPem)
_desktopShowActor(baseDir, actorJson, translate,
systemLanguage, screenreader, espeak)
@@ -868,12 +873,14 @@ def _desktopShowProfileFromHandle(session, nickname: str, domain: str,
systemLanguage: str,
screenreader: str, espeak,
translate: {}, yourActor: str,
- postJsonObject: {}) -> {}:
+ postJsonObject: {},
+ signingPrivateKeyPem: str) -> {}:
"""Shows the profile for a handle
Returns the actor json
"""
actorJson, asHeader = \
- getActorJson(domain, handle, False, False, False, True)
+ getActorJson(domain, handle, False, False, False, True,
+ signingPrivateKeyPem)
_desktopShowActor(baseDir, actorJson, translate,
systemLanguage, screenreader, espeak)
@@ -1112,7 +1119,8 @@ def _desktopNewDM(session, toHandle: str,
cachedWebfingers: {}, personCache: {},
debug: bool,
screenreader: str, systemLanguage: str,
- espeak, lowBandwidth: bool) -> None:
+ espeak, lowBandwidth: bool,
+ signingPrivateKeyPem: str) -> None:
"""Use the desktop client to create a new direct message
which can include multiple destination handles
"""
@@ -1133,7 +1141,8 @@ def _desktopNewDM(session, toHandle: str,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
- espeak, lowBandwidth)
+ espeak, lowBandwidth,
+ signingPrivateKeyPem)
def _desktopNewDMbase(session, toHandle: str,
@@ -1142,7 +1151,8 @@ def _desktopNewDMbase(session, toHandle: str,
cachedWebfingers: {}, personCache: {},
debug: bool,
screenreader: str, systemLanguage: str,
- espeak, lowBandwidth: bool) -> None:
+ espeak, lowBandwidth: bool,
+ signingPrivateKeyPem: str) -> None:
"""Use the desktop client to create a new direct message
"""
conversationId = None
@@ -1201,7 +1211,8 @@ def _desktopNewDMbase(session, toHandle: str,
for after in range(randint(1, 16)):
paddedMessage += ' '
cipherText = \
- pgpEncryptToActor(domain, paddedMessage, toHandle)
+ pgpEncryptToActor(domain, paddedMessage, toHandle,
+ signingPrivateKeyPem)
if not cipherText:
sayStr = \
toHandle + ' has no PGP public key. ' + \
@@ -1222,7 +1233,7 @@ def _desktopNewDMbase(session, toHandle: str,
sayStr = 'Sending'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
- if sendPostViaServer(__version__,
+ if sendPostViaServer(signingPrivateKeyPem, __version__,
baseDir, session, nickname, password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
@@ -1301,6 +1312,9 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
"""Runs the desktop and screen reader client,
which announces new inbox items
"""
+ # TODO: this should probably be retrieved somehow from the server
+ signingPrivateKeyPem = None
+
indent = ' '
if showNewPosts:
indent = ''
@@ -1400,7 +1414,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
cachedWebfingers, personCache,
- debug, False)
+ debug, False,
+ signingPrivateKeyPem)
sayStr = indent + 'PGP public key uploaded'
_sayCommand(sayStr, sayStr, screenreader,
systemLanguage, espeak)
@@ -1410,7 +1425,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
followRequestsJson = \
getFollowRequestsViaServer(baseDir, session,
@@ -1418,14 +1433,16 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, 1,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
if not (currTimeline == 'inbox' and pageNumber == 1):
# monitor the inbox to generate notifications
inboxJson = c2sBoxJson(baseDir, session,
nickname, password,
domain, port, httpPrefix,
- 'inbox', 1, debug)
+ 'inbox', 1, debug,
+ signingPrivateKeyPem)
else:
inboxJson = boxJson
newDMsExist = False
@@ -1502,7 +1519,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
@@ -1519,7 +1536,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
@@ -1537,7 +1554,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
@@ -1556,7 +1573,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
@@ -1583,7 +1600,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname, password,
domain, port, httpPrefix,
currTimeline, pageNumber,
- debug)
+ debug, signingPrivateKeyPem)
if boxJson:
_desktopShowBox(indent, followRequestsJson,
yourActor, currTimeline, boxJson,
@@ -1606,7 +1623,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
pageNumber, postIndex, boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
print('')
sayStr = 'Press Enter to continue...'
sayStr2 = _highlightText(sayStr)
@@ -1628,7 +1646,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
- postJsonObject)
+ postJsonObject,
+ signingPrivateKeyPem)
else:
postIndexStr = '1'
else:
@@ -1643,7 +1662,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
currTimeline, profileHandle,
systemLanguage, screenreader,
espeak, translate, yourActor,
- None)
+ None, signingPrivateKeyPem)
sayStr = 'Press Enter to continue...'
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
@@ -1661,7 +1680,7 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
pageNumber, postIndex, boxJson,
systemLanguage, screenreader,
espeak, translate, yourActor,
- None)
+ None, signingPrivateKeyPem)
sayStr = 'Press Enter to continue...'
sayStr2 = _highlightText(sayStr)
_sayCommand(sayStr2, sayStr,
@@ -1689,7 +1708,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
debug, subject,
screenreader, systemLanguage,
espeak, conversationId,
- lowBandwidth)
+ lowBandwidth,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'post' or commandStr == 'p' or
@@ -1723,7 +1743,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
- espeak, lowBandwidth)
+ espeak, lowBandwidth,
+ signingPrivateKeyPem)
refreshTimeline = True
else:
# public post
@@ -1733,7 +1754,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
cachedWebfingers, personCache,
debug,
screenreader, systemLanguage,
- espeak, lowBandwidth)
+ espeak, lowBandwidth,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif commandStr == 'like' or commandStr.startswith('like '):
@@ -1759,7 +1781,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port, httpPrefix,
postJsonObject['id'],
cachedWebfingers, personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'undo mute' or
@@ -1797,7 +1820,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, postJsonObject['id'],
cachedWebfingers, personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'mute' or
@@ -1826,7 +1850,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, postJsonObject['id'],
cachedWebfingers, personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'undo bookmark' or
@@ -1867,7 +1892,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
postJsonObject['id'],
cachedWebfingers,
personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'bookmark' or
@@ -1896,7 +1922,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port, httpPrefix,
postJsonObject['id'],
cachedWebfingers, personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr.startswith('undo block ') or
@@ -1931,7 +1958,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
blockActor,
cachedWebfingers,
personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif commandStr.startswith('block '):
@@ -1976,7 +2004,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
blockActor,
cachedWebfingers,
personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif commandStr == 'unlike' or commandStr == 'undo like':
@@ -2003,7 +2032,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port, httpPrefix,
postJsonObject['id'],
cachedWebfingers, personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr.startswith('announce') or
@@ -2033,7 +2063,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, postId,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr.startswith('unannounce') or
@@ -2067,7 +2098,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
httpPrefix, postId,
cachedWebfingers,
personCache,
- True, __version__)
+ True, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
elif (commandStr == 'follow requests' or
@@ -2083,7 +2115,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, currPage,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
if followRequestsJson:
if isinstance(followRequestsJson, dict):
_desktopShowFollowRequests(followRequestsJson,
@@ -2102,7 +2135,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, currPage,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
if followingJson:
if isinstance(followingJson, dict):
_desktopShowFollowing(followingJson, translate,
@@ -2122,7 +2156,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
domain, port,
httpPrefix, currPage,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
if followersJson:
if isinstance(followersJson, dict):
_desktopShowFollowing(followersJson, translate,
@@ -2161,7 +2196,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
httpPrefix,
cachedWebfingers,
personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
else:
if followHandle:
sayStr = followHandle + ' is not valid'
@@ -2195,7 +2231,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
httpPrefix,
cachedWebfingers,
personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
else:
sayStr = followHandle + ' is not valid'
_sayCommand(sayStr, sayStr,
@@ -2224,7 +2261,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
cachedWebfingers,
personCache,
debug,
- __version__)
+ __version__,
+ signingPrivateKeyPem)
else:
if approveHandle:
sayStr = approveHandle + ' is not valid'
@@ -2256,7 +2294,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
cachedWebfingers,
personCache,
debug,
- __version__)
+ __version__,
+ signingPrivateKeyPem)
else:
if denyHandle:
sayStr = denyHandle + ' is not valid'
@@ -2341,7 +2380,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
allowLocalNetworkAccess,
recentPostsCache, False,
systemLanguage,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
if postJsonObject2:
postJsonObject = postJsonObject2
if postJsonObject:
@@ -2423,7 +2463,8 @@ def runDesktopClient(baseDir: str, proxyType: str, httpPrefix: str,
postJsonObject['id'],
cachedWebfingers,
personCache,
- False, __version__)
+ False, __version__,
+ signingPrivateKeyPem)
refreshTimeline = True
print('')
diff --git a/epicyon.py b/epicyon.py
index 6748ee1b4..3db9f427d 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -688,9 +688,11 @@ if args.posts:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
getPublicPostsOfPerson(baseDir, nickname, domain, False, True,
proxyType, args.port, httpPrefix, debug,
- __version__, args.language)
+ __version__, args.language,
+ signingPrivateKeyPem)
sys.exit()
if args.postDomains:
@@ -722,13 +724,15 @@ if args.postDomains:
domainList = []
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
domainList = getPublicPostDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
httpPrefix, debug,
__version__,
wordFrequency, domainList,
- args.language)
+ args.language,
+ signingPrivateKeyPem)
for postDomain in domainList:
print(postDomain)
sys.exit()
@@ -765,13 +769,15 @@ if args.postDomainsBlocked:
domainList = []
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
domainList = getPublicPostDomainsBlocked(None,
baseDir, nickname, domain,
proxyType, args.port,
httpPrefix, debug,
__version__,
wordFrequency, domainList,
- args.language)
+ args.language,
+ signingPrivateKeyPem)
for postDomain in domainList:
print(postDomain)
sys.exit()
@@ -806,12 +812,14 @@ if args.checkDomains:
maxBlockedDomains = 0
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
checkDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
httpPrefix, debug,
__version__,
- maxBlockedDomains, False, args.language)
+ maxBlockedDomains, False, args.language,
+ signingPrivateKeyPem)
sys.exit()
if args.socnet:
@@ -825,10 +833,12 @@ if args.socnet:
proxyType = 'tor'
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
dotGraph = instancesGraph(baseDir, args.socnet,
proxyType, args.port,
httpPrefix, debug,
- __version__, args.language)
+ __version__, args.language,
+ signingPrivateKeyPem)
try:
with open('socnet.dot', 'w+') as fp:
fp.write(dotGraph)
@@ -854,9 +864,11 @@ if args.postsraw:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
+ signingPrivateKeyPem = None
getPublicPostsOfPerson(baseDir, nickname, domain, False, False,
proxyType, args.port, httpPrefix, debug,
- __version__, args.language)
+ __version__, args.language,
+ signingPrivateKeyPem)
sys.exit()
if args.json:
@@ -865,8 +877,9 @@ if args.json:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- testJson = getJson(session, args.json, asHeader, None,
- debug, __version__, httpPrefix, None)
+ signingPrivateKeyPem = None
+ testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
+ None, debug, __version__, httpPrefix, None)
pprint(testJson)
sys.exit()
@@ -1075,6 +1088,7 @@ if args.approve:
postLog = []
cachedWebfingers = {}
personCache = {}
+ signingPrivateKeyPem = None
manualApproveFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1082,7 +1096,8 @@ if args.approve:
federationList,
sendThreads, postLog,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
sys.exit()
if args.deny:
@@ -1097,6 +1112,7 @@ if args.deny:
postLog = []
cachedWebfingers = {}
personCache = {}
+ signingPrivateKeyPem = None
manualDenyFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1104,7 +1120,8 @@ if args.deny:
federationList,
sendThreads, postLog,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
sys.exit()
if args.followerspending:
@@ -1184,9 +1201,10 @@ if args.message:
replyTo = args.replyto
followersOnly = False
isArticle = False
+ signingPrivateKeyPem = None
print('Sending post to ' + args.sendto)
- sendPostViaServer(__version__,
+ sendPostViaServer(signingPrivateKeyPem, __version__,
baseDir, session, args.nickname, args.password,
domain, port,
toNickname, toDomain, toPort, ccUrl,
@@ -1216,13 +1234,14 @@ if args.announce:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending announce/repeat of ' + args.announce)
sendAnnounceViaServer(baseDir, session, args.nickname, args.password,
domain, port,
httpPrefix, args.announce,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1255,13 +1274,14 @@ if args.box:
args.port = 80
elif args.gnunet:
proxyType = 'gnunet'
+ signingPrivateKeyPem = None
session = createSession(proxyType)
boxJson = c2sBoxJson(baseDir, session,
args.nickname, args.password,
domain, port, httpPrefix,
args.box, args.pageNumber,
- args.debug)
+ args.debug, signingPrivateKeyPem)
if boxJson:
pprint(boxJson)
else:
@@ -1311,6 +1331,7 @@ if args.itemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending shared item: ' + args.itemName)
sendShareViaServer(baseDir, session,
@@ -1327,7 +1348,8 @@ if args.itemName:
args.duration,
cachedWebfingers, personCache,
debug, __version__,
- args.itemPrice, args.itemCurrency)
+ args.itemPrice, args.itemCurrency,
+ signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1348,6 +1370,7 @@ if args.undoItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo of shared item: ' + args.undoItemName)
sendUndoShareViaServer(baseDir, session,
@@ -1356,7 +1379,7 @@ if args.undoItemName:
httpPrefix,
args.undoItemName,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1405,6 +1428,7 @@ if args.wantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending wanted item: ' + args.wantedItemName)
sendWantedViaServer(baseDir, session,
@@ -1421,7 +1445,8 @@ if args.wantedItemName:
args.duration,
cachedWebfingers, personCache,
debug, __version__,
- args.itemPrice, args.itemCurrency)
+ args.itemPrice, args.itemCurrency,
+ signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1442,6 +1467,7 @@ if args.undoWantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo of wanted item: ' + args.undoWantedItemName)
sendUndoWantedViaServer(baseDir, session,
@@ -1450,7 +1476,7 @@ if args.undoWantedItemName:
httpPrefix,
args.undoWantedItemName,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1471,6 +1497,7 @@ if args.like:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending like of ' + args.like)
sendLikeViaServer(baseDir, session,
@@ -1478,7 +1505,7 @@ if args.like:
domain, port,
httpPrefix, args.like,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1499,6 +1526,7 @@ if args.undolike:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo like of ' + args.undolike)
sendUndoLikeViaServer(baseDir, session,
@@ -1506,7 +1534,8 @@ if args.undolike:
domain, port,
httpPrefix, args.undolike,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__,
+ signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1527,6 +1556,7 @@ if args.bookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending bookmark of ' + args.bookmark)
sendBookmarkViaServer(baseDir, session,
@@ -1534,7 +1564,8 @@ if args.bookmark:
domain, port,
httpPrefix, args.bookmark,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__,
+ signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1555,6 +1586,7 @@ if args.unbookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo bookmark of ' + args.unbookmark)
sendUndoBookmarkViaServer(baseDir, session,
@@ -1562,7 +1594,7 @@ if args.unbookmark:
domain, port,
httpPrefix, args.unbookmark,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1583,6 +1615,7 @@ if args.delete:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending delete request of ' + args.delete)
sendDeleteViaServer(baseDir, session,
@@ -1590,7 +1623,7 @@ if args.delete:
domain, port,
httpPrefix, args.delete,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -1623,6 +1656,7 @@ if args.follow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
+ signingPrivateKeyPem = None
sendFollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1630,7 +1664,7 @@ if args.follow:
followNickname, followDomain, followPort,
httpPrefix,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
for t in range(20):
time.sleep(1)
# TODO some method to know if it worked
@@ -1664,6 +1698,7 @@ if args.unfollow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
+ signingPrivateKeyPem = None
sendUnfollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1671,7 +1706,7 @@ if args.unfollow:
followNickname, followDomain, followPort,
httpPrefix,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
for t in range(20):
time.sleep(1)
# TODO some method to know if it worked
@@ -1694,6 +1729,7 @@ if args.followingList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ signingPrivateKeyPem = None
followingJson = \
getFollowingViaServer(baseDir, session,
@@ -1701,7 +1737,7 @@ if args.followingList:
domain, port,
httpPrefix, args.pageNumber,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
if followingJson:
pprint(followingJson)
sys.exit()
@@ -1722,6 +1758,7 @@ if args.followersList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ signingPrivateKeyPem = None
followersJson = \
getFollowersViaServer(baseDir, session,
@@ -1729,7 +1766,8 @@ if args.followersList:
domain, port,
httpPrefix, args.pageNumber,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__,
+ signingPrivateKeyPem)
if followersJson:
pprint(followersJson)
sys.exit()
@@ -1750,6 +1788,7 @@ if args.followRequestsList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ signingPrivateKeyPem = None
followRequestsJson = \
getFollowRequestsViaServer(baseDir, session,
@@ -1757,7 +1796,7 @@ if args.followRequestsList:
domain, port,
httpPrefix, args.pageNumber,
cachedWebfingers, personCache,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
if followRequestsJson:
pprint(followRequestsJson)
sys.exit()
@@ -1797,9 +1836,10 @@ if args.migrations:
httpPrefix = 'https'
port = 443
session = createSession(proxyType)
+ signingPrivateKeyPem = None
ctr = migrateAccounts(baseDir, session,
httpPrefix, cachedWebfingers,
- True)
+ True, signingPrivateKeyPem)
if ctr == 0:
print('No followed accounts have moved')
else:
@@ -1807,7 +1847,9 @@ if args.migrations:
sys.exit()
if args.actor:
- getActorJson(args.domain, args.actor, args.http, args.gnunet, debug)
+ signingPrivateKeyPem = None
+ getActorJson(args.domain, args.actor, args.http, args.gnunet,
+ debug, False, signingPrivateKeyPem)
sys.exit()
if args.followers:
@@ -1883,9 +1925,11 @@ if args.followers:
nickname = domain
handle = nickname + '@' + domain
+ signingPrivateKeyPem = None
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
- None, __version__, debug, False)
+ None, __version__, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
print('Unable to webfinger ' + handle)
sys.exit()
@@ -1927,9 +1971,10 @@ if args.followers:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
-
+ signingPrivateKeyPem = None
followersList = \
- downloadFollowCollection('followers', session,
+ downloadFollowCollection(signingPrivateKeyPem,
+ 'followers', session,
httpPrefix, personUrl, 1, 3)
if followersList:
for actor in followersList:
@@ -2179,6 +2224,7 @@ if args.skill:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending ' + args.skill + ' skill level ' +
str(args.skillLevelPercent) + ' for ' + nickname)
@@ -2188,7 +2234,7 @@ if args.skill:
httpPrefix,
args.skill, args.skillLevelPercent,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -2209,6 +2255,7 @@ if args.availability:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending availability status of ' + nickname +
' as ' + args.availability)
@@ -2217,7 +2264,7 @@ if args.availability:
httpPrefix,
args.availability,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -2318,13 +2365,14 @@ if args.block:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending block of ' + args.block)
sendBlockViaServer(baseDir, session, nickname, args.password,
domain, port,
httpPrefix, args.block,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -2345,13 +2393,14 @@ if args.mute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending mute of ' + args.mute)
sendMuteViaServer(baseDir, session, nickname, args.password,
domain, port,
httpPrefix, args.mute,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -2372,13 +2421,14 @@ if args.unmute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo mute of ' + args.unmute)
sendUndoMuteViaServer(baseDir, session, nickname, args.password,
domain, port,
httpPrefix, args.unmute,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
@@ -2411,13 +2461,14 @@ if args.unblock:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ signingPrivateKeyPem = None
print('Sending undo block of ' + args.unblock)
sendUndoBlockViaServer(baseDir, session, nickname, args.password,
domain, port,
httpPrefix, args.unblock,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(10):
# TODO detect send success/fail
time.sleep(1)
diff --git a/follow.py b/follow.py
index b81786db3..5182f1ff5 100644
--- a/follow.py
+++ b/follow.py
@@ -631,7 +631,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
cachedWebfingers: {}, personCache: {},
messageJson: {}, federationList: [],
debug: bool, projectVersion: str,
- maxFollowers: int, onionDomain: str) -> bool:
+ maxFollowers: int, onionDomain: str,
+ signingPrivateKeyPem: str) -> bool:
"""Receives a follow request within the POST section of HTTPServer
"""
if not messageJson['type'].startswith('Follow'):
@@ -743,7 +744,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
print('Obtaining the following actor: ' + messageJson['actor'])
if not getPersonPubKey(baseDir, session, messageJson['actor'],
personCache, debug, projectVersion,
- httpPrefix, domainToFollow, onionDomain):
+ httpPrefix, domainToFollow, onionDomain,
+ signingPrivateKeyPem):
if debug:
print('Unable to obtain following actor: ' +
messageJson['actor'])
@@ -779,7 +781,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
print('Obtaining the following actor: ' + messageJson['actor'])
if not getPersonPubKey(baseDir, session, messageJson['actor'],
personCache, debug, projectVersion,
- httpPrefix, domainToFollow, onionDomain):
+ httpPrefix, domainToFollow, onionDomain,
+ signingPrivateKeyPem):
if debug:
print('Unable to obtain following actor: ' +
messageJson['actor'])
@@ -824,7 +827,8 @@ def receiveFollowRequest(session, baseDir: str, httpPrefix: str,
messageJson['actor'], federationList,
messageJson, sendThreads, postLog,
cachedWebfingers, personCache,
- debug, projectVersion, True)
+ debug, projectVersion, True,
+ signingPrivateKeyPem)
def followedAccountAccepts(session, baseDir: str, httpPrefix: str,
@@ -835,7 +839,8 @@ def followedAccountAccepts(session, baseDir: str, httpPrefix: str,
followJson: {}, sendThreads: [], postLog: [],
cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str,
- removeFollowActivity: bool):
+ removeFollowActivity: bool,
+ signingPrivateKeyPem: str):
"""The person receiving a follow request accepts the new follower
and sends back an Accept activity
"""
@@ -884,7 +889,7 @@ def followedAccountAccepts(session, baseDir: str, httpPrefix: str,
federationList,
sendThreads, postLog, cachedWebfingers,
personCache, debug, projectVersion, None,
- groupAccount)
+ groupAccount, signingPrivateKeyPem)
def followedAccountRejects(session, baseDir: str, httpPrefix: str,
@@ -894,7 +899,8 @@ def followedAccountRejects(session, baseDir: str, httpPrefix: str,
federationList: [],
sendThreads: [], postLog: [],
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str):
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str):
"""The person receiving a follow request rejects the new follower
and sends back a Reject activity
"""
@@ -949,7 +955,7 @@ def followedAccountRejects(session, baseDir: str, httpPrefix: str,
federationList,
sendThreads, postLog, cachedWebfingers,
personCache, debug, projectVersion, None,
- groupAccount)
+ groupAccount, signingPrivateKeyPem)
def sendFollowRequest(session, baseDir: str,
@@ -960,7 +966,7 @@ def sendFollowRequest(session, baseDir: str,
clientToServer: bool, federationList: [],
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, debug: bool,
- projectVersion: str) -> {}:
+ projectVersion: str, signingPrivateKeyPem: str) -> {}:
"""Gets the json object for sending a follow request
"""
if not domainPermitted(followDomain, federationList):
@@ -1016,7 +1022,8 @@ def sendFollowRequest(session, baseDir: str,
httpPrefix, True, clientToServer,
federationList,
sendThreads, postLog, cachedWebfingers, personCache,
- debug, projectVersion, None, groupAccount)
+ debug, projectVersion, None, groupAccount,
+ signingPrivateKeyPem)
return newFollowJson
@@ -1028,7 +1035,8 @@ def sendFollowRequestViaServer(baseDir: str, session,
followPort: int,
httpPrefix: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a follow request via c2s
"""
if not session:
@@ -1057,7 +1065,8 @@ def sendFollowRequestViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: follow request webfinger failed for ' + handle)
@@ -1072,7 +1081,8 @@ def sendFollowRequestViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 52025)
@@ -1114,7 +1124,8 @@ def sendUnfollowRequestViaServer(baseDir: str, session,
followPort: int,
httpPrefix: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a unfollow request via c2s
"""
if not session:
@@ -1147,7 +1158,8 @@ def sendUnfollowRequestViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unfollow webfinger failed for ' + handle)
@@ -1162,7 +1174,8 @@ def sendUnfollowRequestViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session,
wfRequest, personCache,
projectVersion, httpPrefix,
fromNickname,
@@ -1205,7 +1218,8 @@ def getFollowingViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, pageNumber: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Gets a page from the following collection as json
"""
if not session:
@@ -1227,9 +1241,8 @@ def getFollowingViaServer(baseDir: str, session,
pageNumber = 1
url = followActor + '/following?page=' + str(pageNumber)
followingJson = \
- getJson(session, url, headers, {}, debug,
- __version__, httpPrefix,
- domain, 10, True)
+ getJson(signingPrivateKeyPem, session, url, headers, {}, debug,
+ __version__, httpPrefix, domain, 10, True)
if not followingJson:
if debug:
print('DEBUG: GET following list failed for c2s to ' + url)
@@ -1246,7 +1259,8 @@ def getFollowersViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, pageNumber: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Gets a page from the followers collection as json
"""
if not session:
@@ -1268,7 +1282,7 @@ def getFollowersViaServer(baseDir: str, session,
pageNumber = 1
url = followActor + '/followers?page=' + str(pageNumber)
followersJson = \
- getJson(session, url, headers, {}, debug,
+ getJson(signingPrivateKeyPem, session, url, headers, {}, debug,
__version__, httpPrefix, domain, 10, True)
if not followersJson:
if debug:
@@ -1286,7 +1300,8 @@ def getFollowRequestsViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, pageNumber: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Gets a page from the follow requests collection as json
"""
if not session:
@@ -1308,7 +1323,7 @@ def getFollowRequestsViaServer(baseDir: str, session,
pageNumber = 1
url = followActor + '/followrequests?page=' + str(pageNumber)
followersJson = \
- getJson(session, url, headers, {}, debug,
+ getJson(signingPrivateKeyPem, session, url, headers, {}, debug,
__version__, httpPrefix, domain, 10, True)
if not followersJson:
if debug:
@@ -1326,7 +1341,8 @@ def approveFollowRequestViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, approveHandle: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> str:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> str:
"""Approves a follow request
This is not exactly via c2s though. It simulates pressing the Approve
button on the web interface
@@ -1348,7 +1364,7 @@ def approveFollowRequestViaServer(baseDir: str, session,
url = actor + '/followapprove=' + approveHandle
approveHtml = \
- getJson(session, url, headers, {}, debug,
+ getJson(signingPrivateKeyPem, session, url, headers, {}, debug,
__version__, httpPrefix, domain, 10, True)
if not approveHtml:
if debug:
@@ -1366,7 +1382,8 @@ def denyFollowRequestViaServer(baseDir: str, session,
domain: str, port: int,
httpPrefix: str, denyHandle: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> str:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> str:
"""Denies a follow request
This is not exactly via c2s though. It simulates pressing the Deny
button on the web interface
@@ -1388,7 +1405,7 @@ def denyFollowRequestViaServer(baseDir: str, session,
url = actor + '/followdeny=' + denyHandle
denyHtml = \
- getJson(session, url, headers, {}, debug,
+ getJson(signingPrivateKeyPem, session, url, headers, {}, debug,
__version__, httpPrefix, domain, 10, True)
if not denyHtml:
if debug:
diff --git a/inbox.py b/inbox.py
index 1ca740a2f..16dd8314f 100644
--- a/inbox.py
+++ b/inbox.py
@@ -170,7 +170,8 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> None:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> None:
"""Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later
"""
@@ -179,7 +180,8 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
if boxname != 'outbox':
boxname = 'inbox'
- individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache, maxRecentPosts,
translate, pageNumber,
baseDir, session, cachedWebfingers,
personCache,
@@ -1271,7 +1273,8 @@ def _receiveAnnounce(recentPostsCache: {},
debug: bool, translate: {},
YTReplacementDomain: str,
allowLocalNetworkAccess: bool,
- themeName: str, systemLanguage: str) -> bool:
+ themeName: str, systemLanguage: str,
+ signingPrivateKeyPem: str) -> bool:
"""Receives an announce activity within the POST section of HTTPServer
"""
if messageJson['type'] != 'Announce':
@@ -1371,7 +1374,8 @@ def _receiveAnnounce(recentPostsCache: {},
allowLocalNetworkAccess,
recentPostsCache, debug,
systemLanguage,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
if not postJsonObject:
notInOnion = True
if onionDomain:
@@ -1422,7 +1426,8 @@ def _receiveAnnounce(recentPostsCache: {},
getPersonPubKey(baseDir, session, lookupActor,
personCache, debug,
__version__, httpPrefix,
- domain, onionDomain)
+ domain, onionDomain,
+ signingPrivateKeyPem)
if pubKey:
if debug:
print('DEBUG: public key obtained for announce: ' +
@@ -1694,7 +1699,8 @@ def _validPostContent(baseDir: str, nickname: str, domain: str,
def _obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
domain: str, onionDomain: str, personCache: {},
- postJsonObject: {}, debug: bool) -> None:
+ postJsonObject: {}, debug: bool,
+ signingPrivateKeyPem: str) -> None:
"""Tries to obtain the actor for the person being replied to
so that their avatar can later be shown
"""
@@ -1725,7 +1731,7 @@ def _obtainAvatarForReplyPost(session, baseDir: str, httpPrefix: str,
getPersonPubKey(baseDir, session, lookupActor,
personCache, debug,
__version__, httpPrefix,
- domain, onionDomain)
+ domain, onionDomain, signingPrivateKeyPem)
if pubKey:
if debug:
print('DEBUG: public key obtained for reply: ' + lookupActor)
@@ -1904,7 +1910,8 @@ def _sendToGroupMembers(session, baseDir: str, handle: str, port: int,
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, debug: bool,
systemLanguage: str,
- onionDomain: str, i2pDomain: str) -> None:
+ onionDomain: str, i2pDomain: str,
+ signingPrivateKeyPem: str) -> None:
"""When a post arrives for a group send it out to the group members
"""
if debug:
@@ -1951,7 +1958,7 @@ def _sendToGroupMembers(session, baseDir: str, handle: str, port: int,
False, False,
sendThreads, postLog,
personCache, cachedWebfingers,
- debug, __version__)
+ debug, __version__, signingPrivateKeyPem)
sendToFollowersThread(session, baseDir, nickname, domain,
onionDomain, i2pDomain, port,
@@ -1960,7 +1967,8 @@ def _sendToGroupMembers(session, baseDir: str, handle: str, port: int,
cachedWebfingers, personCache,
announceJson, debug, __version__,
sharedItemsFederatedDomains,
- sharedItemFederationTokens)
+ sharedItemFederationTokens,
+ signingPrivateKeyPem)
def _inboxUpdateCalendar(baseDir: str, handle: str,
@@ -2074,7 +2082,8 @@ def _bounceDM(senderPostId: str, session, httpPrefix: str,
sendThreads: [], postLog: [],
cachedWebfingers: {}, personCache: {},
translate: {}, debug: bool,
- lastBounceMessage: [], systemLanguage: str) -> bool:
+ lastBounceMessage: [], systemLanguage: str,
+ signingPrivateKeyPem: str) -> bool:
"""Sends a bounce message back to the sending handle
if a DM has been rejected
"""
@@ -2144,7 +2153,8 @@ def _bounceDM(senderPostId: str, session, httpPrefix: str,
senderNickname, senderDomain, senderPort, cc,
httpPrefix, False, False, federationList,
sendThreads, postLog, cachedWebfingers,
- personCache, debug, __version__, None, groupAccount)
+ personCache, debug, __version__, None, groupAccount,
+ signingPrivateKeyPem)
return True
@@ -2157,7 +2167,8 @@ def _isValidDM(baseDir: str, nickname: str, domain: str, port: int,
personCache: {},
translate: {}, debug: bool,
lastBounceMessage: [],
- handle: str, systemLanguage: str) -> bool:
+ handle: str, systemLanguage: str,
+ signingPrivateKeyPem: str) -> bool:
"""Is the given message a valid DM?
"""
if nickname == 'inbox':
@@ -2233,7 +2244,8 @@ def _isValidDM(baseDir: str, nickname: str, domain: str, port: int,
personCache,
translate, debug,
lastBounceMessage,
- systemLanguage)
+ systemLanguage,
+ signingPrivateKeyPem)
return False
# dm index will be updated
@@ -2260,7 +2272,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
lastBounceMessage: [],
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> bool:
+ maxLikeCount: int, signingPrivateKeyPem: str) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
actor = keyId
@@ -2341,7 +2353,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
debug, translate,
YTReplacementDomain,
allowLocalNetworkAccess,
- themeName, systemLanguage):
+ themeName, systemLanguage,
+ signingPrivateKeyPem):
if debug:
print('DEBUG: Announce accepted from ' + actor)
@@ -2466,7 +2479,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
postJsonObject, debug,
__version__,
sharedItemsFederatedDomains,
- sharedItemFederationTokens)
+ sharedItemFederationTokens,
+ signingPrivateKeyPem)
isReplyToMutedPost = False
@@ -2483,7 +2497,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
personCache,
translate, debug,
lastBounceMessage,
- handle, systemLanguage):
+ handle, systemLanguage,
+ signingPrivateKeyPem):
return False
# get the actor being replied to
@@ -2519,7 +2534,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
translate, YTReplacementDomain,
allowLocalNetworkAccess,
recentPostsCache, debug, systemLanguage,
- domainFull, personCache):
+ domainFull, personCache, signingPrivateKeyPem):
# media index will be updated
updateIndexList.append('tlmedia')
if isBlogPost(postJsonObject):
@@ -2529,7 +2544,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
# get the avatar for a reply/announce
_obtainAvatarForReplyPost(session, baseDir,
httpPrefix, domain, onionDomain,
- personCache, postJsonObject, debug)
+ personCache, postJsonObject, debug,
+ signingPrivateKeyPem)
# save the post to file
if saveJson(postJsonObject, destinationFilename):
@@ -2594,7 +2610,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances,
allowLocalNetworkAccess,
themeName, systemLanguage,
- maxLikeCount)
+ maxLikeCount,
+ signingPrivateKeyPem)
if debug:
timeDiff = \
str(int((time.time() - htmlCacheStartTime) *
@@ -2617,7 +2634,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
httpPrefix, federationList, sendThreads,
postLog, cachedWebfingers, personCache,
debug, systemLanguage,
- onionDomain, i2pDomain)
+ onionDomain, i2pDomain,
+ signingPrivateKeyPem)
# if the post wasn't saved
if not os.path.isfile(destinationFilename):
@@ -2857,7 +2875,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
verifyAllSignatures: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> None:
+ maxLikeCount: int, signingPrivateKeyPem: str) -> None:
"""Processes received items and moves them to the appropriate
directories
"""
@@ -3008,7 +3026,7 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
getPersonPubKey(baseDir, session, keyId,
personCache, debug,
projectVersion, httpPrefix,
- domain, onionDomain)
+ domain, onionDomain, signingPrivateKeyPem)
if pubKey:
if debug:
print('DEBUG: public key: ' + str(pubKey))
@@ -3129,7 +3147,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
queueJson['post'],
federationList,
debug, projectVersion,
- maxFollowers, onionDomain):
+ maxFollowers, onionDomain,
+ signingPrivateKeyPem):
if os.path.isfile(queueFilename):
os.remove(queueFilename)
if len(queue) > 0:
@@ -3246,7 +3265,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances,
lastBounceMessage,
themeName, systemLanguage,
- maxLikeCount)
+ maxLikeCount,
+ signingPrivateKeyPem)
if debug:
pprint(queueJson['post'])
print('Queue: Queue post accepted')
diff --git a/like.py b/like.py
index 1eddf571d..b0617c942 100644
--- a/like.py
+++ b/like.py
@@ -62,7 +62,8 @@ def _like(recentPostsCache: {},
clientToServer: bool,
sendThreads: [], postLog: [],
personCache: {}, cachedWebfingers: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a like
actor is the person doing the liking
'to' might be a specific person (actor) whose post was liked
@@ -122,7 +123,8 @@ def _like(recentPostsCache: {},
'https://www.w3.org/ns/activitystreams#Public',
httpPrefix, True, clientToServer, federationList,
sendThreads, postLog, cachedWebfingers, personCache,
- debug, projectVersion, None, groupAccount)
+ debug, projectVersion, None, groupAccount,
+ signingPrivateKeyPem)
return newLikeJson
@@ -135,7 +137,8 @@ def likePost(recentPostsCache: {},
likeStatusNumber: int, clientToServer: bool,
sendThreads: [], postLog: [],
personCache: {}, cachedWebfingers: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Likes a given status post. This is only used by unit tests
"""
likeDomain = getFullDomain(likeDomain, likePort)
@@ -147,7 +150,7 @@ def likePost(recentPostsCache: {},
session, baseDir, federationList, nickname, domain, port,
ccList, httpPrefix, objectUrl, actorLiked, clientToServer,
sendThreads, postLog, personCache, cachedWebfingers,
- debug, projectVersion)
+ debug, projectVersion, signingPrivateKeyPem)
def sendLikeViaServer(baseDir: str, session,
@@ -155,7 +158,8 @@ def sendLikeViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, likeUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a like via c2s
"""
if not session:
@@ -178,7 +182,8 @@ def sendLikeViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: like webfinger failed for ' + handle)
@@ -192,7 +197,8 @@ def sendLikeViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
fromNickname, fromDomain,
@@ -233,7 +239,8 @@ def sendUndoLikeViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, likeUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Undo a like via c2s
"""
if not session:
@@ -260,7 +267,8 @@ def sendUndoLikeViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unlike webfinger failed for ' + handle)
@@ -275,7 +283,8 @@ def sendUndoLikeViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey, fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, fromNickname,
fromDomain, postToBox,
diff --git a/manualapprove.py b/manualapprove.py
index 907cd4b82..97b6ae7b6 100644
--- a/manualapprove.py
+++ b/manualapprove.py
@@ -26,7 +26,8 @@ def manualDenyFollowRequest(session, baseDir: str,
sendThreads: [], postLog: [],
cachedWebfingers: {}, personCache: {},
debug: bool,
- projectVersion: str) -> None:
+ projectVersion: str,
+ signingPrivateKeyPem: str) -> None:
"""Manually deny a follow request
"""
accountsDir = acctDir(baseDir, nickname, domain)
@@ -60,7 +61,8 @@ def manualDenyFollowRequest(session, baseDir: str,
federationList,
sendThreads, postLog,
cachedWebfingers, personCache,
- debug, projectVersion)
+ debug, projectVersion,
+ signingPrivateKeyPem)
print('Follow request from ' + denyHandle + ' was denied.')
@@ -87,7 +89,8 @@ def manualApproveFollowRequest(session, baseDir: str,
sendThreads: [], postLog: [],
cachedWebfingers: {}, personCache: {},
debug: bool,
- projectVersion: str) -> None:
+ projectVersion: str,
+ signingPrivateKeyPem: str) -> None:
"""Manually approve a follow request
"""
handle = nickname + '@' + domain
@@ -176,7 +179,8 @@ def manualApproveFollowRequest(session, baseDir: str,
cachedWebfingers,
personCache,
debug,
- projectVersion, False)
+ projectVersion, False,
+ signingPrivateKeyPem)
updateApprovedFollowers = True
else:
# this isn't the approved follow so it will remain
diff --git a/migrate.py b/migrate.py
index a093cc8fe..4f355f718 100644
--- a/migrate.py
+++ b/migrate.py
@@ -23,7 +23,8 @@ from person import getActorJson
def _moveFollowingHandlesForAccount(baseDir: str, nickname: str, domain: str,
session,
httpPrefix: str, cachedWebfingers: {},
- debug: bool) -> int:
+ debug: bool,
+ signingPrivateKeyPem: str) -> int:
"""Goes through all follows for an account and updates any that have moved
"""
ctr = 0
@@ -38,14 +39,14 @@ def _moveFollowingHandlesForAccount(baseDir: str, nickname: str, domain: str,
_updateMovedHandle(baseDir, nickname, domain,
followHandle, session,
httpPrefix, cachedWebfingers,
- debug)
+ debug, signingPrivateKeyPem)
return ctr
def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
handle: str, session,
httpPrefix: str, cachedWebfingers: {},
- debug: bool) -> int:
+ debug: bool, signingPrivateKeyPem: str) -> int:
"""Check if an account has moved, and if so then alter following.txt
for each account.
Returns 1 if moved, 0 otherwise
@@ -59,7 +60,8 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
handle = handle[1:]
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
- None, __version__, debug, False)
+ None, __version__, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
print('updateMovedHandle unable to webfinger ' + handle)
return ctr
@@ -83,7 +85,8 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
if httpPrefix == 'gnunet':
gnunet = True
personJson = \
- getActorJson(domain, personUrl, httpPrefix, gnunet, debug)
+ getActorJson(domain, personUrl, httpPrefix, gnunet, debug, False,
+ signingPrivateKeyPem)
if not personJson:
return ctr
if not personJson.get('movedTo'):
@@ -172,7 +175,7 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
def migrateAccounts(baseDir: str, session,
httpPrefix: str, cachedWebfingers: {},
- debug: bool) -> int:
+ debug: bool, signingPrivateKeyPem: str) -> int:
"""If followed accounts change then this modifies the
following lists for each account accordingly.
Returns the number of accounts migrated
@@ -188,6 +191,7 @@ def migrateAccounts(baseDir: str, session,
ctr += \
_moveFollowingHandlesForAccount(baseDir, nickname, domain,
session, httpPrefix,
- cachedWebfingers, debug)
+ cachedWebfingers, debug,
+ signingPrivateKeyPem)
break
return ctr
diff --git a/outbox.py b/outbox.py
index b8b6fb98d..1e56d0446 100644
--- a/outbox.py
+++ b/outbox.py
@@ -194,7 +194,8 @@ def postMessageToOutbox(session, translate: {},
city: str, systemLanguage: str,
sharedItemsFederatedDomains: [],
sharedItemFederationTokens: {},
- lowBandwidth: bool) -> bool:
+ lowBandwidth: bool,
+ signingPrivateKeyPem: str) -> bool:
"""post is received by the outbox
Client to server message post
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
@@ -407,7 +408,8 @@ def postMessageToOutbox(session, translate: {},
translate, YTReplacementDomain,
allowLocalNetworkAccess,
recentPostsCache, debug, systemLanguage,
- domainFull, personCache):
+ domainFull, personCache,
+ signingPrivateKeyPem):
inboxUpdateIndex('tlmedia', baseDir,
postToNickname + '@' + domain,
savedFilename, debug)
@@ -468,7 +470,8 @@ def postMessageToOutbox(session, translate: {},
messageJson, debug,
version,
sharedItemsFederatedDomains,
- sharedItemFederationTokens)
+ sharedItemFederationTokens,
+ signingPrivateKeyPem)
followersThreads.append(followersThread)
if debug:
@@ -592,5 +595,6 @@ def postMessageToOutbox(session, translate: {},
messageJson, debug,
version,
sharedItemsFederatedDomains,
- sharedItemFederationTokens)
+ sharedItemFederationTokens,
+ signingPrivateKeyPem)
return True
diff --git a/person.py b/person.py
index 742d62665..0a86abe53 100644
--- a/person.py
+++ b/person.py
@@ -1216,7 +1216,8 @@ def _detectUsersPath(url: str) -> str:
def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
- debug: bool, quiet: bool = False) -> ({}, {}):
+ debug: bool, quiet: bool,
+ signingPrivateKeyPem: str) -> ({}, {}):
"""Returns the actor json
"""
if debug:
@@ -1307,7 +1308,7 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
None, __version__, debug,
- groupAccount)
+ groupAccount, signingPrivateKeyPem)
if not wfRequest:
if not quiet:
print('getActorJson Unable to webfinger ' + handle)
@@ -1361,7 +1362,7 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
'Accept': headerMimeType + '; profile="' + profileStr + '"'
}
personJson = \
- getJson(session, personUrl, asHeader, None,
+ getJson(signingPrivateKeyPem, session, personUrl, asHeader, None,
debug, __version__, httpPrefix, hostDomain, 20, quiet)
if personJson:
if not quiet:
diff --git a/pgp.py b/pgp.py
index 345a0c5f3..c1178e19e 100644
--- a/pgp.py
+++ b/pgp.py
@@ -334,14 +334,16 @@ def _pgpEncrypt(content: str, recipientPubKey: str) -> str:
return encryptResult
-def _getPGPPublicKeyFromActor(domain: str, handle: str,
+def _getPGPPublicKeyFromActor(signingPrivateKeyPem: str,
+ domain: str, handle: str,
actorJson: {} = None) -> str:
"""Searches tags on the actor to see if there is any PGP
public key specified
"""
if not actorJson:
actorJson, asHeader = \
- getActorJson(domain, handle, False, False, False, True)
+ getActorJson(domain, handle, False, False, False, True,
+ signingPrivateKeyPem)
if not actorJson:
return None
if not actorJson.get('attachment'):
@@ -373,18 +375,21 @@ def hasLocalPGPkey() -> bool:
return False
-def pgpEncryptToActor(domain: str, content: str, toHandle: str) -> str:
+def pgpEncryptToActor(domain: str, content: str, toHandle: str,
+ signingPrivateKeyPem: str) -> str:
"""PGP encrypt a message to the given actor or handle
"""
# get the actor and extract the pgp public key from it
- recipientPubKey = _getPGPPublicKeyFromActor(domain, toHandle)
+ recipientPubKey = \
+ _getPGPPublicKeyFromActor(signingPrivateKeyPem, domain, toHandle)
if not recipientPubKey:
return None
# encrypt using the recipient public key
return _pgpEncrypt(content, recipientPubKey)
-def pgpDecrypt(domain: str, content: str, fromHandle: str) -> str:
+def pgpDecrypt(domain: str, content: str, fromHandle: str,
+ signingPrivateKeyPem: str) -> str:
""" Encrypt using your default pgp key to the given recipient
fromHandle can be a handle or actor url
"""
@@ -395,7 +400,9 @@ def pgpDecrypt(domain: str, content: str, fromHandle: str) -> str:
if containsPGPPublicKey(content):
pubKey = extractPGPPublicKey(content)
else:
- pubKey = _getPGPPublicKeyFromActor(domain, content, fromHandle)
+ pubKey = \
+ _getPGPPublicKeyFromActor(signingPrivateKeyPem,
+ domain, content, fromHandle)
if pubKey:
_pgpImportPubKey(pubKey)
@@ -450,7 +457,8 @@ def pgpPublicKeyUpload(baseDir: str, session,
domain: str, port: int,
httpPrefix: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, test: str) -> {}:
+ debug: bool, test: str,
+ signingPrivateKeyPem: str) -> {}:
if debug:
print('pgpPublicKeyUpload')
@@ -482,7 +490,8 @@ def pgpPublicKeyUpload(baseDir: str, session,
print('Getting actor for ' + handle)
actorJson, asHeader = \
- getActorJson(domainFull, handle, False, False, debug, True)
+ getActorJson(domainFull, handle, False, False, debug, True,
+ signingPrivateKeyPem)
if not actorJson:
if debug:
print('No actor returned for ' + handle)
@@ -549,7 +558,8 @@ def pgpPublicKeyUpload(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- domain, __version__, debug, False)
+ domain, __version__, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: pgp actor update webfinger failed for ' +
@@ -566,7 +576,8 @@ def pgpPublicKeyUpload(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest, personCache,
__version__, httpPrefix, nickname,
domain, postToBox, 52025)
diff --git a/posts.py b/posts.py
index aa9ffde00..d91d520e5 100644
--- a/posts.py
+++ b/posts.py
@@ -184,7 +184,8 @@ def getUserUrl(wfRequest: {}, sourceId: int = 0, debug: bool = False) -> str:
return None
-def parseUserFeed(session, feedUrl: str, asHeader: {},
+def parseUserFeed(signingPrivateKeyPem: str,
+ session, feedUrl: str, asHeader: {},
projectVersion: str, httpPrefix: str,
domain: str, debug: bool, depth: int = 0) -> []:
if depth > 10:
@@ -195,7 +196,7 @@ def parseUserFeed(session, feedUrl: str, asHeader: {},
if debug:
print('Getting user feed for ' + feedUrl)
print('User feed header ' + str(asHeader))
- feedJson = getJson(session, feedUrl, asHeader, None,
+ feedJson = getJson(signingPrivateKeyPem, session, feedUrl, asHeader, None,
False, projectVersion, httpPrefix, domain)
if not feedJson:
if debug:
@@ -222,7 +223,8 @@ def parseUserFeed(session, feedUrl: str, asHeader: {},
if isinstance(nextUrl, str):
if '?max_id=0' not in nextUrl:
userFeed = \
- parseUserFeed(session, nextUrl, asHeader,
+ parseUserFeed(signingPrivateKeyPem,
+ session, nextUrl, asHeader,
projectVersion, httpPrefix,
domain, debug, depth + 1)
if userFeed:
@@ -238,7 +240,8 @@ def _getPersonBoxActor(session, baseDir: str, actor: str,
profileStr: str, asHeader: {},
debug: bool, projectVersion: str,
httpPrefix: str, domain: str,
- personCache: {}) -> {}:
+ personCache: {},
+ signingPrivateKeyPem: str) -> {}:
"""Returns the actor json for the given actor url
"""
personJson = \
@@ -250,14 +253,14 @@ def _getPersonBoxActor(session, baseDir: str, actor: str,
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- personJson = getJson(session, actor, asHeader, None,
+ personJson = getJson(signingPrivateKeyPem, session, actor, asHeader, None,
debug, projectVersion, httpPrefix, domain)
if personJson:
return personJson
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- personJson = getJson(session, actor, asHeader, None,
+ personJson = getJson(signingPrivateKeyPem, session, actor, asHeader, None,
debug, projectVersion, httpPrefix, domain)
if personJson:
return personJson
@@ -265,8 +268,8 @@ def _getPersonBoxActor(session, baseDir: str, actor: str,
return None
-def getPersonBox(baseDir: str, session, wfRequest: {},
- personCache: {},
+def getPersonBox(signingPrivateKeyPem: str,
+ baseDir: str, session, wfRequest: {}, personCache: {},
projectVersion: str, httpPrefix: str,
nickname: str, domain: str,
boxName: str = 'inbox',
@@ -304,7 +307,7 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
profileStr, asHeader,
debug, projectVersion,
httpPrefix, domain,
- personCache)
+ personCache, signingPrivateKeyPem)
if not personJson:
return None, None, None, None, None, None, None
@@ -366,7 +369,8 @@ def _getPosts(session, outboxUrl: str, maxPosts: int,
personCache: {}, raw: bool,
simple: bool, debug: bool,
projectVersion: str, httpPrefix: str,
- domain: str, systemLanguage: str) -> {}:
+ domain: str, systemLanguage: str,
+ signingPrivateKeyPem: str) -> {}:
"""Gets public posts from an outbox
"""
if debug:
@@ -387,7 +391,8 @@ def _getPosts(session, outboxUrl: str, maxPosts: int,
print('Returning the raw feed')
result = []
i = 0
- userFeed = parseUserFeed(session, outboxUrl, asHeader,
+ userFeed = parseUserFeed(signingPrivateKeyPem,
+ session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain, debug)
for item in userFeed:
result.append(item)
@@ -399,7 +404,8 @@ def _getPosts(session, outboxUrl: str, maxPosts: int,
if debug:
print('Returning a human readable version of the feed')
- userFeed = parseUserFeed(session, outboxUrl, asHeader,
+ userFeed = parseUserFeed(signingPrivateKeyPem,
+ session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain, debug)
if not userFeed:
return personPosts
@@ -617,7 +623,8 @@ def getPostDomains(session, outboxUrl: str, maxPosts: int,
projectVersion: str, httpPrefix: str,
domain: str,
wordFrequency: {},
- domainList: [], systemLanguage: str) -> []:
+ domainList: [], systemLanguage: str,
+ signingPrivateKeyPem: str) -> []:
"""Returns a list of domains referenced within public posts
"""
if not outboxUrl:
@@ -634,7 +641,8 @@ def getPostDomains(session, outboxUrl: str, maxPosts: int,
postDomains = domainList
i = 0
- userFeed = parseUserFeed(session, outboxUrl, asHeader,
+ userFeed = parseUserFeed(signingPrivateKeyPem,
+ session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain, debug)
for item in userFeed:
i += 1
@@ -672,7 +680,8 @@ def _getPostsForBlockedDomains(baseDir: str,
personCache: {},
debug: bool,
projectVersion: str, httpPrefix: str,
- domain: str) -> {}:
+ domain: str,
+ signingPrivateKeyPem: str) -> {}:
"""Returns a dictionary of posts for blocked domains
"""
if not outboxUrl:
@@ -689,7 +698,8 @@ def _getPostsForBlockedDomains(baseDir: str,
blockedPosts = {}
i = 0
- userFeed = parseUserFeed(session, outboxUrl, asHeader,
+ userFeed = parseUserFeed(signingPrivateKeyPem,
+ session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain, debug)
for item in userFeed:
i += 1
@@ -2025,7 +2035,7 @@ def threadSendPost(session, postJsonStr: str, federationList: [],
tries += 1
-def sendPost(projectVersion: str,
+def sendPost(signingPrivateKeyPem: str, projectVersion: str,
session, baseDir: str, nickname: str, domain: str, port: int,
toNickname: str, toDomain: str, toPort: int, cc: str,
httpPrefix: str, content: str, followersOnly: bool,
@@ -2057,7 +2067,8 @@ def sendPost(projectVersion: str,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
return 1
if not isinstance(wfRequest, dict):
@@ -2075,7 +2086,8 @@ def sendPost(projectVersion: str,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
toPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, postToBox,
@@ -2171,7 +2183,7 @@ def sendPost(projectVersion: str,
return 0
-def sendPostViaServer(projectVersion: str,
+def sendPostViaServer(signingPrivateKeyPem: str, projectVersion: str,
baseDir: str, session, fromNickname: str, password: str,
fromDomain: str, fromPort: int,
toNickname: str, toDomain: str, toPort: int, cc: str,
@@ -2200,7 +2212,8 @@ def sendPostViaServer(projectVersion: str,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomainFull, projectVersion, debug, False)
+ fromDomainFull, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: post webfinger failed for ' + handle)
@@ -2217,7 +2230,8 @@ def sendPostViaServer(projectVersion: str,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
fromNickname,
@@ -2361,7 +2375,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
federationList: [],
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, debug: bool, projectVersion: str,
- sharedItemsToken: str, groupAccount: bool) -> int:
+ sharedItemsToken: str, groupAccount: bool,
+ signingPrivateKeyPem: str) -> int:
"""Sends a signed json object to an inbox/outbox
"""
if debug:
@@ -2397,7 +2412,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- domain, projectVersion, debug, groupAccount)
+ domain, projectVersion, debug, groupAccount,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: webfinger for ' + handle + ' failed')
@@ -2419,7 +2435,8 @@ def sendSignedJson(postJsonObject: {}, session, baseDir: str,
# get the actor inbox/outbox for the To handle
(inboxUrl, pubKeyId, pubKey, toPersonId, sharedInboxUrl, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, postToBox,
@@ -2603,7 +2620,8 @@ def sendToNamedAddresses(session, baseDir: str,
postJsonObject: {}, debug: bool,
projectVersion: str,
sharedItemsFederatedDomains: [],
- sharedItemFederationTokens: {}) -> None:
+ sharedItemFederationTokens: {},
+ signingPrivateKeyPem: str) -> None:
"""sends a post to the specific named addresses in to/cc
"""
if not session:
@@ -2740,11 +2758,12 @@ def sendToNamedAddresses(session, baseDir: str,
federationList,
sendThreads, postLog, cachedWebfingers,
personCache, debug, projectVersion,
- sharedItemsToken, groupAccount)
+ sharedItemsToken, groupAccount,
+ signingPrivateKeyPem)
def _hasSharedInbox(session, httpPrefix: str, domain: str,
- debug: bool) -> bool:
+ debug: bool, signingPrivateKeyPem: str) -> bool:
"""Returns true if the given domain has a shared inbox
This tries the new and the old way of webfingering the shared inbox
"""
@@ -2754,7 +2773,8 @@ def _hasSharedInbox(session, httpPrefix: str, domain: str,
tryHandles.append('inbox@' + domain)
for handle in tryHandles:
wfRequest = webfingerHandle(session, handle, httpPrefix, {},
- None, __version__, debug, False)
+ None, __version__, debug, False,
+ signingPrivateKeyPem)
if wfRequest:
if isinstance(wfRequest, dict):
if not wfRequest.get('errors'):
@@ -2790,7 +2810,8 @@ def sendToFollowers(session, baseDir: str,
postJsonObject: {}, debug: bool,
projectVersion: str,
sharedItemsFederatedDomains: [],
- sharedItemFederationTokens: {}) -> None:
+ sharedItemFederationTokens: {},
+ signingPrivateKeyPem: str) -> None:
"""sends a post to the followers of the given nickname
"""
print('sendToFollowers')
@@ -2848,8 +2869,9 @@ def sendToFollowers(session, baseDir: str,
print('Sending post to followers domain is active: ' +
followerDomainUrl)
- withSharedInbox = _hasSharedInbox(session, httpPrefix,
- followerDomain, debug)
+ withSharedInbox = \
+ _hasSharedInbox(session, httpPrefix, followerDomain, debug,
+ signingPrivateKeyPem)
if debug:
if withSharedInbox:
print(followerDomain + ' has shared inbox')
@@ -2909,7 +2931,8 @@ def sendToFollowers(session, baseDir: str,
federationList,
sendThreads, postLog, cachedWebfingers,
personCache, debug, projectVersion,
- sharedItemsToken, groupAccount)
+ sharedItemsToken, groupAccount,
+ signingPrivateKeyPem)
else:
# send to individual followers without using a shared inbox
for handle in followerHandles:
@@ -2937,7 +2960,8 @@ def sendToFollowers(session, baseDir: str,
federationList,
sendThreads, postLog, cachedWebfingers,
personCache, debug, projectVersion,
- sharedItemsToken, groupAccount)
+ sharedItemsToken, groupAccount,
+ signingPrivateKeyPem)
time.sleep(4)
@@ -2959,7 +2983,8 @@ def sendToFollowersThread(session, baseDir: str,
postJsonObject: {}, debug: bool,
projectVersion: str,
sharedItemsFederatedDomains: [],
- sharedItemFederationTokens: {}):
+ sharedItemFederationTokens: {},
+ signingPrivateKeyPem: str):
"""Returns a thread used to send a post to followers
"""
sendThread = \
@@ -2973,7 +2998,8 @@ def sendToFollowersThread(session, baseDir: str,
postJsonObject.copy(), debug,
projectVersion,
sharedItemsFederatedDomains,
- sharedItemFederationTokens), daemon=True)
+ sharedItemFederationTokens,
+ signingPrivateKeyPem), daemon=True)
try:
sendThread.start()
except SocketError as e:
@@ -3149,7 +3175,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
allowLocalNetworkAccess: bool,
recentPostsCache: {}, debug: bool,
systemLanguage: str,
- domainFull: str, personCache: {}) -> bool:
+ domainFull: str, personCache: {},
+ signingPrivateKeyPem: str) -> bool:
"""Returns true if the given post has attached image media
"""
if postJsonObject['type'] == 'Announce':
@@ -3161,7 +3188,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
allowLocalNetworkAccess,
recentPostsCache, debug,
systemLanguage,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
if postJsonAnnounce:
postJsonObject = postJsonAnnounce
if postJsonObject['type'] != 'Create':
@@ -3724,7 +3752,8 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
raw: bool, simple: bool, proxyType: str,
port: int, httpPrefix: str,
debug: bool, projectVersion: str,
- systemLanguage: str) -> None:
+ systemLanguage: str,
+ signingPrivateKeyPem: str) -> None:
""" This is really just for test purposes
"""
print('Starting new session for getting public posts')
@@ -3745,7 +3774,8 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- domain, projectVersion, debug, groupAccount)
+ domain, projectVersion, debug, groupAccount,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('No webfinger result was returned for ' + handle)
@@ -3759,7 +3789,8 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
print('Getting the outbox for ' + handle)
(personUrl, pubKeyId, pubKey,
personId, shaedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox',
@@ -3773,14 +3804,16 @@ def getPublicPostsOfPerson(baseDir: str, nickname: str, domain: str,
_getPosts(session, personUrl, 30, maxMentions, maxEmoji,
maxAttachments, federationList,
personCache, raw, simple, debug,
- projectVersion, httpPrefix, domain, systemLanguage)
+ projectVersion, httpPrefix, domain, systemLanguage,
+ signingPrivateKeyPem)
def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
proxyType: str, port: int, httpPrefix: str,
debug: bool, projectVersion: str,
wordFrequency: {}, domainList: [],
- systemLanguage: str) -> []:
+ systemLanguage: str,
+ signingPrivateKeyPem: str) -> []:
""" Returns a list of domains referenced within public posts
"""
if not session:
@@ -3795,7 +3828,8 @@ def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
handle = httpPrefix + "://" + domainFull + "/@" + nickname
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
return domainList
if not isinstance(wfRequest, dict):
@@ -3805,7 +3839,8 @@ def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
(personUrl, pubKeyId, pubKey,
personId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox',
@@ -3818,12 +3853,14 @@ def getPublicPostDomains(session, baseDir: str, nickname: str, domain: str,
maxAttachments, federationList,
personCache, debug,
projectVersion, httpPrefix, domain,
- wordFrequency, domainList, systemLanguage)
+ wordFrequency, domainList, systemLanguage,
+ signingPrivateKeyPem)
postDomains.sort()
return postDomains
-def downloadFollowCollection(followType: str,
+def downloadFollowCollection(signingPrivateKeyPem: str,
+ followType: str,
session, httpPrefix: str,
actor: str, pageNumber: int = 1,
noOfPages: int = 1, debug: bool = False) -> []:
@@ -3843,7 +3880,7 @@ def downloadFollowCollection(followType: str,
for pageCtr in range(noOfPages):
url = actor + '/' + followType + '?page=' + str(pageNumber + pageCtr)
followersJson = \
- getJson(session, url, sessionHeaders, None,
+ getJson(signingPrivateKeyPem, session, url, sessionHeaders, None,
debug, __version__, httpPrefix, None)
if followersJson:
if followersJson.get('orderedItems'):
@@ -3860,7 +3897,8 @@ def downloadFollowCollection(followType: str,
def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
proxyType: str, port: int, httpPrefix: str,
debug: bool, projectVersion: str,
- wordFrequency: {}, systemLanguage: str) -> []:
+ wordFrequency: {}, systemLanguage: str,
+ signingPrivateKeyPem: str) -> []:
""" Returns a dict of domains referenced within public posts
"""
if not session:
@@ -3875,7 +3913,8 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
handle = httpPrefix + "://" + domainFull + "/@" + nickname
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
return {}
if not isinstance(wfRequest, dict):
@@ -3885,7 +3924,8 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
(personUrl, pubKeyId, pubKey,
personId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox',
@@ -3899,7 +3939,7 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
maxAttachments, federationList,
personCache, debug,
projectVersion, httpPrefix, domain,
- wordFrequency, [], systemLanguage)
+ wordFrequency, [], systemLanguage, signingPrivateKeyPem)
postDomains.sort()
domainsInfo = {}
for d in postDomains:
@@ -3914,7 +3954,7 @@ def getPublicPostInfo(session, baseDir: str, nickname: str, domain: str,
personCache,
debug,
projectVersion, httpPrefix,
- domain)
+ domain, signingPrivateKeyPem)
for blockedDomain, postUrlList in blockedPosts.items():
domainsInfo[blockedDomain] += postUrlList
@@ -3926,7 +3966,8 @@ def getPublicPostDomainsBlocked(session, baseDir: str,
proxyType: str, port: int, httpPrefix: str,
debug: bool, projectVersion: str,
wordFrequency: {}, domainList: [],
- systemLanguage: str) -> []:
+ systemLanguage: str,
+ signingPrivateKeyPem: str) -> []:
""" Returns a list of domains referenced within public posts which
are globally blocked on this instance
"""
@@ -3934,7 +3975,8 @@ def getPublicPostDomainsBlocked(session, baseDir: str,
getPublicPostDomains(session, baseDir, nickname, domain,
proxyType, port, httpPrefix,
debug, projectVersion,
- wordFrequency, domainList, systemLanguage)
+ wordFrequency, domainList, systemLanguage,
+ signingPrivateKeyPem)
if not postDomains:
return []
@@ -3983,7 +4025,8 @@ def checkDomains(session, baseDir: str,
proxyType: str, port: int, httpPrefix: str,
debug: bool, projectVersion: str,
maxBlockedDomains: int, singleCheck: bool,
- systemLanguage: str) -> None:
+ systemLanguage: str,
+ signingPrivateKeyPem: str) -> None:
"""Checks follower accounts for references to globally blocked domains
"""
wordFrequency = {}
@@ -4012,7 +4055,8 @@ def checkDomains(session, baseDir: str,
proxyType, port, httpPrefix,
debug, projectVersion,
wordFrequency, [],
- systemLanguage)
+ systemLanguage,
+ signingPrivateKeyPem)
if blockedDomains:
if len(blockedDomains) > maxBlockedDomains:
followerWarningStr += handle + '\n'
@@ -4033,7 +4077,8 @@ def checkDomains(session, baseDir: str,
proxyType, port, httpPrefix,
debug, projectVersion,
wordFrequency, [],
- systemLanguage)
+ systemLanguage,
+ signingPrivateKeyPem)
if blockedDomains:
print(handle)
for d in blockedDomains:
@@ -4133,7 +4178,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
allowLocalNetworkAccess: bool,
recentPostsCache: {}, debug: bool,
systemLanguage: str,
- domainFull: str, personCache: {}) -> {}:
+ domainFull: str, personCache: {},
+ signingPrivateKeyPem: str) -> {}:
"""Download the post referenced by an announce
"""
if not postJsonObject.get('object'):
@@ -4206,8 +4252,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
print('Downloading Announce content for ' +
postJsonObject['object'])
announcedJson = \
- getJson(session, postJsonObject['object'], asHeader,
- None, debug, projectVersion, httpPrefix, domain)
+ getJson(signingPrivateKeyPem, session, postJsonObject['object'],
+ asHeader, None, debug, projectVersion, httpPrefix, domain)
if not announcedJson:
return None
@@ -4351,7 +4397,8 @@ def sendBlockViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, blockedUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a block via c2s
"""
if not session:
@@ -4378,7 +4425,8 @@ def sendBlockViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: block webfinger failed for ' + handle)
@@ -4393,7 +4441,8 @@ def sendBlockViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 72652)
@@ -4431,7 +4480,8 @@ def sendMuteViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, mutedUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a mute via c2s
"""
if not session:
@@ -4454,7 +4504,8 @@ def sendMuteViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: mute webfinger failed for ' + handle)
@@ -4469,7 +4520,8 @@ def sendMuteViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 72652)
@@ -4507,7 +4559,8 @@ def sendUndoMuteViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, mutedUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Undoes a mute via c2s
"""
if not session:
@@ -4535,7 +4588,8 @@ def sendUndoMuteViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: undo mute webfinger failed for ' + handle)
@@ -4550,7 +4604,8 @@ def sendUndoMuteViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 72652)
@@ -4589,7 +4644,8 @@ def sendUndoBlockViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, blockedUrl: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a block via c2s
"""
if not session:
@@ -4620,7 +4676,8 @@ def sendUndoBlockViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unblock webfinger failed for ' + handle)
@@ -4635,7 +4692,8 @@ def sendUndoBlockViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox, avatarUrl,
- displayName) = getPersonBox(baseDir, session, wfRequest, personCache,
+ displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest, personCache,
projectVersion, httpPrefix, fromNickname,
fromDomain, postToBox, 53892)
@@ -4697,7 +4755,7 @@ def c2sBoxJson(baseDir: str, session,
domain: str, port: int,
httpPrefix: str,
boxName: str, pageNumber: int,
- debug: bool) -> {}:
+ debug: bool, signingPrivateKeyPem: str) -> {}:
"""C2S Authenticated GET of posts for a timeline
"""
if not session:
@@ -4719,7 +4777,7 @@ def c2sBoxJson(baseDir: str, session,
# GET json
url = actor + '/' + boxName + '?page=' + str(pageNumber)
- boxJson = getJson(session, url, headers, None,
+ boxJson = getJson(signingPrivateKeyPem, session, url, headers, None,
debug, __version__, httpPrefix, None)
if boxJson is not None and debug:
diff --git a/schedule.py b/schedule.py
index 3395b47df..e29d46cff 100644
--- a/schedule.py
+++ b/schedule.py
@@ -115,7 +115,8 @@ def _updatePostSchedule(baseDir: str, handle: str, httpd,
httpd.city, httpd.systemLanguage,
httpd.sharedItemsFederatedDomains,
httpd.sharedItemFederationTokens,
- httpd.lowBandwidth):
+ httpd.lowBandwidth,
+ httpd.signingPrivateKeyPem):
indexLines.remove(line)
os.remove(postFilename)
continue
diff --git a/session.py b/session.py
index d82241d1b..5602d2678 100644
--- a/session.py
+++ b/session.py
@@ -87,7 +87,7 @@ def urlExists(session, url: str, timeoutSec: int = 3,
def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
sessionParams: {}, timeoutSec: int,
- privateKeyPem: str, quiet: bool, debug: bool) -> {}:
+ signingPrivateKeyPem: str, quiet: bool, debug: bool) -> {}:
"""http GET for json
"""
try:
@@ -95,12 +95,12 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- if not privateKeyPem:
+ if not signingPrivateKeyPem:
print("WARN: getJson requires secure fetch url: " + url)
else:
return _getJsonSigned(session, url, domainFull,
sessionHeaders, sessionParams,
- timeoutSec, privateKeyPem,
+ timeoutSec, signingPrivateKeyPem,
quiet, debug)
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
@@ -138,7 +138,7 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
sessionParams: {}, timeoutSec: int,
- privateKeyPem: str, quiet: bool, debug: bool) -> {}:
+ signingPrivateKeyPem: str, quiet: bool, debug: bool) -> {}:
"""Authorized fetch
"""
if not domainFull:
@@ -175,27 +175,26 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
nickname = domain
if debug:
- print('Signed GET privateKeyPem: ' + privateKeyPem)
+ print('Signed GET privateKeyPem: ' + signingPrivateKeyPem)
print('Signed GET nickname: ' + nickname)
print('Signed GET domain: ' + domain + ' ' + str(port))
print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
print('Signed GET url: ' + url)
print('Signed GET httpPrefix: ' + httpPrefix)
signatureHeaderJson = \
- createSignedHeader(privateKeyPem, nickname, domain, port,
- toDomain, toPort,
- url, httpPrefix, False, '')
+ createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
+ toDomain, toPort, url, httpPrefix, False, '')
for key, value in signatureHeaderJson.items():
if key == 'Accept' or key == 'User-Agent':
continue
sessionHeaders[key] = value
return _getJsonRequest(session, url, domainFull, sessionHeaders,
- sessionParams, timeoutSec,
- None, quiet, debug)
+ sessionParams, timeoutSec, None, quiet, debug)
-def getJson(session, url: str, headers: {}, params: {}, debug: bool,
+def getJson(signingPrivateKeyPem: str,
+ session, url: str, headers: {}, params: {}, debug: bool,
version: str = '1.2.0', httpPrefix: str = 'https',
domain: str = 'testdomain',
timeoutSec: int = 20, quiet: bool = False) -> {}:
@@ -222,10 +221,9 @@ def getJson(session, url: str, headers: {}, params: {}, debug: bool,
if debug:
HTTPConnection.debuglevel = 1
- privateKeyPem = 'TODO instance actor private key'
return _getJsonRequest(session, url, domain, sessionHeaders,
sessionParams, timeoutSec,
- privateKeyPem, quiet, debug)
+ signingPrivateKeyPem, quiet, debug)
def postJson(httpPrefix: str, domainFull: str,
diff --git a/shares.py b/shares.py
index e4b7e4045..dd40d583b 100644
--- a/shares.py
+++ b/shares.py
@@ -535,7 +535,8 @@ def sendShareViaServer(baseDir, session,
location: str, duration: str,
cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str,
- itemPrice: str, itemCurrency: str) -> {}:
+ itemPrice: str, itemCurrency: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates an item share via c2s
"""
if not session:
@@ -585,7 +586,8 @@ def sendShareViaServer(baseDir, session,
wfRequest = \
webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: share webfinger failed for ' + handle)
@@ -600,7 +602,8 @@ def sendShareViaServer(baseDir, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, fromNickname,
fromDomain, postToBox,
@@ -652,7 +655,8 @@ def sendUndoShareViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, displayName: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Undoes a share via c2s
"""
if not session:
@@ -685,7 +689,8 @@ def sendUndoShareViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unshare webfinger failed for ' + handle)
@@ -700,7 +705,8 @@ def sendUndoShareViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, fromNickname,
fromDomain, postToBox,
@@ -747,7 +753,8 @@ def sendWantedViaServer(baseDir, session,
location: str, duration: str,
cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str,
- itemMaxPrice: str, itemCurrency: str) -> {}:
+ itemMaxPrice: str, itemCurrency: str,
+ signingPrivateKeyPem: str) -> {}:
"""Creates a wanted item via c2s
"""
if not session:
@@ -797,7 +804,8 @@ def sendWantedViaServer(baseDir, session,
wfRequest = \
webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: share webfinger failed for ' + handle)
@@ -812,7 +820,8 @@ def sendWantedViaServer(baseDir, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, fromNickname,
fromDomain, postToBox,
@@ -864,7 +873,8 @@ def sendUndoWantedViaServer(baseDir: str, session,
fromDomain: str, fromPort: int,
httpPrefix: str, displayName: str,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Undoes a wanted item via c2s
"""
if not session:
@@ -897,7 +907,8 @@ def sendUndoWantedViaServer(baseDir: str, session,
# lookup the inbox for the To handle
wfRequest = \
webfingerHandle(session, handle, httpPrefix, cachedWebfingers,
- fromDomain, projectVersion, debug, False)
+ fromDomain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: unwant webfinger failed for ' + handle)
@@ -912,7 +923,8 @@ def sendUndoWantedViaServer(baseDir: str, session,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, fromNickname,
fromDomain, postToBox,
@@ -953,7 +965,8 @@ def sendUndoWantedViaServer(baseDir: str, session,
def getSharedItemsCatalogViaServer(baseDir, session,
nickname: str, password: str,
domain: str, port: int,
- httpPrefix: str, debug: bool) -> {}:
+ httpPrefix: str, debug: bool,
+ signingPrivateKeyPem: str) -> {}:
"""Returns the shared items catalog via c2s
"""
if not session:
@@ -972,8 +985,8 @@ def getSharedItemsCatalogViaServer(baseDir, session,
url = localActorUrl(httpPrefix, nickname, domainFull) + '/catalog'
if debug:
print('Shared items catalog request to: ' + url)
- catalogJson = getJson(session, url, headers, None, debug,
- __version__, httpPrefix, None)
+ catalogJson = getJson(signingPrivateKeyPem, session, url, headers, None,
+ debug, __version__, httpPrefix, None)
if not catalogJson:
if debug:
print('DEBUG: GET shared items catalog failed for c2s to ' + url)
diff --git a/skills.py b/skills.py
index af8e125a4..7f99acd9a 100644
--- a/skills.py
+++ b/skills.py
@@ -177,7 +177,8 @@ def sendSkillViaServer(baseDir: str, session, nickname: str, password: str,
httpPrefix: str,
skill: str, skillLevelPercent: int,
cachedWebfingers: {}, personCache: {},
- debug: bool, projectVersion: str) -> {}:
+ debug: bool, projectVersion: str,
+ signingPrivateKeyPem: str) -> {}:
"""Sets a skill for a person via c2s
"""
if not session:
@@ -209,7 +210,8 @@ def sendSkillViaServer(baseDir: str, session, nickname: str, password: str,
wfRequest = \
webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
if debug:
print('DEBUG: skill webfinger failed for ' + handle)
@@ -224,7 +226,8 @@ def sendSkillViaServer(baseDir: str, session, nickname: str, password: str,
# get the actor inbox for the To handle
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, nickname, domain,
postToBox, 86725)
diff --git a/socnet.py b/socnet.py
index cd2d4da72..62b357b25 100644
--- a/socnet.py
+++ b/socnet.py
@@ -18,7 +18,7 @@ def instancesGraph(baseDir: str, handles: str,
proxyType: str,
port: int, httpPrefix: str,
debug: bool, projectVersion: str,
- systemLanguage: str) -> str:
+ systemLanguage: str, signingPrivateKeyPem: str) -> str:
""" Returns a dot graph of federating instances
based upon a few sample handles.
The handles argument should contain a comma separated list
@@ -54,7 +54,8 @@ def instancesGraph(baseDir: str, handles: str,
wfRequest = \
webfingerHandle(session, handle, httpPrefix,
cachedWebfingers,
- domain, projectVersion, debug, False)
+ domain, projectVersion, debug, False,
+ signingPrivateKeyPem)
if not wfRequest:
return dotGraphStr + '}\n'
if not isinstance(wfRequest, dict):
@@ -64,7 +65,8 @@ def instancesGraph(baseDir: str, handles: str,
(personUrl, pubKeyId, pubKey,
personId, shaedInbox,
- avatarUrl, displayName) = getPersonBox(baseDir, session, wfRequest,
+ avatarUrl, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox',
@@ -75,7 +77,8 @@ def instancesGraph(baseDir: str, handles: str,
maxAttachments, federationList,
personCache, debug,
projectVersion, httpPrefix, domain,
- wordFrequency, [], systemLanguage)
+ wordFrequency, [], systemLanguage,
+ signingPrivateKeyPem)
postDomains.sort()
for fedDomain in postDomains:
dotLineStr = ' "' + domain + '" -> "' + fedDomain + '";\n'
diff --git a/tests.py b/tests.py
index 323b2bd2d..20bf548f1 100644
--- a/tests.py
+++ b/tests.py
@@ -978,8 +978,9 @@ def testPostMessageBetweenServers():
assert len([name for name in os.listdir(outboxPath)
if os.path.isfile(os.path.join(outboxPath, name))]) == 0
lowBandwidth = False
+ signingPrivateKeyPem = None
sendResult = \
- sendPost(__version__,
+ sendPost(signingPrivateKeyPem, __version__,
sessionAlice, aliceDir, 'alice', aliceDomain, alicePort,
'bob', bobDomain, bobPort, ccUrl, httpPrefix,
'Why is a mouse when it spins? ' +
@@ -1100,7 +1101,7 @@ def testPostMessageBetweenServers():
'alice', aliceDomain, alicePort, [],
statusNumber, False, bobSendThreads, bobPostLog,
bobPersonCache, bobCachedWebfingers,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(20):
if 'likes' in open(outboxPostFilename).read():
@@ -1135,7 +1136,7 @@ def testPostMessageBetweenServers():
objectUrl,
False, bobSendThreads, bobPostLog,
bobPersonCache, bobCachedWebfingers,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
announceMessageArrived = False
outboxMessageArrived = False
for i in range(10):
@@ -1270,6 +1271,7 @@ def testFollowBetweenServers():
aliceCachedWebfingers = {}
alicePostLog = []
bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
+ signingPrivateKeyPem = None
sendResult = \
sendFollowRequest(sessionAlice, aliceDir,
'alice', aliceDomain, alicePort, httpPrefix,
@@ -1278,7 +1280,7 @@ def testFollowBetweenServers():
clientToServer, federationList,
aliceSendThreads, alicePostLog,
aliceCachedWebfingers, alicePersonCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
print('sendResult: ' + str(sendResult))
for t in range(16):
@@ -1315,8 +1317,9 @@ def testFollowBetweenServers():
isArticle = False
city = 'London, England'
lowBandwidth = False
+ signingPrivateKeyPem = None
sendResult = \
- sendPost(__version__,
+ sendPost(signingPrivateKeyPem, __version__,
sessionAlice, aliceDir, 'alice', aliceDomain, alicePort,
'bob', bobDomain, bobPort, ccUrl,
httpPrefix, 'Alice message', followersOnly, saveToFile,
@@ -1466,6 +1469,7 @@ def testSharedItemsFederation():
aliceCachedWebfingers = {}
alicePostLog = []
bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
+ signingPrivateKeyPem = None
sendResult = \
sendFollowRequest(sessionAlice, aliceDir,
'alice', aliceDomain, alicePort, httpPrefix,
@@ -1474,7 +1478,7 @@ def testSharedItemsFederation():
clientToServer, federationList,
aliceSendThreads, alicePostLog,
aliceCachedWebfingers, alicePersonCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
print('sendResult: ' + str(sendResult))
for t in range(16):
@@ -1527,6 +1531,7 @@ def testSharedItemsFederation():
sharedItemDuration = "10 days"
sharedItemPrice = "1.30"
sharedItemCurrency = "EUR"
+ signingPrivateKeyPem = None
shareJson = \
sendShareViaServer(bobDir, sessionBob,
'bob', bobPassword,
@@ -1537,7 +1542,8 @@ def testSharedItemsFederation():
sharedItemLocation, sharedItemDuration,
bobCachedWebfingers, bobPersonCache,
True, __version__,
- sharedItemPrice, sharedItemCurrency)
+ sharedItemPrice, sharedItemCurrency,
+ signingPrivateKeyPem)
assert shareJson
assert isinstance(shareJson, dict)
sharedItemName = 'Epicyon T-shirt'
@@ -1560,7 +1566,8 @@ def testSharedItemsFederation():
sharedItemLocation, sharedItemDuration,
bobCachedWebfingers, bobPersonCache,
True, __version__,
- sharedItemPrice, sharedItemCurrency)
+ sharedItemPrice, sharedItemCurrency,
+ signingPrivateKeyPem)
assert shareJson
assert isinstance(shareJson, dict)
sharedItemName = 'Soldering iron'
@@ -1583,7 +1590,8 @@ def testSharedItemsFederation():
sharedItemLocation, sharedItemDuration,
bobCachedWebfingers, bobPersonCache,
True, __version__,
- sharedItemPrice, sharedItemCurrency)
+ sharedItemPrice, sharedItemCurrency,
+ signingPrivateKeyPem)
assert shareJson
assert isinstance(shareJson, dict)
@@ -1605,9 +1613,11 @@ def testSharedItemsFederation():
print('\n\n*********************************************************')
print('Bob can read the shared items catalog on his own instance')
+ signingPrivateKeyPem = None
catalogJson = \
getSharedItemsCatalogViaServer(bobDir, sessionBob, 'bob', bobPassword,
- bobDomain, bobPort, httpPrefix, True)
+ bobDomain, bobPort, httpPrefix, True,
+ signingPrivateKeyPem)
assert catalogJson
pprint(catalogJson)
assert 'DFC:supplies' in catalogJson
@@ -1633,8 +1643,9 @@ def testSharedItemsFederation():
isArticle = False
city = 'London, England'
lowBandwidth = False
+ signingPrivateKeyPem = None
sendResult = \
- sendPost(__version__,
+ sendPost(signingPrivateKeyPem, __version__,
sessionAlice, aliceDir, 'alice', aliceDomain, alicePort,
'bob', bobDomain, bobPort, ccUrl,
httpPrefix, 'Alice message', followersOnly, saveToFile,
@@ -1697,7 +1708,9 @@ def testSharedItemsFederation():
'Accept': 'application/json'
}
url = httpPrefix + '://' + bobAddress + '/catalog'
- catalogJson = getJson(sessionAlice, url, headers, None, True)
+ signingPrivateKeyPem = None
+ catalogJson = getJson(signingPrivateKeyPem, sessionAlice, url, headers,
+ None, True)
assert catalogJson
pprint(catalogJson)
assert 'DFC:supplies' in catalogJson
@@ -1836,8 +1849,9 @@ def testGroupFollow():
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- outboxJson = getJson(session, aliceOutbox, asHeader, None,
- True, __version__, 'http', None)
+ signingPrivateKeyPem = None
+ outboxJson = getJson(signingPrivateKeyPem, session, aliceOutbox, asHeader,
+ None, True, __version__, 'http', None)
assert outboxJson
pprint(outboxJson)
assert outboxJson['type'] == 'OrderedCollection'
@@ -1847,8 +1861,8 @@ def testGroupFollow():
print('Alice outbox totalItems: ' + str(outboxJson['totalItems']))
assert outboxJson['totalItems'] == 3
- outboxJson = getJson(session, firstPage, asHeader, None,
- True, __version__, 'http', None)
+ outboxJson = getJson(signingPrivateKeyPem, session, firstPage, asHeader,
+ None, True, __version__, 'http', None)
assert outboxJson
pprint(outboxJson)
assert 'orderedItems' in outboxJson
@@ -1879,6 +1893,7 @@ def testGroupFollow():
alicePostLog = []
# aliceActor = httpPrefix + '://' + aliceAddress + '/users/alice'
testgroupActor = httpPrefix + '://' + testgroupAddress + '/users/testgroup'
+ signingPrivateKeyPem = None
sendResult = \
sendFollowRequest(sessionAlice, aliceDir,
'alice', aliceDomain, alicePort, httpPrefix,
@@ -1887,7 +1902,7 @@ def testGroupFollow():
clientToServer, federationList,
aliceSendThreads, alicePostLog,
aliceCachedWebfingers, alicePersonCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
print('sendResult: ' + str(sendResult))
aliceFollowingFilename = \
@@ -1954,6 +1969,7 @@ def testGroupFollow():
bobPostLog = []
# bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
testgroupActor = httpPrefix + '://' + testgroupAddress + '/users/testgroup'
+ signingPrivateKeyPem = None
sendResult = \
sendFollowRequest(sessionBob, bobDir,
'bob', bobDomain, bobPort, httpPrefix,
@@ -1962,7 +1978,7 @@ def testGroupFollow():
clientToServer, federationList,
bobSendThreads, bobPostLog,
bobCachedWebfingers, bobPersonCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
print('sendResult: ' + str(sendResult))
bobFollowingFilename = \
@@ -2025,8 +2041,9 @@ def testGroupFollow():
isArticle = False
city = 'London, England'
lowBandwidth = False
+ signingPrivateKeyPem = None
sendResult = \
- sendPost(__version__,
+ sendPost(signingPrivateKeyPem, __version__,
sessionAlice, aliceDir, 'alice', aliceDomain, alicePort,
'testgroup', testgroupDomain, testgroupPort, ccUrl,
httpPrefix, "Alice group message", followersOnly,
@@ -2553,8 +2570,9 @@ def testClientToServer():
assert len([name for name in os.listdir(bobOutboxPath)
if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 0
print('EVENT: all inboxes and outboxes are empty')
+ signingPrivateKeyPem = None
sendResult = \
- sendPostViaServer(__version__,
+ sendPostViaServer(signingPrivateKeyPem, __version__,
aliceDir, sessionAlice, 'alice', password,
aliceDomain, alicePort,
'bob', bobDomain, bobPort, None,
@@ -2619,13 +2637,14 @@ def testClientToServer():
aliceDomain, alicePort)
print('\n\nAlice follows Bob')
+ signingPrivateKeyPem = None
sendFollowRequestViaServer(aliceDir, sessionAlice,
'alice', password,
aliceDomain, alicePort,
'bob', bobDomain, bobPort,
httpPrefix,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
alicePetnamesFilename = aliceDir + '/accounts/' + \
'alice@' + aliceDomain + '/petnames.txt'
aliceFollowingFilename = \
@@ -2667,7 +2686,7 @@ def testClientToServer():
'alice', aliceDomain, alicePort,
httpPrefix,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for t in range(10):
if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain +
'/followers.txt'):
@@ -2722,7 +2741,7 @@ def testClientToServer():
bobDomain, bobPort,
httpPrefix, outboxPostId,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(20):
if os.path.isdir(outboxPath) and os.path.isdir(inboxPath):
if len([name for name in os.listdir(outboxPath)
@@ -2753,11 +2772,13 @@ def testClientToServer():
showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
showTestBoxes('bob', bobInboxPath, bobOutboxPath)
print('\n\nEVENT: Bob repeats the post')
+ signingPrivateKeyPem = None
sendAnnounceViaServer(bobDir, sessionBob, 'bob', password,
bobDomain, bobPort,
httpPrefix, outboxPostId,
cachedWebfingers,
- personCache, True, __version__)
+ personCache, True, __version__,
+ signingPrivateKeyPem)
for i in range(20):
if os.path.isdir(outboxPath) and os.path.isdir(inboxPath):
if len([name for name in os.listdir(outboxPath)
@@ -2788,7 +2809,7 @@ def testClientToServer():
aliceDomain, alicePort,
httpPrefix, outboxPostId,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for i in range(30):
if os.path.isdir(inboxPath):
test = len([name for name in os.listdir(inboxPath)
@@ -2813,7 +2834,7 @@ def testClientToServer():
'bob', bobDomain, bobPort,
httpPrefix,
cachedWebfingers, personCache,
- True, __version__)
+ True, __version__, signingPrivateKeyPem)
for t in range(10):
if 'alice@' + aliceDomain + ':' + str(alicePort) not in \
open(bobFollowersFilename).read():
@@ -4704,13 +4725,14 @@ def testUpdateActor():
'fnaZ2Wi050483Sj2RmQRpb99Dod7rVZTDtCqXk0J\n' + \
'=gv5G\n' + \
'-----END PGP PUBLIC KEY BLOCK-----'
+ signingPrivateKeyPem = None
actorUpdate = \
pgpPublicKeyUpload(aliceDir, sessionAlice,
'alice', password,
aliceDomain, alicePort,
httpPrefix,
cachedWebfingers, personCache,
- True, pubKey)
+ True, pubKey, signingPrivateKeyPem)
print('actor update result: ' + str(actorUpdate))
assert actorUpdate
diff --git a/webapp_confirm.py b/webapp_confirm.py
index 982c8f921..2bc63a939 100644
--- a/webapp_confirm.py
+++ b/webapp_confirm.py
@@ -34,7 +34,7 @@ def htmlConfirmDelete(cssCache: {},
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int, signingPrivateKeyPem: str) -> str:
"""Shows a screen asking to confirm the deletion of a post
"""
if '/statuses/' not in messageId:
@@ -66,7 +66,8 @@ def htmlConfirmDelete(cssCache: {},
getConfigParam(baseDir, 'instanceTitle')
deletePostStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle)
deletePostStr += \
- individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache, maxRecentPosts,
translate, pageNumber,
baseDir, session, cachedWebfingers, personCache,
nickname, domain, port, postJsonObject,
diff --git a/webapp_frontscreen.py b/webapp_frontscreen.py
index afcf3cb11..3569d76a2 100644
--- a/webapp_frontscreen.py
+++ b/webapp_frontscreen.py
@@ -33,7 +33,8 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Shows posts on the front screen of a news instance
These should only be public blog posts from the features timeline
which is the blog timeline of the news actor
@@ -61,7 +62,8 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
for item in outboxFeed['orderedItems']:
if item['type'] == 'Create':
postStr = \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session,
@@ -86,7 +88,8 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
return profileStr
-def htmlFrontScreen(rssIconAtTop: bool,
+def htmlFrontScreen(signingPrivateKeyPem: str,
+ rssIconAtTop: bool,
cssCache: {}, iconsAsButtons: bool,
defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
@@ -173,7 +176,8 @@ def htmlFrontScreen(rssIconAtTop: bool,
peertubeInstances,
allowLocalNetworkAccess,
theme, systemLanguage,
- maxLikeCount) + licenseStr
+ maxLikeCount,
+ signingPrivateKeyPem) + licenseStr
# Footer which is only used for system accounts
profileFooterStr = ' \n'
diff --git a/webapp_moderation.py b/webapp_moderation.py
index 31823d8d7..16d0f684c 100644
--- a/webapp_moderation.py
+++ b/webapp_moderation.py
@@ -50,7 +50,8 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the moderation feed as html
This is what you see when selecting the "mod" timeline
"""
@@ -67,14 +68,15 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
authorized, moderationActionStr, theme,
peertubeInstances, allowLocalNetworkAccess,
textModeBanner, accessKeys, systemLanguage,
- maxLikeCount, sharedItemsFederatedDomains)
+ maxLikeCount, sharedItemsFederatedDomains,
+ signingPrivateKeyPem)
def htmlAccountInfo(cssCache: {}, translate: {},
baseDir: str, httpPrefix: str,
nickname: str, domain: str, port: int,
searchHandle: str, debug: bool,
- systemLanguage: str) -> str:
+ systemLanguage: str, signingPrivateKeyPem: str) -> str:
"""Shows which domains a search handle interacts with.
This screen is shown if a moderator enters a handle and selects info
on the moderation screen
@@ -116,11 +118,13 @@ def htmlAccountInfo(cssCache: {}, translate: {},
baseDir, searchNickname, searchDomain,
proxyType, searchPort,
httpPrefix, debug,
- __version__, wordFrequency, systemLanguage)
+ __version__, wordFrequency, systemLanguage,
+ signingPrivateKeyPem)
# get a list of any blocked followers
followersList = \
- downloadFollowCollection('followers', session,
+ downloadFollowCollection(signingPrivateKeyPem,
+ 'followers', session,
httpPrefix, searchActor, 1, 5)
blockedFollowers = []
for followerActor in followersList:
diff --git a/webapp_post.py b/webapp_post.py
index 5becbd2ef..e17300b0c 100644
--- a/webapp_post.py
+++ b/webapp_post.py
@@ -188,7 +188,8 @@ def _getPostFromRecentCache(session,
postStartTime,
pageNumber: int,
recentPostsCache: {},
- maxRecentPosts: int) -> str:
+ maxRecentPosts: int,
+ signingPrivateKeyPem: str) -> str:
"""Attempts to get the html post from the recent posts cache in memory
"""
if boxName == 'tlmedia':
@@ -213,7 +214,8 @@ def _getPostFromRecentCache(session,
_logPostTiming(enableTimingLog, postStartTime, '2.1')
- updateAvatarImageCache(session, baseDir, httpPrefix,
+ updateAvatarImageCache(signingPrivateKeyPem,
+ session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
@@ -1095,7 +1097,8 @@ def _getFooterWithIcons(showIcons: bool,
return footerStr
-def individualPostAsHtml(allowDownloads: bool,
+def individualPostAsHtml(signingPrivateKeyPem: str,
+ allowDownloads: bool,
recentPostsCache: {}, maxRecentPosts: int,
translate: {},
pageNumber: int, baseDir: str,
@@ -1169,7 +1172,8 @@ def individualPostAsHtml(allowDownloads: bool,
postStartTime,
pageNumber,
recentPostsCache,
- maxRecentPosts)
+ maxRecentPosts,
+ signingPrivateKeyPem)
if postHtml:
return postHtml
@@ -1179,7 +1183,8 @@ def individualPostAsHtml(allowDownloads: bool,
getAvatarImageUrl(session,
baseDir, httpPrefix,
postActor, personCache,
- avatarUrl, allowDownloads)
+ avatarUrl, allowDownloads,
+ signingPrivateKeyPem)
_logPostTiming(enableTimingLog, postStartTime, '5')
@@ -1193,14 +1198,16 @@ def individualPostAsHtml(allowDownloads: bool,
postActorWf = \
webfingerHandle(session, postActorHandle, httpPrefix,
cachedWebfingers,
- domain, __version__, False, False)
+ domain, __version__, False, False,
+ signingPrivateKeyPem)
avatarUrl2 = None
displayName = None
if postActorWf:
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl2, displayName) = getPersonBox(baseDir, session,
+ avatarUrl2, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session,
postActorWf,
personCache,
projectVersion,
@@ -1261,7 +1268,8 @@ def individualPostAsHtml(allowDownloads: bool,
allowLocalNetworkAccess,
recentPostsCache, False,
systemLanguage,
- domainFull, personCache)
+ domainFull, personCache,
+ signingPrivateKeyPem)
if not postJsonAnnounce:
# if the announce could not be downloaded then mark it as rejected
rejectPostId(baseDir, nickname, domain, postJsonObject['id'],
@@ -1691,7 +1699,7 @@ def htmlIndividualPost(cssCache: {},
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int, signingPrivateKeyPem: str) -> str:
"""Show an individual post as html
"""
postStr = ''
@@ -1723,7 +1731,8 @@ def htmlIndividualPost(cssCache: {},
postStr += followStr + '
\n'
postStr += \
- individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache, maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers, personCache,
nickname, domain, port, postJsonObject,
@@ -1748,7 +1757,8 @@ def htmlIndividualPost(cssCache: {},
postJsonObject = loadJson(postFilename)
if postJsonObject:
postStr = \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
@@ -1781,7 +1791,8 @@ def htmlIndividualPost(cssCache: {},
# add items to the html output
for item in repliesJson['orderedItems']:
postStr += \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
@@ -1818,14 +1829,16 @@ def htmlPostReplies(cssCache: {},
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Show the replies to an individual post as html
"""
repliesStr = ''
if repliesJson.get('orderedItems'):
for item in repliesJson['orderedItems']:
repliesStr += \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
diff --git a/webapp_profile.py b/webapp_profile.py
index 2d5005255..ac87c99c2 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -84,7 +84,8 @@ def htmlProfileAfterSearch(cssCache: {},
themeName: str,
accessKeys: {},
systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Show a profile page after a search for a fediverse address
"""
http = False
@@ -94,7 +95,8 @@ def htmlProfileAfterSearch(cssCache: {},
elif httpPrefix == 'gnunet':
gnunet = True
profileJson, asHeader = \
- getActorJson(domain, profileHandle, http, gnunet, debug, False)
+ getActorJson(domain, profileHandle, http, gnunet, debug, False,
+ signingPrivateKeyPem)
if not profileJson:
return None
@@ -251,7 +253,8 @@ def htmlProfileAfterSearch(cssCache: {},
'\n'
userFeed = \
- parseUserFeed(session, outboxUrl, asHeader, projectVersion,
+ parseUserFeed(signingPrivateKeyPem,
+ session, outboxUrl, asHeader, projectVersion,
httpPrefix, domain, debug)
if userFeed:
i = 0
@@ -268,7 +271,8 @@ def htmlProfileAfterSearch(cssCache: {},
continue
profileStr += \
- individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache, maxRecentPosts,
translate, None, baseDir,
session, cachedWebfingers, personCache,
nickname, domain, port,
@@ -466,7 +470,8 @@ def _getProfileHeaderAfterSearch(baseDir: str,
return htmlStr
-def htmlProfile(rssIconAtTop: bool,
+def htmlProfile(signingPrivateKeyPem: str,
+ rssIconAtTop: bool,
cssCache: {}, iconsAsButtons: bool,
defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
@@ -491,7 +496,8 @@ def htmlProfile(rssIconAtTop: bool,
if not nickname:
return ""
if isSystemAccount(nickname):
- return htmlFrontScreen(rssIconAtTop,
+ return htmlFrontScreen(signingPrivateKeyPem,
+ rssIconAtTop,
cssCache, iconsAsButtons,
defaultTimeline,
recentPostsCache, maxRecentPosts,
@@ -851,7 +857,8 @@ def htmlProfile(rssIconAtTop: bool,
peertubeInstances,
allowLocalNetworkAccess,
theme, systemLanguage,
- maxLikeCount) + licenseStr
+ maxLikeCount,
+ signingPrivateKeyPem) + licenseStr
elif selected == 'following':
profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix,
@@ -860,7 +867,7 @@ def htmlProfile(rssIconAtTop: bool,
cachedWebfingers, personCache, extraJson,
projectVersion, ["unfollow"], selected,
usersPath, pageNumber, maxItemsPerPage,
- dormantMonths, debug)
+ dormantMonths, debug, signingPrivateKeyPem)
elif selected == 'followers':
profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix,
@@ -869,7 +876,8 @@ def htmlProfile(rssIconAtTop: bool,
cachedWebfingers, personCache, extraJson,
projectVersion, ["block"],
selected, usersPath, pageNumber,
- maxItemsPerPage, dormantMonths, debug)
+ maxItemsPerPage, dormantMonths, debug,
+ signingPrivateKeyPem)
elif selected == 'roles':
profileStr += \
_htmlProfileRoles(translate, nickname, domainFull,
@@ -911,7 +919,8 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Shows posts on the profile screen
These should only be public posts
"""
@@ -939,7 +948,8 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
for item in outboxFeed['orderedItems']:
if item['type'] == 'Create':
postStr = \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
@@ -972,7 +982,8 @@ def _htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
feedName: str, actor: str,
pageNumber: int,
maxItemsPerPage: int,
- dormantMonths: int, debug: bool) -> str:
+ dormantMonths: int, debug: bool,
+ signingPrivateKeyPem: str) -> str:
"""Shows following on the profile screen
"""
profileStr = ''
@@ -999,7 +1010,8 @@ def _htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
dormantMonths)
profileStr += \
- _individualFollowAsHtml(translate, baseDir, session,
+ _individualFollowAsHtml(signingPrivateKeyPem,
+ translate, baseDir, session,
cachedWebfingers, personCache,
domain, followingActor,
authorized, nickname,
@@ -2068,7 +2080,8 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
return editProfileForm
-def _individualFollowAsHtml(translate: {},
+def _individualFollowAsHtml(signingPrivateKeyPem: str,
+ translate: {},
baseDir: str, session,
cachedWebfingers: {},
personCache: {}, domain: str,
@@ -2095,11 +2108,13 @@ def _individualFollowAsHtml(translate: {},
followUrlWf = \
webfingerHandle(session, followUrlHandle, httpPrefix,
cachedWebfingers,
- domain, __version__, debug, False)
+ domain, __version__, debug, False,
+ signingPrivateKeyPem)
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
- avatarUrl2, displayName) = getPersonBox(baseDir, session,
+ avatarUrl2, displayName) = getPersonBox(signingPrivateKeyPem,
+ baseDir, session,
followUrlWf,
personCache, projectVersion,
httpPrefix, followUrlNickname,
diff --git a/webapp_search.py b/webapp_search.py
index 2ed8d497f..9ec76b1af 100644
--- a/webapp_search.py
+++ b/webapp_search.py
@@ -575,7 +575,8 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
allowLocalNetworkAccess: bool,
themeName: str, boxName: str,
systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Show a page containing search results for your post history
"""
if historysearch.startswith("'"):
@@ -641,7 +642,8 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
showIndividualPostIcons = True
allowDeletion = False
postStr = \
- individualPostAsHtml(True, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ True, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
@@ -680,7 +682,8 @@ def htmlHashtagSearch(cssCache: {},
peertubeInstances: [],
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int) -> str:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> str:
"""Show a page containing search results for a hashtag
or after selecting a hashtag from the swarm
"""
@@ -816,7 +819,8 @@ def htmlHashtagSearch(cssCache: {},
avatarUrl = None
showAvatarOptions = True
postStr = \
- individualPostAsHtml(allowDownloads, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ allowDownloads, recentPostsCache,
maxRecentPosts,
translate, None,
baseDir, session, cachedWebfingers,
diff --git a/webapp_timeline.py b/webapp_timeline.py
index cfd470dce..2bac74ea6 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -423,7 +423,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the timeline as html
"""
enableTimingLog = False
@@ -854,7 +855,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# read the post from disk
currTlStr = \
- individualPostAsHtml(False, recentPostsCache,
+ individualPostAsHtml(signingPrivateKeyPem,
+ False, recentPostsCache,
maxRecentPosts,
translate, pageNumber,
baseDir, session,
@@ -1079,7 +1081,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the shares timeline as html
"""
manuallyApproveFollowers = \
@@ -1102,7 +1105,7 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlWanted(cssCache: {}, defaultTimeline: str,
@@ -1127,7 +1130,8 @@ def htmlWanted(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the wanted timeline as html
"""
manuallyApproveFollowers = \
@@ -1150,7 +1154,7 @@ def htmlWanted(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInbox(cssCache: {}, defaultTimeline: str,
@@ -1175,7 +1179,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the inbox as html
"""
manuallyApproveFollowers = \
@@ -1198,7 +1203,7 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@@ -1223,7 +1228,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the bookmarks as html
"""
manuallyApproveFollowers = \
@@ -1246,7 +1252,7 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@@ -1271,7 +1277,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the DM timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1289,7 +1296,7 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@@ -1314,7 +1321,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the replies timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1333,7 +1341,7 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@@ -1358,7 +1366,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the media timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1377,7 +1386,7 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@@ -1402,7 +1411,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the blogs timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1421,7 +1431,7 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@@ -1447,7 +1457,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the features timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1466,7 +1477,7 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@@ -1491,7 +1502,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the news timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@@ -1510,7 +1522,7 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
def htmlOutbox(cssCache: {}, defaultTimeline: str,
@@ -1535,7 +1547,8 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
textModeBanner: str,
accessKeys: {}, systemLanguage: str,
maxLikeCount: int,
- sharedItemsFederatedDomains: []) -> str:
+ sharedItemsFederatedDomains: [],
+ signingPrivateKeyPem: str) -> str:
"""Show the Outbox as html
"""
manuallyApproveFollowers = \
@@ -1555,4 +1568,4 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess, textModeBanner,
accessKeys, systemLanguage, maxLikeCount,
- sharedItemsFederatedDomains)
+ sharedItemsFederatedDomains, signingPrivateKeyPem)
diff --git a/webapp_utils.py b/webapp_utils.py
index e3bbe47e6..3bb07355d 100644
--- a/webapp_utils.py
+++ b/webapp_utils.py
@@ -232,7 +232,8 @@ def setBlogAddress(actorJson: {}, blogAddress: str) -> None:
_setActorPropertyUrl(actorJson, 'Blog', removeHtml(blogAddress))
-def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
+def updateAvatarImageCache(signingPrivateKeyPem: str,
+ session, baseDir: str, httpPrefix: str,
actor: str, avatarUrl: str,
personCache: {}, allowDownloads: bool,
force: bool = False, debug: bool = False) -> str:
@@ -299,7 +300,7 @@ def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
'Accept': 'application/ld+json; profile="' + prof + '"'
}
personJson = \
- getJson(session, actor, sessionHeaders, None,
+ getJson(signingPrivateKeyPem, session, actor, sessionHeaders, None,
debug, __version__, httpPrefix, None)
if personJson:
if not personJson.get('id'):
@@ -1113,7 +1114,8 @@ def htmlHighlightLabel(label: str, highlight: bool) -> str:
def getAvatarImageUrl(session,
baseDir: str, httpPrefix: str,
postActor: str, personCache: {},
- avatarUrl: str, allowDownloads: bool) -> str:
+ avatarUrl: str, allowDownloads: bool,
+ signingPrivateKeyPem: str) -> str:
"""Returns the avatar image url
"""
# get the avatar image url for the post actor
@@ -1122,11 +1124,13 @@ def getAvatarImageUrl(session,
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
avatarUrl = \
- updateAvatarImageCache(session, baseDir, httpPrefix,
+ updateAvatarImageCache(signingPrivateKeyPem,
+ session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
else:
- updateAvatarImageCache(session, baseDir, httpPrefix,
+ updateAvatarImageCache(signingPrivateKeyPem,
+ session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
diff --git a/webfinger.py b/webfinger.py
index a562f24ad..cbe948122 100644
--- a/webfinger.py
+++ b/webfinger.py
@@ -63,7 +63,8 @@ def _parseHandle(handle: str) -> (str, str, bool):
def webfingerHandle(session, handle: str, httpPrefix: str,
cachedWebfingers: {},
fromDomain: str, projectVersion: str,
- debug: bool, groupAccount: bool) -> {}:
+ debug: bool, groupAccount: bool,
+ signingPrivateKeyPem: str) -> {}:
"""Gets webfinger result for the given ActivityPub handle
"""
if not session:
@@ -98,9 +99,8 @@ def webfingerHandle(session, handle: str, httpPrefix: str,
}
try:
result = \
- getJson(session, url, hdr, par,
- debug, projectVersion,
- httpPrefix, fromDomain)
+ getJson(signingPrivateKeyPem, session, url, hdr, par,
+ debug, projectVersion, httpPrefix, fromDomain)
except Exception as e:
print('ERROR: webfingerHandle ' + str(e))
return None
From a83c6594d09c5e69665c31bd1e1886ab35881b9d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 15:23:50 +0100
Subject: [PATCH 033/385] Debug signed get
---
session.py | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/session.py b/session.py
index 5602d2678..606744915 100644
--- a/session.py
+++ b/session.py
@@ -174,20 +174,22 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
# instance actor
nickname = domain
- if debug:
- print('Signed GET privateKeyPem: ' + signingPrivateKeyPem)
- print('Signed GET nickname: ' + nickname)
- print('Signed GET domain: ' + domain + ' ' + str(port))
- print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
- print('Signed GET url: ' + url)
- print('Signed GET httpPrefix: ' + httpPrefix)
+# if debug:
+ print('Signed GET privateKeyPem: ' + signingPrivateKeyPem)
+ print('Signed GET nickname: ' + nickname)
+ print('Signed GET domain: ' + domain + ' ' + str(port))
+ print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
+ print('Signed GET url: ' + url)
+ print('Signed GET httpPrefix: ' + httpPrefix)
signatureHeaderJson = \
createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
toDomain, toPort, url, httpPrefix, False, '')
+ print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
if key == 'Accept' or key == 'User-Agent':
continue
sessionHeaders[key] = value
+ print('Signed GET sessionHeaders ' + str(sessionHeaders))
return _getJsonRequest(session, url, domainFull, sessionHeaders,
sessionParams, timeoutSec, None, quiet, debug)
From 73a5182232f94ae97d9c6e6d8e5e48419b9c2495 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 15:27:55 +0100
Subject: [PATCH 034/385] Don't include key in debug
---
session.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/session.py b/session.py
index 606744915..a3259ba0d 100644
--- a/session.py
+++ b/session.py
@@ -175,7 +175,6 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
nickname = domain
# if debug:
- print('Signed GET privateKeyPem: ' + signingPrivateKeyPem)
print('Signed GET nickname: ' + nickname)
print('Signed GET domain: ' + domain + ' ' + str(port))
print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
From 5238e194019e68288472ff5a4d8e3c56be986f13 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:19:55 +0100
Subject: [PATCH 035/385] Showing instance actor
---
daemon.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 86 insertions(+), 5 deletions(-)
diff --git a/daemon.py b/daemon.py
index 2fe6bfa80..3718fa186 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10258,6 +10258,66 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return True
+ def _showInstanceActor(self, 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,
+ enableSharedInbox: bool) -> bool:
+ """Shows the instance actor
+ """
+ actorJson = personLookup(domain, path, baseDir)
+ if not actorJson:
+ self._404()
+ return False
+ acceptStr = self.headers['Accept']
+ if onionDomain and callingDomain.endswith('.onion'):
+ actorDomainUrl = 'http://' + onionDomain
+ elif i2pDomain and callingDomain.endswith('.i2p'):
+ actorDomainUrl = 'http://' + i2pDomain
+ else:
+ actorDomainUrl = httpPrefix + '://' + domainFull
+ actorUrl = actorDomainUrl + '/users/Actor'
+ del actorJson['icon']
+ del actorJson['image']
+ actorJson['endpoints'] = {}
+ if enableSharedInbox:
+ actorJson['endpoints'] = {
+ 'sharedInbox': actorDomainUrl + '/inbox'
+ }
+ actorJson['name'] = 'ACTOR'
+ actorJson['preferredUsername'] = 'Actor'
+ actorJson['id'] = actorUrl
+ actorJson['publicKey']['id'] = actorUrl + '#main-key'
+ actorJson['publicKey']['owner'] = actorUrl
+ actorJson['url'] = actorUrl
+ actorJson['inbox'] = actorUrl + '/inbox'
+ actorJson['followers'] = actorUrl + '/followers'
+ actorJson['following'] = actorUrl + '/following'
+ msgStr = json.dumps(actorJson, ensure_ascii=False)
+ if onionDomain and callingDomain.endswith('.onion'):
+ msgStr = msgStr.replace(httpPrefix + '://' + domainFull,
+ 'http://' + onionDomain)
+ elif i2pDomain and callingDomain.endswith('.i2p'):
+ msgStr = msgStr.replace(httpPrefix + '://' + domainFull,
+ 'http://' + i2pDomain)
+ msg = msgStr.encode('utf-8')
+ msglen = len(msg)
+ if 'application/ld+json' in acceptStr:
+ self._set_headers('application/ld+json', msglen,
+ cookie, callingDomain, False)
+ elif 'application/jrd+json' in acceptStr:
+ self._set_headers('application/jrd+json', msglen,
+ cookie, callingDomain, False)
+ else:
+ self._set_headers('application/activity+json', msglen,
+ cookie, callingDomain, False)
+ self._write(msg)
+ self.server.GETbusy = False
+ return True
+
def _showBlogPage(self, authorized: bool,
callingDomain: str, path: str,
baseDir: str, httpPrefix: str,
@@ -11418,11 +11478,32 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'hasAccept', 'fonts')
- # treat shared inbox paths consistently
- if self.path == '/sharedInbox' or \
- self.path == '/users/inbox' or \
- self.path == '/actor/inbox' or \
- self.path == '/users/' + self.server.domain:
+ if not htmlGET and \
+ self.path == '/actor' or \
+ self.path == '/users/actor' or \
+ self.path == '/Actor' or \
+ self.path == '/users/Actor':
+ self.path = '/users/inbox'
+ if self._showInstanceActor(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,
+ self.server.enableSharedInbox):
+ return
+ else:
+ self._404()
+ return
+ elif self.path == '/sharedInbox' or \
+ self.path == '/users/inbox' or \
+ self.path == '/actor/inbox' or \
+ self.path == '/users/' + self.server.domain:
# if shared inbox is not enabled
if not self.server.enableSharedInbox:
self._503()
From 4888b53f60bd21697cb1ea66d76cfe32ed3b2e62 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:21:37 +0100
Subject: [PATCH 036/385] Missing parameter
---
webapp_moderation.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/webapp_moderation.py b/webapp_moderation.py
index 16d0f684c..c1a195060 100644
--- a/webapp_moderation.py
+++ b/webapp_moderation.py
@@ -137,7 +137,8 @@ def htmlAccountInfo(cssCache: {}, translate: {},
# get a list of any blocked following
followingList = \
- downloadFollowCollection('following', session,
+ downloadFollowCollection(signingPrivateKeyPem,
+ 'following', session,
httpPrefix, searchActor, 1, 5)
blockedFollowing = []
for followingActor in followingList:
From 4bf11a34ce59e78d35ca804a3e90ad9d4290ba75 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:42:32 +0100
Subject: [PATCH 037/385] Don't clear busy state
---
daemon.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 3718fa186..e1986da60 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10315,7 +10315,6 @@ class PubServer(BaseHTTPRequestHandler):
self._set_headers('application/activity+json', msglen,
cookie, callingDomain, False)
self._write(msg)
- self.server.GETbusy = False
return True
def _showBlogPage(self, authorized: bool,
From 0b3bed070dd9e579aac901176436e7e7281761cf Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:44:49 +0100
Subject: [PATCH 038/385] Debug
---
daemon.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/daemon.py b/daemon.py
index e1986da60..d13875d3b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10268,8 +10268,10 @@ class PubServer(BaseHTTPRequestHandler):
enableSharedInbox: bool) -> bool:
"""Shows the instance actor
"""
+ print('Instance actor requested by ' + callingDomain)
actorJson = personLookup(domain, path, baseDir)
if not actorJson:
+ print('ERROR: no instance actor found')
self._404()
return False
acceptStr = self.headers['Accept']
From f661e98880794c7156ef3740ed8e4dd17706b3bd Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:53:59 +0100
Subject: [PATCH 039/385] Instance actor earlier
---
daemon.py | 53 +++++++++++++++++++++++++++--------------------------
1 file changed, 27 insertions(+), 26 deletions(-)
diff --git a/daemon.py b/daemon.py
index d13875d3b..5bbd9f64d 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11201,6 +11201,29 @@ class PubServer(BaseHTTPRequestHandler):
'favicon.ico')
return
+ # instance actor
+ if self.path == '/actor' or \
+ self.path == '/users/actor' or \
+ self.path == '/Actor' or \
+ self.path == '/users/Actor':
+ self.path = '/users/inbox'
+ if self._showInstanceActor(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,
+ self.server.enableSharedInbox):
+ return
+ else:
+ self._404()
+ return
+
# check authorization
authorized = self._isAuthorized()
if self.server.debug:
@@ -11479,32 +11502,10 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'hasAccept', 'fonts')
- if not htmlGET and \
- self.path == '/actor' or \
- self.path == '/users/actor' or \
- self.path == '/Actor' or \
- self.path == '/users/Actor':
- self.path = '/users/inbox'
- if self._showInstanceActor(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,
- self.server.enableSharedInbox):
- return
- else:
- self._404()
- return
- elif self.path == '/sharedInbox' or \
- self.path == '/users/inbox' or \
- self.path == '/actor/inbox' or \
- self.path == '/users/' + self.server.domain:
+ if self.path == '/sharedInbox' or \
+ self.path == '/users/inbox' or \
+ self.path == '/actor/inbox' or \
+ self.path == '/users/' + self.server.domain:
# if shared inbox is not enabled
if not self.server.enableSharedInbox:
self._503()
From ad460992737875159cb4d5254247e845ab30b3a2 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 17:54:40 +0100
Subject: [PATCH 040/385] Instance actor earlier
---
daemon.py | 46 +++++++++++++++++++++++-----------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/daemon.py b/daemon.py
index 5bbd9f64d..e3011f6f3 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11152,6 +11152,29 @@ class PubServer(BaseHTTPRequestHandler):
self.path.replace('/users/' + nickname + '/',
'/users/' + nickname + '/statuses/')
+ # instance actor
+ if self.path == '/actor' or \
+ self.path == '/users/actor' or \
+ self.path == '/Actor' or \
+ self.path == '/users/Actor':
+ self.path = '/users/inbox'
+ if self._showInstanceActor(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,
+ self.server.enableSharedInbox):
+ return
+ else:
+ self._404()
+ return
+
# turn off dropdowns on new post screen
noDropDown = False
if self.path.endswith('?nodropdown'):
@@ -11201,29 +11224,6 @@ class PubServer(BaseHTTPRequestHandler):
'favicon.ico')
return
- # instance actor
- if self.path == '/actor' or \
- self.path == '/users/actor' or \
- self.path == '/Actor' or \
- self.path == '/users/Actor':
- self.path = '/users/inbox'
- if self._showInstanceActor(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,
- self.server.enableSharedInbox):
- return
- else:
- self._404()
- return
-
# check authorization
authorized = self._isAuthorized()
if self.server.debug:
From a02946c82f1ca639e733416c79c58564508f92be Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 18:04:57 +0100
Subject: [PATCH 041/385] No cookie on instance actor
---
daemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index e3011f6f3..6251f380b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11168,7 +11168,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.i2pDomain,
GETstartTime, GETtimings,
self.server.proxyType,
- cookie, self.server.debug,
+ None, self.server.debug,
self.server.enableSharedInbox):
return
else:
From 1087afc4459629e2d9a8b42f3cdecd2e258f178d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 18:18:07 +0100
Subject: [PATCH 042/385] debug
---
daemon.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/daemon.py b/daemon.py
index 6251f380b..5db12a5aa 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11153,11 +11153,13 @@ class PubServer(BaseHTTPRequestHandler):
'/users/' + nickname + '/statuses/')
# instance actor
+ print('actor test 1: ' + self.path)
if self.path == '/actor' or \
self.path == '/users/actor' or \
self.path == '/Actor' or \
self.path == '/users/Actor':
self.path = '/users/inbox'
+ print('actor test 2: ' + self.path)
if self._showInstanceActor(callingDomain, self.path,
self.server.baseDir,
self.server.httpPrefix,
@@ -11170,8 +11172,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.proxyType,
None, self.server.debug,
self.server.enableSharedInbox):
+ print('actor test success')
return
else:
+ print('actor test failed')
self._404()
return
From c1781051646d0310baed66bb1b47607c5bd5027c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 18:21:37 +0100
Subject: [PATCH 043/385] Debug
---
daemon.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/daemon.py b/daemon.py
index 5db12a5aa..3c9ed1bcb 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11062,6 +11062,7 @@ class PubServer(BaseHTTPRequestHandler):
return True
def do_GET(self):
+ print('actor test 0: ' + self.path)
callingDomain = self.server.domainFull
if self.headers.get('Host'):
callingDomain = decodedHost(self.headers['Host'])
@@ -11079,10 +11080,12 @@ class PubServer(BaseHTTPRequestHandler):
self._400()
return
+ print('actor test 1: ' + self.path)
if self._blockedUserAgent(callingDomain):
self._400()
return
+ print('actor test 2: ' + self.path)
GETstartTime = time.time()
GETtimings = {}
@@ -11097,6 +11100,7 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'start', '_nodeinfo[callingDomain]')
+ print('actor test 3: ' + self.path)
if self.path == '/logout':
if not self.server.newsInstance:
msg = \
@@ -11153,13 +11157,13 @@ class PubServer(BaseHTTPRequestHandler):
'/users/' + nickname + '/statuses/')
# instance actor
- print('actor test 1: ' + self.path)
+ print('actor test 4: ' + self.path)
if self.path == '/actor' or \
self.path == '/users/actor' or \
self.path == '/Actor' or \
self.path == '/users/Actor':
self.path = '/users/inbox'
- print('actor test 2: ' + self.path)
+ print('actor test 4: ' + self.path)
if self._showInstanceActor(callingDomain, self.path,
self.server.baseDir,
self.server.httpPrefix,
From 2ee6673dc0ee2a8c28d2894a8d2685dffd6f12da Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:30:39 +0100
Subject: [PATCH 044/385] Resolve instance actor
---
daemon.py | 20 +++++++++-----------
person.py | 2 +-
tests.py | 22 ++++++++++++++++++++--
3 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/daemon.py b/daemon.py
index 3c9ed1bcb..32a96bcd8 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10268,7 +10268,8 @@ class PubServer(BaseHTTPRequestHandler):
enableSharedInbox: bool) -> bool:
"""Shows the instance actor
"""
- print('Instance actor requested by ' + callingDomain)
+ if debug:
+ print('Instance actor requested by ' + callingDomain)
actorJson = personLookup(domain, path, baseDir)
if not actorJson:
print('ERROR: no instance actor found')
@@ -10282,8 +10283,10 @@ class PubServer(BaseHTTPRequestHandler):
else:
actorDomainUrl = httpPrefix + '://' + domainFull
actorUrl = actorDomainUrl + '/users/Actor'
- del actorJson['icon']
- del actorJson['image']
+ removeFields = ('icon', 'image', 'tts', 'shares')
+ for r in removeFields:
+ if actorJson.get(r):
+ del actorJson[r]
actorJson['endpoints'] = {}
if enableSharedInbox:
actorJson['endpoints'] = {
@@ -11062,7 +11065,6 @@ class PubServer(BaseHTTPRequestHandler):
return True
def do_GET(self):
- print('actor test 0: ' + self.path)
callingDomain = self.server.domainFull
if self.headers.get('Host'):
callingDomain = decodedHost(self.headers['Host'])
@@ -11080,12 +11082,10 @@ class PubServer(BaseHTTPRequestHandler):
self._400()
return
- print('actor test 1: ' + self.path)
if self._blockedUserAgent(callingDomain):
self._400()
return
- print('actor test 2: ' + self.path)
GETstartTime = time.time()
GETtimings = {}
@@ -11100,7 +11100,6 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'start', '_nodeinfo[callingDomain]')
- print('actor test 3: ' + self.path)
if self.path == '/logout':
if not self.server.newsInstance:
msg = \
@@ -11157,13 +11156,12 @@ class PubServer(BaseHTTPRequestHandler):
'/users/' + nickname + '/statuses/')
# instance actor
- print('actor test 4: ' + self.path)
if self.path == '/actor' or \
self.path == '/users/actor' or \
self.path == '/Actor' or \
self.path == '/users/Actor':
self.path = '/users/inbox'
- print('actor test 4: ' + self.path)
+ print('actor test 1: ' + self.path)
if self._showInstanceActor(callingDomain, self.path,
self.server.baseDir,
self.server.httpPrefix,
@@ -11176,10 +11174,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.proxyType,
None, self.server.debug,
self.server.enableSharedInbox):
- print('actor test success')
+ print('actor test 2: ' + self.path)
return
else:
- print('actor test failed')
+ print('actor test 3: ' + self.path)
self._404()
return
diff --git a/person.py b/person.py
index 0a86abe53..db2b882d4 100644
--- a/person.py
+++ b/person.py
@@ -718,7 +718,7 @@ def personLookup(domain: str, path: str, baseDir: str) -> {}:
isSharedInbox = False
if path == '/inbox' or path == '/users/inbox' or path == '/sharedInbox':
# shared inbox actor on @domain@domain
- path = '/users/' + domain
+ path = '/users/inbox'
isSharedInbox = True
else:
notPersonLookup = ('/inbox', '/outbox', '/outboxarchive',
diff --git a/tests.py b/tests.py
index 20bf548f1..310362cfd 100644
--- a/tests.py
+++ b/tests.py
@@ -1444,6 +1444,25 @@ def testSharedItemsFederation():
assert ctr <= 60
time.sleep(1)
+ signingPrivateKeyPem = None
+ sessionClient = createSession(proxyType)
+
+ # Get Bob's instance actor
+ print('\n\n*********************************************************')
+ print("Test Bob's instance actor")
+ profileStr = 'https://www.w3.org/ns/activitystreams'
+ testHeaders = {
+ 'host': bobAddress,
+ 'Accept': 'application/ld+json; profile="' + profileStr + '"'
+ }
+ bobInstanceActorJson = \
+ getJson(signingPrivateKeyPem, sessionClient,
+ 'http://' + bobAddress + '/@actor', testHeaders, {}, True,
+ __version__, 'http', 'somedomain.or.other', 10, True)
+ assert bobInstanceActorJson
+ pprint(bobInstanceActorJson)
+ assert bobInstanceActorJson['name'] == 'ACTOR'
+
# In the beginning all was calm and there were no follows
print('\n\n*********************************************************')
@@ -1469,7 +1488,6 @@ def testSharedItemsFederation():
aliceCachedWebfingers = {}
alicePostLog = []
bobActor = httpPrefix + '://' + bobAddress + '/users/bob'
- signingPrivateKeyPem = None
sendResult = \
sendFollowRequest(sessionAlice, aliceDir,
'alice', aliceDomain, alicePort, httpPrefix,
@@ -1520,7 +1538,6 @@ def testSharedItemsFederation():
assert os.path.isfile(bobDir + '/ontology/foodTypes.json')
assert os.path.isfile(bobDir + '/ontology/toolTypes.json')
assert os.path.isfile(bobDir + '/ontology/clothesTypes.json')
- sessionBob = createSession(proxyType)
sharedItemName = 'cheddar'
sharedItemDescription = 'Some cheese'
sharedItemImageFilename = 'logo.png'
@@ -1532,6 +1549,7 @@ def testSharedItemsFederation():
sharedItemPrice = "1.30"
sharedItemCurrency = "EUR"
signingPrivateKeyPem = None
+ sessionBob = createSession(proxyType)
shareJson = \
sendShareViaServer(bobDir, sessionBob,
'bob', bobPassword,
From f7a76eab330ea96811eaf47f6bf6dd7298375c72 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:35:06 +0100
Subject: [PATCH 045/385] Tweaks to instance actor
---
daemon.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 32a96bcd8..6d0908df6 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10283,7 +10283,8 @@ class PubServer(BaseHTTPRequestHandler):
else:
actorDomainUrl = httpPrefix + '://' + domainFull
actorUrl = actorDomainUrl + '/users/Actor'
- removeFields = ('icon', 'image', 'tts', 'shares')
+ removeFields = ('icon', 'image', 'tts', 'shares',
+ 'alsoKnownAs', 'hasOccupation', 'featured')
for r in removeFields:
if actorJson.get(r):
del actorJson[r]
@@ -10295,6 +10296,7 @@ class PubServer(BaseHTTPRequestHandler):
actorJson['name'] = 'ACTOR'
actorJson['preferredUsername'] = 'Actor'
actorJson['id'] = actorUrl
+ actorJson['summary'] = 'Instance Actor'
actorJson['publicKey']['id'] = actorUrl + '#main-key'
actorJson['publicKey']['owner'] = actorUrl
actorJson['url'] = actorUrl
From 643f18a2f0408beb5f1f1cd89950fab439fb34e1 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:37:45 +0100
Subject: [PATCH 046/385] Instance actor type
---
daemon.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/daemon.py b/daemon.py
index 6d0908df6..2f7baee5b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10296,6 +10296,7 @@ class PubServer(BaseHTTPRequestHandler):
actorJson['name'] = 'ACTOR'
actorJson['preferredUsername'] = 'Actor'
actorJson['id'] = actorUrl
+ actorJson['type'] = 'Person'
actorJson['summary'] = 'Instance Actor'
actorJson['publicKey']['id'] = actorUrl + '#main-key'
actorJson['publicKey']['owner'] = actorUrl
From fa7b27a1962fbceea2555ac594ecbeb4405cf7af Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:39:03 +0100
Subject: [PATCH 047/385] Only upgrade user accounts
---
person.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/person.py b/person.py
index db2b882d4..756bcd7a0 100644
--- a/person.py
+++ b/person.py
@@ -742,7 +742,8 @@ def personLookup(domain: str, path: str, baseDir: str) -> {}:
if not os.path.isfile(filename):
return None
personJson = loadJson(filename)
- personUpgradeActor(baseDir, personJson, handle, filename)
+ if not isSharedInbox:
+ personUpgradeActor(baseDir, personJson, handle, filename)
# if not personJson:
# personJson={"user": "unknown"}
return personJson
From 58507b0120394f7cb4c293371f9dcde4ceaf6049 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:41:31 +0100
Subject: [PATCH 048/385] Instance actor not discoverable
---
daemon.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/daemon.py b/daemon.py
index 2f7baee5b..8031aaca3 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10297,6 +10297,7 @@ class PubServer(BaseHTTPRequestHandler):
actorJson['preferredUsername'] = 'Actor'
actorJson['id'] = actorUrl
actorJson['type'] = 'Person'
+ actorJson['discoverable'] = False
actorJson['summary'] = 'Instance Actor'
actorJson['publicKey']['id'] = actorUrl + '#main-key'
actorJson['publicKey']['owner'] = actorUrl
From ded0ec1ea6ee986381e7c56a298a86ae5e8efcfa Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 19:56:16 +0100
Subject: [PATCH 049/385] Remove debug
---
daemon.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/daemon.py b/daemon.py
index 8031aaca3..94dca486b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11165,7 +11165,6 @@ class PubServer(BaseHTTPRequestHandler):
self.path == '/Actor' or \
self.path == '/users/Actor':
self.path = '/users/inbox'
- print('actor test 1: ' + self.path)
if self._showInstanceActor(callingDomain, self.path,
self.server.baseDir,
self.server.httpPrefix,
@@ -11178,10 +11177,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.proxyType,
None, self.server.debug,
self.server.enableSharedInbox):
- print('actor test 2: ' + self.path)
return
else:
- print('actor test 3: ' + self.path)
self._404()
return
From cd6f2fdbbb14b9a1d142044c70f450722bb643f0 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 20:04:29 +0100
Subject: [PATCH 050/385] Webfingering instance actor
---
webfinger.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/webfinger.py b/webfinger.py
index cbe948122..bf87c4c6d 100644
--- a/webfinger.py
+++ b/webfinger.py
@@ -267,6 +267,11 @@ def webfingerLookup(path: str, baseDir: str,
if onionDomain in handle:
handle = handle.replace(onionDomain, domain)
onionify = True
+ # instance actor
+ if handle.startswith('actor@'):
+ handle = handle.replace('actor@', 'inbox@', 1)
+ elif handle.startswith('Actor@'):
+ handle = handle.replace('Actor@', 'inbox@', 1)
filename = baseDir + '/wfendpoints/' + handle + '.json'
if debug:
print('DEBUG: WEBFINGER filename ' + filename)
From 8093fe92ebecd42c8dc6daf0251589cdb16e8e1c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 20:14:45 +0100
Subject: [PATCH 051/385] No http get for instance actor
---
daemon.py | 47 ++++++++++++++++++++++++-----------------------
1 file changed, 24 insertions(+), 23 deletions(-)
diff --git a/daemon.py b/daemon.py
index 94dca486b..c3957c4ad 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11159,29 +11159,6 @@ class PubServer(BaseHTTPRequestHandler):
self.path.replace('/users/' + nickname + '/',
'/users/' + nickname + '/statuses/')
- # instance actor
- if self.path == '/actor' or \
- self.path == '/users/actor' or \
- self.path == '/Actor' or \
- self.path == '/users/Actor':
- self.path = '/users/inbox'
- if self._showInstanceActor(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,
- None, self.server.debug,
- self.server.enableSharedInbox):
- return
- else:
- self._404()
- return
-
# turn off dropdowns on new post screen
noDropDown = False
if self.path.endswith('?nodropdown'):
@@ -11485,6 +11462,30 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'create session', 'hasAccept')
+ # instance actor
+ if not httpGET:
+ if self.path == '/actor' or \
+ self.path == '/users/actor' or \
+ self.path == '/Actor' or \
+ self.path == '/users/Actor':
+ self.path = '/users/inbox'
+ if self._showInstanceActor(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,
+ None, self.server.debug,
+ self.server.enableSharedInbox):
+ return
+ else:
+ self._404()
+ return
+
# get css
# Note that this comes before the busy flag to avoid conflicts
if self.path.endswith('.css'):
From d2b5b2030778869000ed5bbbde598b1d14a87680 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 20:17:19 +0100
Subject: [PATCH 052/385] Revert "No http get for instance actor"
This reverts commit 8093fe92ebecd42c8dc6daf0251589cdb16e8e1c.
---
daemon.py | 47 +++++++++++++++++++++++------------------------
1 file changed, 23 insertions(+), 24 deletions(-)
diff --git a/daemon.py b/daemon.py
index c3957c4ad..94dca486b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11159,6 +11159,29 @@ class PubServer(BaseHTTPRequestHandler):
self.path.replace('/users/' + nickname + '/',
'/users/' + nickname + '/statuses/')
+ # instance actor
+ if self.path == '/actor' or \
+ self.path == '/users/actor' or \
+ self.path == '/Actor' or \
+ self.path == '/users/Actor':
+ self.path = '/users/inbox'
+ if self._showInstanceActor(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,
+ None, self.server.debug,
+ self.server.enableSharedInbox):
+ return
+ else:
+ self._404()
+ return
+
# turn off dropdowns on new post screen
noDropDown = False
if self.path.endswith('?nodropdown'):
@@ -11462,30 +11485,6 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings,
'create session', 'hasAccept')
- # instance actor
- if not httpGET:
- if self.path == '/actor' or \
- self.path == '/users/actor' or \
- self.path == '/Actor' or \
- self.path == '/users/Actor':
- self.path = '/users/inbox'
- if self._showInstanceActor(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,
- None, self.server.debug,
- self.server.enableSharedInbox):
- return
- else:
- self._404()
- return
-
# get css
# Note that this comes before the busy flag to avoid conflicts
if self.path.endswith('.css'):
From 131d8b66251fe5ab788e5e255d200c26840f546e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 20:19:21 +0100
Subject: [PATCH 053/385] Alternative check for http
---
daemon.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/daemon.py b/daemon.py
index 94dca486b..d293a2c8d 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10270,6 +10270,8 @@ class PubServer(BaseHTTPRequestHandler):
"""
if debug:
print('Instance actor requested by ' + callingDomain)
+ if self._requestHTTP():
+ return False
actorJson = personLookup(domain, path, baseDir)
if not actorJson:
print('ERROR: no instance actor found')
From 50f2a16fde03f71d5e04901b70412b4fb5f81ed1 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 20:19:50 +0100
Subject: [PATCH 054/385] Return 404 for http instance actor
---
daemon.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/daemon.py b/daemon.py
index d293a2c8d..d1caf928b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -10271,6 +10271,7 @@ class PubServer(BaseHTTPRequestHandler):
if debug:
print('Instance actor requested by ' + callingDomain)
if self._requestHTTP():
+ self._404()
return False
actorJson = personLookup(domain, path, baseDir)
if not actorJson:
From ed516c8ff7d1e7547e8e8d591f4c7ea27aaa6ab3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 21:20:58 +0100
Subject: [PATCH 055/385] Get signature with digest
---
session.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/session.py b/session.py
index a3259ba0d..62a63aa1b 100644
--- a/session.py
+++ b/session.py
@@ -139,7 +139,7 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
sessionParams: {}, timeoutSec: int,
signingPrivateKeyPem: str, quiet: bool, debug: bool) -> {}:
- """Authorized fetch
+ """Authorized fetch - a signed version of GET
"""
if not domainFull:
if debug:
@@ -180,9 +180,12 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
print('Signed GET url: ' + url)
print('Signed GET httpPrefix: ' + httpPrefix)
+ messageStr = ''
+ withDigest = True
signatureHeaderJson = \
createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
- toDomain, toPort, url, httpPrefix, False, '')
+ toDomain, toPort, url, httpPrefix, withDigest,
+ messageStr)
print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
if key == 'Accept' or key == 'User-Agent':
From a69ac4c883f1fca6855e6434dbffceb37ad0ecb7 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 21:53:53 +0100
Subject: [PATCH 056/385] Include accept within non-digest http signature
---
httpsig.py | 3 ++-
session.py | 2 +-
tests.py | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 50d897e3c..bbe65b59d 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -199,7 +199,8 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
headers = {
'(request-target)': f'post {path}',
'host': headerDomain,
- 'date': dateStr
+ 'date': dateStr,
+ 'accept': contentType
}
signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname,
diff --git a/session.py b/session.py
index 62a63aa1b..fd997babf 100644
--- a/session.py
+++ b/session.py
@@ -181,7 +181,7 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
print('Signed GET url: ' + url)
print('Signed GET httpPrefix: ' + httpPrefix)
messageStr = ''
- withDigest = True
+ withDigest = False
signatureHeaderJson = \
createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
toDomain, toPort, url, httpPrefix, withDigest,
diff --git a/tests.py b/tests.py
index 310362cfd..969b29f8f 100644
--- a/tests.py
+++ b/tests.py
@@ -1457,7 +1457,7 @@ def testSharedItemsFederation():
}
bobInstanceActorJson = \
getJson(signingPrivateKeyPem, sessionClient,
- 'http://' + bobAddress + '/@actor', testHeaders, {}, True,
+ 'http://' + bobAddress + '/@actor', testHeaders, {}, True,
__version__, 'http', 'somedomain.or.other', 10, True)
assert bobInstanceActorJson
pprint(bobInstanceActorJson)
From e8d59a951b279f988eb33a55c1fee5f7ff8accd5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 22:02:58 +0100
Subject: [PATCH 057/385] Special case for instance actor
---
httpsig.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/httpsig.py b/httpsig.py
index bbe65b59d..0f91ac059 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -49,7 +49,11 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
if not dateStr:
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
- keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key'
+ if nickname != domain:
+ keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key'
+ else:
+ # instance actor
+ keyID = httpPrefix + '://' + domain + '/actor#main-key'
if not messageBodyJsonStr:
headers = {
'(request-target)': f'post {path}',
From 18cbe07f569aa74b0dfcb88a84071478b2479d5f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 22:26:07 +0100
Subject: [PATCH 058/385] Change content-type to accept within non-digest http
signature
---
httpsig.py | 2 +-
tests.py | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 0f91ac059..ad9b92632 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -59,7 +59,7 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
'(request-target)': f'post {path}',
'host': toDomain,
'date': dateStr,
- 'content-type': 'application/json'
+ 'accept': 'application/json'
}
else:
bodyDigest = messageContentDigest(messageBodyJsonStr)
diff --git a/tests.py b/tests.py
index 969b29f8f..1d0a2b0ff 100644
--- a/tests.py
+++ b/tests.py
@@ -365,7 +365,7 @@ def _testHttpsigBase(withDigest):
headers = {
'host': headersDomain,
'date': dateStr,
- 'content-type': 'application/json'
+ 'accept': 'application/json'
}
signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname,
@@ -5362,6 +5362,7 @@ def _translateOntology() -> None:
def runAllTests():
print('Running tests...')
+ _testHttpsig()
updateDefaultThemesList(os.getcwd())
_translateOntology()
_testGetPriceFromString()
From 6cef4923de4c377c1d47937de3bbf73d8dbb0114 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 22:26:29 +0100
Subject: [PATCH 059/385] Tidying
---
tests.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests.py b/tests.py
index 1d0a2b0ff..3eb8cd96f 100644
--- a/tests.py
+++ b/tests.py
@@ -5362,7 +5362,6 @@ def _translateOntology() -> None:
def runAllTests():
print('Running tests...')
- _testHttpsig()
updateDefaultThemesList(os.getcwd())
_translateOntology()
_testGetPriceFromString()
From a3328b33bba35adcc96b44175e58f21cd63e31fc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 31 Aug 2021 22:57:49 +0100
Subject: [PATCH 060/385] Consolidation of Accept within signed GET
---
daemon.py | 5 +++++
session.py | 4 ++++
2 files changed, 9 insertions(+)
diff --git a/daemon.py b/daemon.py
index d1caf928b..3a2cfcb4f 100644
--- a/daemon.py
+++ b/daemon.py
@@ -907,6 +907,11 @@ class PubServer(BaseHTTPRequestHandler):
def _hasAccept(self, callingDomain: str) -> bool:
"""Do the http headers have an Accept field?
"""
+ if not self.headers.get('Accept'):
+ if self.headers.get('accept'):
+ print('Upper case Accept')
+ self.headers['Accept'] = self.headers['accept']
+
if self.headers.get('Accept') or callingDomain.endswith('.b32.i2p'):
if not self.headers.get('Accept'):
self.headers['Accept'] = \
diff --git a/session.py b/session.py
index fd997babf..1ce079984 100644
--- a/session.py
+++ b/session.py
@@ -191,6 +191,10 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
if key == 'Accept' or key == 'User-Agent':
continue
sessionHeaders[key] = value
+ # avoid double accept
+ if sessionHeaders.get('Accept') and sessionHeaders.get('accept'):
+ del sessionHeaders['Accept']
+ sessionHeaders['Origin'] = domainFull
print('Signed GET sessionHeaders ' + str(sessionHeaders))
return _getJsonRequest(session, url, domainFull, sessionHeaders,
From d04d047b6414e9aa74ac73060453e8263fff156b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 15:22:11 +0100
Subject: [PATCH 061/385] Tidying
---
httpsig.py | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index ad9b92632..b3651707e 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -338,19 +338,17 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
if headers.get(signedHeader):
appendStr = f'content-length: {headers[signedHeader]}'
signedHeaderList.append(appendStr)
+ elif headers.get('Content-Length'):
+ contentLength = headers['Content-Length']
+ signedHeaderList.append(f'content-length: {contentLength}')
+ elif headers.get('Content-length'):
+ contentLength = headers['Content-length']
+ appendStr = f'content-length: {contentLength}'
+ signedHeaderList.append(appendStr)
else:
- if headers.get('Content-Length'):
- contentLength = headers['Content-Length']
- signedHeaderList.append(f'content-length: {contentLength}')
- else:
- if headers.get('Content-length'):
- contentLength = headers['Content-length']
- appendStr = f'content-length: {contentLength}'
- signedHeaderList.append(appendStr)
- else:
- if debug:
- print('DEBUG: verifyPostHeaders ' + signedHeader +
- ' not found in ' + str(headers))
+ if debug:
+ print('DEBUG: verifyPostHeaders ' + signedHeader +
+ ' not found in ' + str(headers))
else:
if headers.get(signedHeader):
if signedHeader == 'date' and not noRecencyCheck:
From 196c8aba149ee12d85aa6a2d750b89fe64bde78d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 15:42:43 +0100
Subject: [PATCH 062/385] Allow for varying http signature verification
algorithms
---
httpsig.py | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index b3651707e..080e08371 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -310,6 +310,7 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
# Unpack the signed headers and set values based on current headers and
# body (if a digest was included)
signedHeaderList = []
+ algorithm = 'rsa-sha256'
for signedHeader in signatureDict[requestTargetKey].split(fieldSep2):
signedHeader = signedHeader.strip()
if debug:
@@ -328,6 +329,9 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
# if ')' in appendStr:
# appendStr = appendStr.split(')')[0]
signedHeaderList.append(appendStr)
+ elif signedHeader == 'algorithm':
+ if headers.get(signedHeader):
+ algorithm = headers[signedHeader]
elif signedHeader == 'digest':
if messageBodyDigest:
bodyDigest = messageBodyDigest
@@ -402,7 +406,6 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
print('DEBUG: signedHeaderList: ' + str(signedHeaderList))
# Now we have our header data digest
signedHeaderText = '\n'.join(signedHeaderList)
- headerDigest = getSHA256(signedHeaderText.encode('ascii'))
# Get the signature, verify with public key, return result
signature = None
@@ -419,12 +422,19 @@ def verifyPostHeaders(httpPrefix: str, publicKeyPem: str, headers: dict,
# Original Mastodon signature
signature = base64.b64decode(signatureDict['signature'])
+ # If extra signing algorithms need to be added then do it here
+ if algorithm == 'rsa-sha256':
+ headerDigest = getSHA256(signedHeaderText.encode('ascii'))
+ paddingStr = padding.PKCS1v15()
+ alg = hazutils.Prehashed(hashes.SHA256())
+ else:
+ print('Unknown http signature algorithm: ' + algorithm)
+ paddingStr = padding.PKCS1v15()
+ alg = hazutils.Prehashed(hashes.SHA256())
+ headerDigest = ''
+
try:
- pubkey.verify(
- signature,
- headerDigest,
- padding.PKCS1v15(),
- hazutils.Prehashed(hashes.SHA256()))
+ pubkey.verify(signature, headerDigest, paddingStr, alg)
return True
except BaseException:
if debug:
From 049cea43b8dddb5ff1f339c0d89fa1680793ae8d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 16:00:34 +0100
Subject: [PATCH 063/385] Log signed GET
---
daemon.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/daemon.py b/daemon.py
index 3a2cfcb4f..d8bd50686 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11077,6 +11077,9 @@ class PubServer(BaseHTTPRequestHandler):
return True
def do_GET(self):
+ if self.headers.get('Signature'):
+ print('Signed GET: ' + str(self.headers))
+
callingDomain = self.server.domainFull
if self.headers.get('Host'):
callingDomain = decodedHost(self.headers['Host'])
From f7898d09b921c15c1f7dfc1aedee347e87422b64 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 16:04:08 +0100
Subject: [PATCH 064/385] Differentiate from other debug
---
daemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index d8bd50686..ab2cc10d9 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11078,7 +11078,7 @@ class PubServer(BaseHTTPRequestHandler):
def do_GET(self):
if self.headers.get('Signature'):
- print('Signed GET: ' + str(self.headers))
+ print('Signed HTTP GET: ' + str(self.headers))
callingDomain = self.server.domainFull
if self.headers.get('Host'):
From 240def39e6a5db6f5809df5f814aafa1fd629dca Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 16:48:08 +0100
Subject: [PATCH 065/385] Field separators
---
daemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index ab2cc10d9..607d19328 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11078,7 +11078,7 @@ class PubServer(BaseHTTPRequestHandler):
def do_GET(self):
if self.headers.get('Signature'):
- print('Signed HTTP GET: ' + str(self.headers))
+ print('Signed HTTP GET: ' + str(self.headers).replace('\n', ', '))
callingDomain = self.server.domainFull
if self.headers.get('Host'):
From c1649a35dd5f2224e838bc0b568cd8f7d8143440 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 17:06:02 +0100
Subject: [PATCH 066/385] Record path of signed get
---
daemon.py | 3 ++-
tests.py | 33 +++++++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 607d19328..1e1999c8f 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11078,7 +11078,8 @@ class PubServer(BaseHTTPRequestHandler):
def do_GET(self):
if self.headers.get('Signature'):
- print('Signed HTTP GET: ' + str(self.headers).replace('\n', ', '))
+ print('Signed HTTP GET: path: ' + self.path + ', ' +
+ str(self.headers).replace('\n', ', '))
callingDomain = self.server.domainFull
if self.headers.get('Host'):
diff --git a/tests.py b/tests.py
index 3eb8cd96f..786e317d7 100644
--- a/tests.py
+++ b/tests.py
@@ -161,6 +161,39 @@ thrBob = None
thrEve = None
+def _testHttpSigAuthorized():
+ print('testHttpSigAuthorized')
+ publicKeyPem = \
+ '-----BEGIN PUBLIC KEY-----\n' + \
+ 'MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3PcHF18xQPG4zNKKhkZf\n' + \
+ 'rIi0I1vxJc8B7p24SVXSGoO4v5RiASNQYEpov/7orGS9dX93jd0JWkqpdTKnay4X\n' + \
+ 'AIHamedcXBPX/7IbwfhV4jtPu+yFA+AGmybKe1z7zQpo4O9LlEGuW6tMR+L16q3/\n' + \
+ 'x3nP5/9UPOClG7HbgxvA1UkaS1vllOj6pWBOenfbJNx6m7DmEtWFlBsauPTmHBqY\n' + \
+ '2xfx/cFFS757WlPTA61gYpTqsRiyX5d2IWEyfIwhEJHYeK6EtblA1Pf2L1EjM2bw\n' + \
+ 'qx2bgOEzG6ub48aQsTuASjXun39eEgMwNO02BEJXvsGr+fLRx/7xot2u2ekSwt3Z\n' + \
+ 'bQKtUWSNbv+o/PV8hXlwp8gMjw/P+SsjbvYwDOFWa+xIvwNIBDkCk0quxokjiRp/\n' + \
+ '800JxHxsuyUk4D5bLy43aveuxMziOjb1Knr8N3Xm7DLnFzpz2SaHSaeeDkFTddEZ\n' + \
+ 'uPo8d/vqI09Owremr7ZVvJHSeOzTibfD0xg984pneWP3KGzHvXQF2MWPSM7SckHl\n' + \
+ 'eTADO03FGdL8nMO1kV3EIKFudNBjIVZYIZFyYHLff8sw4fILK2/TShf+3jdjOPo+\n' + \
+ 'R4zgBlH2EBd9HvYl6vE1nl43EG4G4UXdv1UTfKKnVEUbIiJ4u6AsEYD6aOCbGp9D\n' + \
+ '3+CJDwridP1XwCLPoUChmAECAwEAAQ==\n' + \
+ '-----END PUBLIC KEY-----\n'
+ signature = 'signature: keyId="https://canic.us/friendica#main-key",algorithm="rsa-sha256",headers="(request-target) date host",signature="cm4IV/H2NaxrbMx1JlQd8rOqyqtKokaSBIDQUYJbP64arelpui+BAaYIEEQM2yn06Kp9quTz+hXgchdzUSJTSRFUwp3d2R0mnz7NR9EOt2UdzjSxafTC0bgyQdDHG++Wp8uBEx0MVol5fQjWZrK6JjLn7iF1YpyFC0i2HzY4ZnQjXv4J07t1mpBudqP13F7hkNVSfppRMDnkvd4qxn6zMOgISc/y5l52p/Qe0rbjVwCl1RbH9LFykP6/+Ii62+5VaLh8QjZCDot624P5ZceX9GemsdEoxAc7yLlhhGanpO9Wmel25y0Jg6dOt8CSRDQlAYvCyw7TC16Rm+JSLyS5ikslHaGnXiNnFV41uCHwXcRCBRJ7N7/U5Yd7xQ/UZ8uPBWYlRr/UUPBKFQU/pV6ZsiZkOHmcOy+qbgDZ7FOuemfc6xypf8uH1/MYdZf0P2cvxeViyHHunPyfA0zdYvQFH9KDe2174Wtd+TfC4RkHozY5LVpcrZpOgJFCYjzLjxUx34S6tf/YWonOLz4NxWgx4QfacdTaLirQuSIibT92nRdQIJcEm7JWj8efh6XkDIs54cKMm8Qb5Zk0CfpmM1bGuwJFZ0w7j3D9v/Q/mdYv7so4cG5jgNX6uiVJAp0PbNZkHvyu/T0v6iXAws09Xa2wnfNahyt3uuu/WVtYPlAngG8="'
+ headers = {
+ "accept": 'application/activity+json, application/ld+json',
+ "user-agent": "Friendica 'Red Hot Poker' 2021.01-1384; https://canic.us",
+ "host": 'epicyon.libreserver.org',
+ "date": 'Wed, 01 Sep 2021 15:41:51 GMT',
+ "accept-encoding": 'deflate, gzip',
+ "signature": signature
+ }
+ httpPrefix = 'https'
+ debug = False
+ assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
+ boxpath, False, None,
+ messageBodyJsonStr, debug, True)
+
+
def _testHttpSigNew():
print('testHttpSigNew')
messageBodyJson = {"hello": "world"}
From cfc45c013f0c0dd3674ac2bdcb99554ca359724f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 17:22:00 +0100
Subject: [PATCH 067/385] Working test case for signed GET
---
tests.py | 49 ++++++++++++++++++++++++++++---------------------
1 file changed, 28 insertions(+), 21 deletions(-)
diff --git a/tests.py b/tests.py
index 786e317d7..5be000b1b 100644
--- a/tests.py
+++ b/tests.py
@@ -163,35 +163,40 @@ thrEve = None
def _testHttpSigAuthorized():
print('testHttpSigAuthorized')
+
+ boxpath = '"/users/Actor HTTP/1.1"'
+ boxpath = "/users/Actor"
+ host = "epicyon.libreserver.org"
+ content_length = "0"
+ user_agent = "http.rb/4.4.1 (Mastodon/3.4.1; +https://octodon.social/)"
+ dateStr = 'Wed, 01 Sep 2021 16:11:10 GMT'
+ accept_encoding = 'gzip'
+ accept = 'application/activity+json, application/ld+json'
+ signature = 'keyId="https://octodon.social/actor#main-key",algorithm="rsa-sha256",headers="(request-target) host date accept",signature="Fe53PS9A2OSP4x+W/svhAjUKHBvnAR73Ez+H32au7DQklLk08Lvm8alLS7pCor28yfyx+DfZADgq6G1mLLRZo0OOnPFSog7DhdcygLhBUMS0KlT5KVGwUS0twjdiHv4OC83RiCr/ZySBgOv65YLHYmGCi5BIqSZJRkqi8+SLmLGESlNOEzKu+jIxOBYmEEdIpNrDeE5YrFKpfTC3vS2GnxGOo5J/4lB2h+dlUpso+sv5rDz1d1FsqRWK8waV74HUfLV+qbgYRceOTyZIi50vVqLvt9CTQesKZHG3GrrPfaBuvoUbR4MCM3BUvpB7EzL9F17Y+Ea9mo8zjqzZm8HaZQ=="'
publicKeyPem = \
'-----BEGIN PUBLIC KEY-----\n' + \
- 'MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3PcHF18xQPG4zNKKhkZf\n' + \
- 'rIi0I1vxJc8B7p24SVXSGoO4v5RiASNQYEpov/7orGS9dX93jd0JWkqpdTKnay4X\n' + \
- 'AIHamedcXBPX/7IbwfhV4jtPu+yFA+AGmybKe1z7zQpo4O9LlEGuW6tMR+L16q3/\n' + \
- 'x3nP5/9UPOClG7HbgxvA1UkaS1vllOj6pWBOenfbJNx6m7DmEtWFlBsauPTmHBqY\n' + \
- '2xfx/cFFS757WlPTA61gYpTqsRiyX5d2IWEyfIwhEJHYeK6EtblA1Pf2L1EjM2bw\n' + \
- 'qx2bgOEzG6ub48aQsTuASjXun39eEgMwNO02BEJXvsGr+fLRx/7xot2u2ekSwt3Z\n' + \
- 'bQKtUWSNbv+o/PV8hXlwp8gMjw/P+SsjbvYwDOFWa+xIvwNIBDkCk0quxokjiRp/\n' + \
- '800JxHxsuyUk4D5bLy43aveuxMziOjb1Knr8N3Xm7DLnFzpz2SaHSaeeDkFTddEZ\n' + \
- 'uPo8d/vqI09Owremr7ZVvJHSeOzTibfD0xg984pneWP3KGzHvXQF2MWPSM7SckHl\n' + \
- 'eTADO03FGdL8nMO1kV3EIKFudNBjIVZYIZFyYHLff8sw4fILK2/TShf+3jdjOPo+\n' + \
- 'R4zgBlH2EBd9HvYl6vE1nl43EG4G4UXdv1UTfKKnVEUbIiJ4u6AsEYD6aOCbGp9D\n' + \
- '3+CJDwridP1XwCLPoUChmAECAwEAAQ==\n' + \
+ 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1XT+ov/i4LDYuaXCwh4r\n' + \
+ '2rVfWtnz68wnFx3knwymwtRoAc/SFGzp9ye5ogG1uPcbe7MeirZHhaBICynPlL32\n' + \
+ 's9OYootI7MsQWn+vu7azxiXO7qcTPByvGcl0vpLhtT/ApmlMintkRTVXdzBdJVM0\n' + \
+ 'UsmYKg6U+IHNL+a1gURHGXep2Ih0BJMh4AaDbaID6jtpJZvbIkYgJ4IJucOe+A3T\n' + \
+ 'YPMwkBA84ew+hso+vKQfTunyDInuPQbEzrAzMJXEHS7IpBhdS4/cEox86BoDJ/q0\n' + \
+ 'KOEOUpUDniFYWb9k1+9B387OviRDLIcLxNZnf+bNq8d+CwEXY2xGsToBle/q74d8\n' + \
+ 'BwIDAQAB\n' + \
'-----END PUBLIC KEY-----\n'
- signature = 'signature: keyId="https://canic.us/friendica#main-key",algorithm="rsa-sha256",headers="(request-target) date host",signature="cm4IV/H2NaxrbMx1JlQd8rOqyqtKokaSBIDQUYJbP64arelpui+BAaYIEEQM2yn06Kp9quTz+hXgchdzUSJTSRFUwp3d2R0mnz7NR9EOt2UdzjSxafTC0bgyQdDHG++Wp8uBEx0MVol5fQjWZrK6JjLn7iF1YpyFC0i2HzY4ZnQjXv4J07t1mpBudqP13F7hkNVSfppRMDnkvd4qxn6zMOgISc/y5l52p/Qe0rbjVwCl1RbH9LFykP6/+Ii62+5VaLh8QjZCDot624P5ZceX9GemsdEoxAc7yLlhhGanpO9Wmel25y0Jg6dOt8CSRDQlAYvCyw7TC16Rm+JSLyS5ikslHaGnXiNnFV41uCHwXcRCBRJ7N7/U5Yd7xQ/UZ8uPBWYlRr/UUPBKFQU/pV6ZsiZkOHmcOy+qbgDZ7FOuemfc6xypf8uH1/MYdZf0P2cvxeViyHHunPyfA0zdYvQFH9KDe2174Wtd+TfC4RkHozY5LVpcrZpOgJFCYjzLjxUx34S6tf/YWonOLz4NxWgx4QfacdTaLirQuSIibT92nRdQIJcEm7JWj8efh6XkDIs54cKMm8Qb5Zk0CfpmM1bGuwJFZ0w7j3D9v/Q/mdYv7so4cG5jgNX6uiVJAp0PbNZkHvyu/T0v6iXAws09Xa2wnfNahyt3uuu/WVtYPlAngG8="'
headers = {
- "accept": 'application/activity+json, application/ld+json',
- "user-agent": "Friendica 'Red Hot Poker' 2021.01-1384; https://canic.us",
- "host": 'epicyon.libreserver.org',
- "date": 'Wed, 01 Sep 2021 15:41:51 GMT',
- "accept-encoding": 'deflate, gzip',
+ "user-agent": user_agent,
+ "content-length": content_length,
+ "host": host,
+ "date": dateStr,
+ "accept": accept,
+ "accept-encoding": accept_encoding,
"signature": signature
}
httpPrefix = 'https'
- debug = False
+ debug = True
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- boxpath, False, None,
- messageBodyJsonStr, debug, True)
+ boxpath, True, None,
+ '', debug, True)
def _testHttpSigNew():
@@ -5395,10 +5400,12 @@ def _translateOntology() -> None:
def runAllTests():
print('Running tests...')
+ _testHttpSigAuthorized()
updateDefaultThemesList(os.getcwd())
_translateOntology()
_testGetPriceFromString()
_testFunctions()
+ _testHttpSigAuthorized()
_testDateConversions()
_testAuthorizeSharedItems()
_testValidPassword()
From bc77b25520379d661b1c32bebab1f1792b25b9af Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 17:35:24 +0100
Subject: [PATCH 068/385] Add failing test for signed GET
---
tests.py | 46 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 35 insertions(+), 11 deletions(-)
diff --git a/tests.py b/tests.py
index 5be000b1b..945e9b4f1 100644
--- a/tests.py
+++ b/tests.py
@@ -161,8 +161,8 @@ thrBob = None
thrEve = None
-def _testHttpSigAuthorized():
- print('testHttpSigAuthorized')
+def _testHttpSignedGET():
+ print('testHttpSignedGET')
boxpath = '"/users/Actor HTTP/1.1"'
boxpath = "/users/Actor"
@@ -172,15 +172,35 @@ def _testHttpSigAuthorized():
dateStr = 'Wed, 01 Sep 2021 16:11:10 GMT'
accept_encoding = 'gzip'
accept = 'application/activity+json, application/ld+json'
- signature = 'keyId="https://octodon.social/actor#main-key",algorithm="rsa-sha256",headers="(request-target) host date accept",signature="Fe53PS9A2OSP4x+W/svhAjUKHBvnAR73Ez+H32au7DQklLk08Lvm8alLS7pCor28yfyx+DfZADgq6G1mLLRZo0OOnPFSog7DhdcygLhBUMS0KlT5KVGwUS0twjdiHv4OC83RiCr/ZySBgOv65YLHYmGCi5BIqSZJRkqi8+SLmLGESlNOEzKu+jIxOBYmEEdIpNrDeE5YrFKpfTC3vS2GnxGOo5J/4lB2h+dlUpso+sv5rDz1d1FsqRWK8waV74HUfLV+qbgYRceOTyZIi50vVqLvt9CTQesKZHG3GrrPfaBuvoUbR4MCM3BUvpB7EzL9F17Y+Ea9mo8zjqzZm8HaZQ=="'
+ signature = \
+ 'keyId="https://octodon.social/actor#main-key",' + \
+ 'algorithm="rsa-sha256",' + \
+ 'headers="(request-target) host date accept",' + \
+ 'signature="Fe53PS9A2OSP4x+W/svhA' + \
+ 'jUKHBvnAR73Ez+H32au7DQklLk08Lvm8al' + \
+ 'LS7pCor28yfyx+DfZADgq6G1mLLRZo0OOn' + \
+ 'PFSog7DhdcygLhBUMS0KlT5KVGwUS0tw' + \
+ 'jdiHv4OC83RiCr/ZySBgOv65YLHYmGCi5B' + \
+ 'IqSZJRkqi8+SLmLGESlNOEzKu+jIxOBY' + \
+ 'mEEdIpNrDeE5YrFKpfTC3vS2GnxGOo5J/4' + \
+ 'lB2h+dlUpso+sv5rDz1d1FsqRWK8waV7' + \
+ '4HUfLV+qbgYRceOTyZIi50vVqLvt9CTQes' + \
+ 'KZHG3GrrPfaBuvoUbR4MCM3BUvpB7EzL' + \
+ '9F17Y+Ea9mo8zjqzZm8HaZQ=="'
publicKeyPem = \
'-----BEGIN PUBLIC KEY-----\n' + \
- 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1XT+ov/i4LDYuaXCwh4r\n' + \
- '2rVfWtnz68wnFx3knwymwtRoAc/SFGzp9ye5ogG1uPcbe7MeirZHhaBICynPlL32\n' + \
- 's9OYootI7MsQWn+vu7azxiXO7qcTPByvGcl0vpLhtT/ApmlMintkRTVXdzBdJVM0\n' + \
- 'UsmYKg6U+IHNL+a1gURHGXep2Ih0BJMh4AaDbaID6jtpJZvbIkYgJ4IJucOe+A3T\n' + \
- 'YPMwkBA84ew+hso+vKQfTunyDInuPQbEzrAzMJXEHS7IpBhdS4/cEox86BoDJ/q0\n' + \
- 'KOEOUpUDniFYWb9k1+9B387OviRDLIcLxNZnf+bNq8d+CwEXY2xGsToBle/q74d8\n' + \
+ 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMII' + \
+ 'BCgKCAQEA1XT+ov/i4LDYuaXCwh4r\n' + \
+ '2rVfWtnz68wnFx3knwymwtRoAc/SFGzp9ye' + \
+ '5ogG1uPcbe7MeirZHhaBICynPlL32\n' + \
+ 's9OYootI7MsQWn+vu7azxiXO7qcTPByvGcl' + \
+ '0vpLhtT/ApmlMintkRTVXdzBdJVM0\n' + \
+ 'UsmYKg6U+IHNL+a1gURHGXep2Ih0BJMh4Aa' + \
+ 'DbaID6jtpJZvbIkYgJ4IJucOe+A3T\n' + \
+ 'YPMwkBA84ew+hso+vKQfTunyDInuPQbEzrA' + \
+ 'zMJXEHS7IpBhdS4/cEox86BoDJ/q0\n' + \
+ 'KOEOUpUDniFYWb9k1+9B387OviRDLIcLxNZ' + \
+ 'nf+bNq8d+CwEXY2xGsToBle/q74d8\n' + \
'BwIDAQAB\n' + \
'-----END PUBLIC KEY-----\n'
headers = {
@@ -197,6 +217,11 @@ def _testHttpSigAuthorized():
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
boxpath, True, None,
'', debug, True)
+ # Change a single character and the signature should fail
+ headers['date'] = headers['date'].replace(':10', ':11')
+ assert not verifyPostHeaders(httpPrefix, publicKeyPem, headers,
+ boxpath, True, None,
+ '', debug, True)
def _testHttpSigNew():
@@ -5400,12 +5425,10 @@ def _translateOntology() -> None:
def runAllTests():
print('Running tests...')
- _testHttpSigAuthorized()
updateDefaultThemesList(os.getcwd())
_translateOntology()
_testGetPriceFromString()
_testFunctions()
- _testHttpSigAuthorized()
_testDateConversions()
_testAuthorizeSharedItems()
_testValidPassword()
@@ -5461,6 +5484,7 @@ def runAllTests():
_testAddEmoji()
_testActorParsing()
_testHttpsig()
+ _testHttpSignedGET()
_testHttpSigNew()
_testCache()
_testThreads()
From 152febac88cfece52f790aa8f43dd2da3e7ba901 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:26:40 +0100
Subject: [PATCH 069/385] Attempt to obtain instance actor when using
commandline
---
epicyon.py | 73 +++++++++++++++++++++++++++---------------------------
posts.py | 44 ++++++++++++++++++++++++++------
2 files changed, 74 insertions(+), 43 deletions(-)
diff --git a/epicyon.py b/epicyon.py
index 3db9f427d..df40c7f8f 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -25,6 +25,7 @@ from roles import setRole
from webfinger import webfingerHandle
from bookmarks import sendBookmarkViaServer
from bookmarks import sendUndoBookmarkViaServer
+from posts import getInstanceActorKey
from posts import sendMuteViaServer
from posts import sendUndoMuteViaServer
from posts import c2sBoxJson
@@ -688,7 +689,7 @@ if args.posts:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getPublicPostsOfPerson(baseDir, nickname, domain, False, True,
proxyType, args.port, httpPrefix, debug,
__version__, args.language,
@@ -724,7 +725,7 @@ if args.postDomains:
domainList = []
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
domainList = getPublicPostDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -769,7 +770,7 @@ if args.postDomainsBlocked:
domainList = []
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
domainList = getPublicPostDomainsBlocked(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -812,7 +813,7 @@ if args.checkDomains:
maxBlockedDomains = 0
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
checkDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -833,7 +834,7 @@ if args.socnet:
proxyType = 'tor'
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
dotGraph = instancesGraph(baseDir, args.socnet,
proxyType, args.port,
httpPrefix, debug,
@@ -864,7 +865,7 @@ if args.postsraw:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getPublicPostsOfPerson(baseDir, nickname, domain, False, False,
proxyType, args.port, httpPrefix, debug,
__version__, args.language,
@@ -877,7 +878,7 @@ if args.json:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
None, debug, __version__, httpPrefix, None)
pprint(testJson)
@@ -1088,7 +1089,7 @@ if args.approve:
postLog = []
cachedWebfingers = {}
personCache = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualApproveFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1112,7 +1113,7 @@ if args.deny:
postLog = []
cachedWebfingers = {}
personCache = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualDenyFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1201,7 +1202,7 @@ if args.message:
replyTo = args.replyto
followersOnly = False
isArticle = False
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending post to ' + args.sendto)
sendPostViaServer(signingPrivateKeyPem, __version__,
@@ -1234,7 +1235,7 @@ if args.announce:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending announce/repeat of ' + args.announce)
sendAnnounceViaServer(baseDir, session, args.nickname, args.password,
@@ -1274,7 +1275,7 @@ if args.box:
args.port = 80
elif args.gnunet:
proxyType = 'gnunet'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
session = createSession(proxyType)
boxJson = c2sBoxJson(baseDir, session,
@@ -1331,7 +1332,7 @@ if args.itemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending shared item: ' + args.itemName)
sendShareViaServer(baseDir, session,
@@ -1370,7 +1371,7 @@ if args.undoItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of shared item: ' + args.undoItemName)
sendUndoShareViaServer(baseDir, session,
@@ -1428,7 +1429,7 @@ if args.wantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending wanted item: ' + args.wantedItemName)
sendWantedViaServer(baseDir, session,
@@ -1467,7 +1468,7 @@ if args.undoWantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of wanted item: ' + args.undoWantedItemName)
sendUndoWantedViaServer(baseDir, session,
@@ -1497,7 +1498,7 @@ if args.like:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending like of ' + args.like)
sendLikeViaServer(baseDir, session,
@@ -1526,7 +1527,7 @@ if args.undolike:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo like of ' + args.undolike)
sendUndoLikeViaServer(baseDir, session,
@@ -1556,7 +1557,7 @@ if args.bookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending bookmark of ' + args.bookmark)
sendBookmarkViaServer(baseDir, session,
@@ -1586,7 +1587,7 @@ if args.unbookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo bookmark of ' + args.unbookmark)
sendUndoBookmarkViaServer(baseDir, session,
@@ -1615,7 +1616,7 @@ if args.delete:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending delete request of ' + args.delete)
sendDeleteViaServer(baseDir, session,
@@ -1656,7 +1657,7 @@ if args.follow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendFollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1698,7 +1699,7 @@ if args.unfollow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendUnfollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1729,7 +1730,7 @@ if args.followingList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followingJson = \
getFollowingViaServer(baseDir, session,
@@ -1758,7 +1759,7 @@ if args.followersList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followersJson = \
getFollowersViaServer(baseDir, session,
@@ -1788,7 +1789,7 @@ if args.followRequestsList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followRequestsJson = \
getFollowRequestsViaServer(baseDir, session,
@@ -1836,7 +1837,7 @@ if args.migrations:
httpPrefix = 'https'
port = 443
session = createSession(proxyType)
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
ctr = migrateAccounts(baseDir, session,
httpPrefix, cachedWebfingers,
True, signingPrivateKeyPem)
@@ -1847,7 +1848,7 @@ if args.migrations:
sys.exit()
if args.actor:
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getActorJson(args.domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
sys.exit()
@@ -1925,7 +1926,7 @@ if args.followers:
nickname = domain
handle = nickname + '@' + domain
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
None, __version__, debug, False,
@@ -1971,7 +1972,7 @@ if args.followers:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followersList = \
downloadFollowCollection(signingPrivateKeyPem,
'followers', session,
@@ -2224,7 +2225,7 @@ if args.skill:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending ' + args.skill + ' skill level ' +
str(args.skillLevelPercent) + ' for ' + nickname)
@@ -2255,7 +2256,7 @@ if args.availability:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending availability status of ' + nickname +
' as ' + args.availability)
@@ -2365,7 +2366,7 @@ if args.block:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending block of ' + args.block)
sendBlockViaServer(baseDir, session, nickname, args.password,
@@ -2393,7 +2394,7 @@ if args.mute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending mute of ' + args.mute)
sendMuteViaServer(baseDir, session, nickname, args.password,
@@ -2421,7 +2422,7 @@ if args.unmute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo mute of ' + args.unmute)
sendUndoMuteViaServer(baseDir, session, nickname, args.password,
@@ -2461,7 +2462,7 @@ if args.unblock:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
- signingPrivateKeyPem = None
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo block of ' + args.unblock)
sendUndoBlockViaServer(baseDir, session, nickname, args.password,
diff --git a/posts.py b/posts.py
index d91d520e5..78dd2fecd 100644
--- a/posts.py
+++ b/posts.py
@@ -130,19 +130,49 @@ def noOfFollowersOnDomain(baseDir: str, handle: str,
return ctr
+def _getLocalPrivateKey(baseDir: str, nickname: str, domain: str) -> str:
+ """Returns the private key for a local account
+ """
+ handle = nickname + '@' + domain
+ keyFilename = baseDir + '/keys/private/' + handle.lower() + '.key'
+ if not os.path.isfile(keyFilename):
+ return None
+ with open(keyFilename, 'r') as pemFile:
+ return pemFile.read()
+ return None
+
+
+def getInstanceActorKey(baseDir: str, domain: str) -> str:
+ """Returns the private key for the instance actor used for
+ signing GET posts
+ """
+ return _getLocalPrivateKey(baseDir, 'inbox', domain)
+
+
+def _getLocalPublicKey(baseDir: str, nickname: str, domain: str) -> str:
+ """Returns the public key for a local account
+ """
+ handle = nickname + '@' + domain
+ keyFilename = baseDir + '/keys/public/' + handle.lower() + '.key'
+ if not os.path.isfile(keyFilename):
+ return None
+ with open(keyFilename, 'r') as pemFile:
+ return pemFile.read()
+ return None
+
+
def _getPersonKey(nickname: str, domain: str, baseDir: str,
keyType: str = 'public', debug: bool = False):
"""Returns the public or private key of a person
"""
- handle = nickname + '@' + domain
- keyFilename = baseDir + '/keys/' + keyType + '/' + handle.lower() + '.key'
- if not os.path.isfile(keyFilename):
+ if keyType == 'private':
+ keyPem = _getLocalPrivateKey(baseDir, nickname, domain)
+ else:
+ keyPem = _getLocalPublicKey(baseDir, nickname, domain)
+ if not keyPem:
if debug:
- print('DEBUG: private key file not found: ' + keyFilename)
+ print('DEBUG: ' + keyType + ' key file not found')
return ''
- keyPem = ''
- with open(keyFilename, 'r') as pemFile:
- keyPem = pemFile.read()
if len(keyPem) < 20:
if debug:
print('DEBUG: private key was too short: ' + keyPem)
From 88f44e3887b152dc57f065760327625806ae54a3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:40:27 +0100
Subject: [PATCH 070/385] Get instance actor key from commandline
---
epicyon.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
diff --git a/epicyon.py b/epicyon.py
index df40c7f8f..d1cd9101c 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -834,6 +834,8 @@ if args.socnet:
proxyType = 'tor'
if not args.language:
args.language = 'en'
+ if not args.domain:
+ args.domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
dotGraph = instancesGraph(baseDir, args.socnet,
proxyType, args.port,
@@ -878,6 +880,8 @@ if args.json:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
+ if not args.domain:
+ args.domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
None, debug, __version__, httpPrefix, None)
@@ -1089,6 +1093,8 @@ if args.approve:
postLog = []
cachedWebfingers = {}
personCache = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualApproveFollowRequest(session, baseDir,
httpPrefix,
@@ -1113,6 +1119,8 @@ if args.deny:
postLog = []
cachedWebfingers = {}
personCache = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualDenyFollowRequest(session, baseDir,
httpPrefix,
@@ -1202,6 +1210,8 @@ if args.message:
replyTo = args.replyto
followersOnly = False
isArticle = False
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending post to ' + args.sendto)
@@ -1235,6 +1245,8 @@ if args.announce:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending announce/repeat of ' + args.announce)
@@ -1275,6 +1287,8 @@ if args.box:
args.port = 80
elif args.gnunet:
proxyType = 'gnunet'
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
session = createSession(proxyType)
@@ -1332,6 +1346,8 @@ if args.itemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending shared item: ' + args.itemName)
@@ -1371,6 +1387,8 @@ if args.undoItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of shared item: ' + args.undoItemName)
@@ -1429,6 +1447,8 @@ if args.wantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending wanted item: ' + args.wantedItemName)
@@ -1468,6 +1488,8 @@ if args.undoWantedItemName:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of wanted item: ' + args.undoWantedItemName)
@@ -1498,6 +1520,8 @@ if args.like:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending like of ' + args.like)
@@ -1527,6 +1551,8 @@ if args.undolike:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo like of ' + args.undolike)
@@ -1557,6 +1583,8 @@ if args.bookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending bookmark of ' + args.bookmark)
@@ -1587,6 +1615,8 @@ if args.unbookmark:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo bookmark of ' + args.unbookmark)
@@ -1616,6 +1646,8 @@ if args.delete:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending delete request of ' + args.delete)
@@ -1657,6 +1689,8 @@ if args.follow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendFollowRequestViaServer(baseDir, session,
@@ -1699,6 +1733,8 @@ if args.unfollow:
followHttpPrefix = httpPrefix
if args.follow.startswith('https'):
followHttpPrefix = 'https'
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendUnfollowRequestViaServer(baseDir, session,
@@ -1730,6 +1766,8 @@ if args.followingList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followingJson = \
@@ -1759,6 +1797,8 @@ if args.followersList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followersJson = \
@@ -1789,6 +1829,8 @@ if args.followRequestsList:
personCache = {}
cachedWebfingers = {}
followHttpPrefix = httpPrefix
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followRequestsJson = \
@@ -1837,6 +1879,8 @@ if args.migrations:
httpPrefix = 'https'
port = 443
session = createSession(proxyType)
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
ctr = migrateAccounts(baseDir, session,
httpPrefix, cachedWebfingers,
@@ -1848,6 +1892,8 @@ if args.migrations:
sys.exit()
if args.actor:
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getActorJson(args.domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
@@ -2225,6 +2271,8 @@ if args.skill:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending ' + args.skill + ' skill level ' +
str(args.skillLevelPercent) + ' for ' + nickname)
@@ -2256,6 +2304,8 @@ if args.availability:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending availability status of ' + nickname +
' as ' + args.availability)
@@ -2366,6 +2416,8 @@ if args.block:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending block of ' + args.block)
@@ -2394,6 +2446,8 @@ if args.mute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending mute of ' + args.mute)
@@ -2422,6 +2476,8 @@ if args.unmute:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo mute of ' + args.unmute)
@@ -2462,6 +2518,8 @@ if args.unblock:
session = createSession(proxyType)
personCache = {}
cachedWebfingers = {}
+ if not domain:
+ domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo block of ' + args.unblock)
From 69adc3d4dde2fcf56d854b5e11656f15a3715775 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:44:22 +0100
Subject: [PATCH 071/385] Extra debug
---
epicyon.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/epicyon.py b/epicyon.py
index d1cd9101c..b1d5226ea 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -883,6 +883,8 @@ if args.json:
if not args.domain:
args.domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
+ if debug and signingPrivateKeyPem:
+ print('Obtained instance actor signing key')
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
None, debug, __version__, httpPrefix, None)
pprint(testJson)
@@ -1895,6 +1897,8 @@ if args.actor:
if not domain:
domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ if debug and signingPrivateKeyPem:
+ print('Obtained instance actor signing key')
getActorJson(args.domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
sys.exit()
From 68db5ce0901bfbc885dd8b054138839b14e0a371 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:46:07 +0100
Subject: [PATCH 072/385] More debug
---
epicyon.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/epicyon.py b/epicyon.py
index b1d5226ea..e166c4767 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -885,6 +885,8 @@ if args.json:
signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
if debug and signingPrivateKeyPem:
print('Obtained instance actor signing key')
+ else:
+ print('Did not obtain instance actor key for ' + args.domain)
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
None, debug, __version__, httpPrefix, None)
pprint(testJson)
@@ -1899,6 +1901,8 @@ if args.actor:
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
if debug and signingPrivateKeyPem:
print('Obtained instance actor signing key')
+ else:
+ print('Did not obtain instance actor key for ' + domain)
getActorJson(args.domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
sys.exit()
From 7cbc4cdda741a9e6fa463c1cf7cc71a3ec107360 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:48:44 +0100
Subject: [PATCH 073/385] More debug
---
epicyon.py | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/epicyon.py b/epicyon.py
index e166c4767..9576f1b81 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -883,10 +883,12 @@ if args.json:
if not args.domain:
args.domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
- if debug and signingPrivateKeyPem:
- print('Obtained instance actor signing key')
- else:
- print('Did not obtain instance actor key for ' + args.domain)
+ if debug:
+ print('baseDir: ' + str(baseDir))
+ if signingPrivateKeyPem:
+ print('Obtained instance actor signing key')
+ else:
+ print('Did not obtain instance actor key for ' + args.domain)
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
None, debug, __version__, httpPrefix, None)
pprint(testJson)
@@ -1899,10 +1901,12 @@ if args.actor:
if not domain:
domain = getConfigParam(baseDir, 'domain')
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
- if debug and signingPrivateKeyPem:
- print('Obtained instance actor signing key')
- else:
- print('Did not obtain instance actor key for ' + domain)
+ if debug:
+ print('baseDir: ' + str(baseDir))
+ if signingPrivateKeyPem:
+ print('Obtained instance actor signing key')
+ else:
+ print('Did not obtain instance actor key for ' + domain)
getActorJson(args.domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
sys.exit()
From 89f59fa71cbdf9bdd02f4c709a5f2ec6a581149e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 18:58:42 +0100
Subject: [PATCH 074/385] Debug
---
session.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/session.py b/session.py
index 1ce079984..4199f82e1 100644
--- a/session.py
+++ b/session.py
@@ -98,6 +98,8 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
if not signingPrivateKeyPem:
print("WARN: getJson requires secure fetch url: " + url)
else:
+ if debug:
+ print('Using signed GET')
return _getJsonSigned(session, url, domainFull,
sessionHeaders, sessionParams,
timeoutSec, signingPrivateKeyPem,
From 1398685e7445eb25ced67a3ea0059450166190bd Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 19:02:34 +0100
Subject: [PATCH 075/385] Set domain
---
epicyon.py | 4 ++--
session.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/epicyon.py b/epicyon.py
index 9576f1b81..453e6eb11 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -890,7 +890,7 @@ if args.json:
else:
print('Did not obtain instance actor key for ' + args.domain)
testJson = getJson(signingPrivateKeyPem, session, args.json, asHeader,
- None, debug, __version__, httpPrefix, None)
+ None, debug, __version__, httpPrefix, args.domain)
pprint(testJson)
sys.exit()
@@ -1907,7 +1907,7 @@ if args.actor:
print('Obtained instance actor signing key')
else:
print('Did not obtain instance actor key for ' + domain)
- getActorJson(args.domain, args.actor, args.http, args.gnunet,
+ getActorJson(domain, args.actor, args.http, args.gnunet,
debug, False, signingPrivateKeyPem)
sys.exit()
diff --git a/session.py b/session.py
index 4199f82e1..de772fa85 100644
--- a/session.py
+++ b/session.py
@@ -99,7 +99,7 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
print("WARN: getJson requires secure fetch url: " + url)
else:
if debug:
- print('Using signed GET')
+ print('Using signed GET for domain: ' + domainFull)
return _getJsonSigned(session, url, domainFull,
sessionHeaders, sessionParams,
timeoutSec, signingPrivateKeyPem,
From a751dbdf1493b6e686aab425192b5eb8c317a57a Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 19:46:28 +0100
Subject: [PATCH 076/385] Fixing signed get
---
httpsig.py | 4 ++--
session.py | 6 +++++-
tests.py | 11 ++++++-----
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 080e08371..5189b5713 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -56,7 +56,7 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
keyID = httpPrefix + '://' + domain + '/actor#main-key'
if not messageBodyJsonStr:
headers = {
- '(request-target)': f'post {path}',
+ '(request-target)': f'get {path}',
'host': toDomain,
'date': dateStr,
'accept': 'application/json'
@@ -201,7 +201,7 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
if not withDigest:
headers = {
- '(request-target)': f'post {path}',
+ '(request-target)': f'get {path}',
'host': headerDomain,
'date': dateStr,
'accept': contentType
diff --git a/session.py b/session.py
index de772fa85..6c1a2aacf 100644
--- a/session.py
+++ b/session.py
@@ -184,9 +184,13 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
print('Signed GET httpPrefix: ' + httpPrefix)
messageStr = ''
withDigest = False
+ if toDomainFull + '/' in url:
+ path = '/' + url.split(toDomainFull + '/')[1]
+ else:
+ path = '/actor'
signatureHeaderJson = \
createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
- toDomain, toPort, url, httpPrefix, withDigest,
+ toDomain, toPort, path, httpPrefix, withDigest,
messageStr)
print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
diff --git a/tests.py b/tests.py
index 945e9b4f1..be014979e 100644
--- a/tests.py
+++ b/tests.py
@@ -452,20 +452,21 @@ def _testHttpsigBase(withDigest):
boxpath, httpPrefix, messageBodyJsonStr)
headers['signature'] = signatureHeader
+ GETmethod = not withDigest
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- boxpath, False, None,
+ boxpath, GETmethod, None,
messageBodyJsonStr, False)
if withDigest:
# everything correct except for content-length
headers['content-length'] = str(contentLength + 2)
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- boxpath, False, None,
+ boxpath, GETmethod, None,
messageBodyJsonStr, False) is False
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- '/parambulator' + boxpath, False, None,
+ '/parambulator' + boxpath, GETmethod, None,
messageBodyJsonStr, False) is False
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- boxpath, True, None,
+ boxpath, not GETmethod, None,
messageBodyJsonStr, False) is False
if not withDigest:
# fake domain
@@ -490,7 +491,7 @@ def _testHttpsigBase(withDigest):
}
headers['signature'] = signatureHeader
assert verifyPostHeaders(httpPrefix, publicKeyPem, headers,
- boxpath, True, None,
+ boxpath, not GETmethod, None,
messageBodyJsonStr, False) is False
os.chdir(baseDir)
From 8b3a638d82261756887f06f2b325677b69c28a5c Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 20:12:48 +0100
Subject: [PATCH 077/385] Content type
---
httpsig.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/httpsig.py b/httpsig.py
index 5189b5713..96fc2d338 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -195,11 +195,11 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
messageBodyJsonStr: str) -> {}:
"""Note that the domain is the destination, not the sender
"""
- contentType = 'application/activity+json'
headerDomain = getFullDomain(toDomain, toPort)
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
if not withDigest:
+ contentType = 'application/activity+json, application/ld+json'
headers = {
'(request-target)': f'get {path}',
'host': headerDomain,
@@ -211,6 +211,7 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
domain, port, toDomain, toPort,
path, httpPrefix, None)
else:
+ contentType = 'application/activity+json'
bodyDigest = messageContentDigest(messageBodyJsonStr)
contentLength = len(messageBodyJsonStr)
headers = {
From 3b0b4076fb168864b1c0370af448d921c3372a79 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 20:29:26 +0100
Subject: [PATCH 078/385] Add content length to signed get
---
session.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/session.py b/session.py
index 6c1a2aacf..bf74fa96d 100644
--- a/session.py
+++ b/session.py
@@ -194,13 +194,15 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
messageStr)
print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
- if key == 'Accept' or key == 'User-Agent':
+ if key == 'Accept' or key == 'User-Agent' or \
+ key.lower() == 'content-length':
continue
sessionHeaders[key] = value
# avoid double accept
if sessionHeaders.get('Accept') and sessionHeaders.get('accept'):
del sessionHeaders['Accept']
sessionHeaders['Origin'] = domainFull
+ sessionHeaders['Content-Length'] = '0'
print('Signed GET sessionHeaders ' + str(sessionHeaders))
return _getJsonRequest(session, url, domainFull, sessionHeaders,
From ddacf7265748b6bd411174aab022049f3193bff6 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:02:36 +0100
Subject: [PATCH 079/385] Use function to load instance actor key
---
daemon.py | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/daemon.py b/daemon.py
index 1e1999c8f..8b33a6637 100644
--- a/daemon.py
+++ b/daemon.py
@@ -71,6 +71,7 @@ from person import removeAccount
from person import canRemovePost
from person import personSnooze
from person import personUnsnooze
+from posts import getInstanceActorKey
from posts import removePostInteractions
from posts import outboxMessageCreateWrap
from posts import getPinnedPostAsJson
@@ -16272,14 +16273,7 @@ def runDaemon(lowBandwidth: bool,
# signing key used for authorized fetch
# this is the instance actor private key
- instanceActorPrivateKeyFilename = \
- baseDir + '/keys/private/inbox@' + domain + '.key'
- if not os.path.isfile(instanceActorPrivateKeyFilename):
- print('ERROR: no instance actor private key for authorized fetch ' +
- instanceActorPrivateKeyFilename)
- return
- with open(instanceActorPrivateKeyFilename) as fp:
- httpd.signingPrivateKeyPem = fp.read()
+ httpd.signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
if not unitTest:
print('Creating inbox queue watchdog')
From 9c79aee5c820275c066d6ae0e86248cdf42c2c58 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:11:59 +0100
Subject: [PATCH 080/385] Title fields
---
session.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/session.py b/session.py
index bf74fa96d..f844393d4 100644
--- a/session.py
+++ b/session.py
@@ -197,10 +197,11 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
if key == 'Accept' or key == 'User-Agent' or \
key.lower() == 'content-length':
continue
- sessionHeaders[key] = value
+ sessionHeaders[key.title()] = value
# avoid double accept
if sessionHeaders.get('Accept') and sessionHeaders.get('accept'):
- del sessionHeaders['Accept']
+ sessionHeaders['Accept'] = sessionHeaders['accept']
+ del sessionHeaders['accept']
sessionHeaders['Origin'] = domainFull
sessionHeaders['Content-Length'] = '0'
print('Signed GET sessionHeaders ' + str(sessionHeaders))
From fed6d0c144e3e61d16870880926e38da25f155a7 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:14:16 +0100
Subject: [PATCH 081/385] Remove lower case fields
---
session.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/session.py b/session.py
index f844393d4..18815c464 100644
--- a/session.py
+++ b/session.py
@@ -198,6 +198,8 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
key.lower() == 'content-length':
continue
sessionHeaders[key.title()] = value
+ if sessionHeaders.get(key.lower()):
+ del sessionHeaders[key.lower()]
# avoid double accept
if sessionHeaders.get('Accept') and sessionHeaders.get('accept'):
sessionHeaders['Accept'] = sessionHeaders['accept']
From b66ab9e74a40498232a85bda6f901f7d85142c18 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:24:01 +0100
Subject: [PATCH 082/385] Field not needed
---
session.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/session.py b/session.py
index 18815c464..0d5df9a4d 100644
--- a/session.py
+++ b/session.py
@@ -194,6 +194,8 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
messageStr)
print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
+ if key.startswith('(') or key.startswith('*('):
+ continue
if key == 'Accept' or key == 'User-Agent' or \
key.lower() == 'content-length':
continue
From ddd8146f60bf3583a687120f7a692ceb429f211b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:55:54 +0100
Subject: [PATCH 083/385] Test with capitals
---
httpsig.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 96fc2d338..415a8015b 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -201,10 +201,10 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
if not withDigest:
contentType = 'application/activity+json, application/ld+json'
headers = {
- '(request-target)': f'get {path}',
- 'host': headerDomain,
- 'date': dateStr,
- 'accept': contentType
+ '(request-target)': f'GET {path}',
+ 'Host': headerDomain,
+ 'Date': dateStr,
+ 'Accept': contentType
}
signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname,
From aa36efb346cb3e67b8e43827dd88f71260362db2 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 21:57:54 +0100
Subject: [PATCH 084/385] Undo capitals
---
httpsig.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 415a8015b..96fc2d338 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -201,10 +201,10 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
if not withDigest:
contentType = 'application/activity+json, application/ld+json'
headers = {
- '(request-target)': f'GET {path}',
- 'Host': headerDomain,
- 'Date': dateStr,
- 'Accept': contentType
+ '(request-target)': f'get {path}',
+ 'host': headerDomain,
+ 'date': dateStr,
+ 'accept': contentType
}
signatureHeader = \
signPostHeaders(dateStr, privateKeyPem, nickname,
From edeb0d9f1e4b483669abfed8794c844754ef14be Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 1 Sep 2021 22:09:24 +0100
Subject: [PATCH 085/385] Extraneous logic
---
session.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/session.py b/session.py
index 0d5df9a4d..8851ff2a7 100644
--- a/session.py
+++ b/session.py
@@ -196,17 +196,9 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
for key, value in signatureHeaderJson.items():
if key.startswith('(') or key.startswith('*('):
continue
- if key == 'Accept' or key == 'User-Agent' or \
- key.lower() == 'content-length':
- continue
sessionHeaders[key.title()] = value
if sessionHeaders.get(key.lower()):
del sessionHeaders[key.lower()]
- # avoid double accept
- if sessionHeaders.get('Accept') and sessionHeaders.get('accept'):
- sessionHeaders['Accept'] = sessionHeaders['accept']
- del sessionHeaders['accept']
- sessionHeaders['Origin'] = domainFull
sessionHeaders['Content-Length'] = '0'
print('Signed GET sessionHeaders ' + str(sessionHeaders))
From 243b4bc310ea3f9c893f76e0475c2c91513866cc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 10:07:49 +0100
Subject: [PATCH 086/385] Alternative user agent
---
daemon.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/daemon.py b/daemon.py
index 8b33a6637..c141559ca 100644
--- a/daemon.py
+++ b/daemon.py
@@ -497,8 +497,13 @@ class PubServer(BaseHTTPRequestHandler):
"""
agentDomain = None
agentStr = None
+
if self.headers.get('User-Agent'):
agentStr = self.headers['User-Agent']
+ elif self.headers.get('user-agent'):
+ agentStr = self.headers['user-agent']
+
+ if agentStr:
# is this a web crawler? If so the block it
agentStrLower = agentStr.lower()
if 'bot/' in agentStrLower or 'bot-' in agentStrLower:
From 72b7ea6977e675ba98462fc3b89aa4931744a2be Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 10:46:18 +0100
Subject: [PATCH 087/385] Tidying of authorized fetch
---
daemon.py | 81 +++++++++++++++++++++++++++++++-----------------------
epicyon.py | 8 +++---
2 files changed, 50 insertions(+), 39 deletions(-)
diff --git a/daemon.py b/daemon.py
index c141559ca..9127fbbb7 100644
--- a/daemon.py
+++ b/daemon.py
@@ -586,20 +586,28 @@ class PubServer(BaseHTTPRequestHandler):
return False
return True
- def _fetchAuthenticated(self) -> bool:
+ def _authorizedFetch(self) -> bool:
"""http authentication of GET requests for json
"""
- if not self.server.authenticatedFetch:
+ if not self.server.authorizedFetch:
return True
+
+ signature = None
+ if self.headers.get('signature'):
+ signature = self.headers['signature']
+ elif self.headers.get('Signature'):
+ signature = self.headers['Signature']
+
# check that the headers are signed
- if not self.headers.get('signature'):
+ if not signature:
if self.server.debug:
- print('WARN: authenticated fetch, ' +
+ print('WARN: authorized fetch, ' +
'GET has no signature in headers')
return False
- # get the keyId
+
+ # get the keyId, which is typically the instance actor
keyId = None
- signatureParams = self.headers['signature'].split(',')
+ signatureParams = signature.split(',')
for signatureItem in signatureParams:
if signatureItem.startswith('keyId='):
if '"' in signatureItem:
@@ -607,23 +615,30 @@ class PubServer(BaseHTTPRequestHandler):
break
if not keyId:
if self.server.debug:
- print('WARN: authenticated fetch, ' +
+ print('WARN: authorized fetch, ' +
'failed to obtain keyId from signature')
return False
+
+ # remove #main-key
+ if '#' in keyId:
+ keyId = keyId.split('#')[0]
+
# is the keyId (actor) valid?
if not urlPermitted(keyId, self.server.federationList):
if self.server.debug:
print('Authorized fetch failed: ' + keyId +
' is not permitted')
return False
+
# make sure we have a session
if not self.server.session:
- print('DEBUG: creating new session during authenticated fetch')
+ print('DEBUG: creating new session during authorized fetch')
self.server.session = createSession(self.server.proxyType)
if not self.server.session:
print('ERROR: GET failed to create session during ' +
- 'authenticated fetch')
+ 'authorized fetch')
return False
+
# obtain the public key
pubKey = \
getPersonPubKey(self.server.baseDir, self.server.session, keyId,
@@ -633,21 +648,17 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signingPrivateKeyPem)
if not pubKey:
if self.server.debug:
- print('DEBUG: Authenticated fetch failed to ' +
+ print('DEBUG: Authorized fetch failed to ' +
'obtain public key for ' + keyId)
return False
- # it is assumed that there will be no message body on
- # authenticated fetches and also consequently no digest
- GETrequestBody = ''
- GETrequestDigest = None
+
# verify the GET request without any digest
- if verifyPostHeaders(self.server.httpPrefix,
- pubKey, self.headers,
- self.path, True,
- GETrequestDigest,
- GETrequestBody,
- self.server.debug):
+ if verifyPostHeaders(self.server.httpPrefix, pubKey, self.headers,
+ self.path, True, None, '', self.server.debug):
return True
+
+ if self.server.debug:
+ print('Authorized fetch failed for ' + keyId)
return False
def _login_headers(self, fileFormat: str, length: int,
@@ -7720,7 +7731,7 @@ class PubServer(BaseHTTPRequestHandler):
cookie, callingDomain, False)
self._write(msg)
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(repliesJson, ensure_ascii=False)
msg = msg.encode('utf-8')
protocolStr = 'application/json'
@@ -7814,7 +7825,7 @@ class PubServer(BaseHTTPRequestHandler):
'individual post done',
'post replies done')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(repliesJson,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -7917,7 +7928,7 @@ class PubServer(BaseHTTPRequestHandler):
'post replies done',
'show roles')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
rolesList = getActorRolesList(actorJson)
msg = json.dumps(rolesList,
ensure_ascii=False)
@@ -8025,7 +8036,7 @@ class PubServer(BaseHTTPRequestHandler):
'post roles done',
'show skills')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
actorSkillsList = \
getOccupationSkills(actorJson)
skills = getSkillsFromList(actorSkillsList)
@@ -8161,7 +8172,7 @@ class PubServer(BaseHTTPRequestHandler):
'done',
'show status')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(postJsonObject,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -9635,7 +9646,7 @@ class PubServer(BaseHTTPRequestHandler):
'show events done',
'show outbox')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(outboxFeed,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -9879,7 +9890,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return True
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(shares,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -9996,7 +10007,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 3')
return True
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(following,
ensure_ascii=False).encode('utf-8')
msglen = len(msg)
@@ -10113,7 +10124,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 4')
return True
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
msg = json.dumps(followers,
ensure_ascii=False).encode('utf-8')
msglen = len(msg)
@@ -10249,7 +10260,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 4 done',
'show profile posts')
else:
- if self._fetchAuthenticated():
+ if self._authorizedFetch():
acceptStr = self.headers['Accept']
msgStr = json.dumps(actorJson, ensure_ascii=False)
msg = msgStr.encode('utf-8')
@@ -13846,16 +13857,16 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return
- if not self._fetchAuthenticated():
+ if not self._authorizedFetch():
if self.server.debug:
- print('WARN: Unauthenticated GET')
+ print('WARN: Unauthorized GET')
self._404()
self.server.GETbusy = False
return
self._benchmarkGETtimings(GETstartTime, GETtimings,
'show profile posts done',
- 'authenticated fetch')
+ 'authorized fetch')
# check that the file exists
filename = self.server.baseDir + self.path
@@ -15829,7 +15840,7 @@ def runDaemon(lowBandwidth: bool,
httpPrefix: str = 'https',
fedList: [] = [],
maxMentions: int = 10, maxEmoji: int = 10,
- authenticatedFetch: bool = False,
+ authorizedFetch: bool = False,
proxyType: str = None, maxReplies: int = 64,
domainMaxPostsPerDay: int = 8640,
accountMaxPostsPerDay: int = 864,
@@ -16050,7 +16061,7 @@ def runDaemon(lowBandwidth: bool,
httpd.outboxThread = {}
httpd.newPostThread = {}
httpd.projectVersion = projectVersion
- httpd.authenticatedFetch = authenticatedFetch
+ httpd.authorizedFetch = authorizedFetch
# max POST size of 30M
httpd.maxPostLength = 1024 * 1024 * 30
httpd.maxMediaSize = httpd.maxPostLength
diff --git a/epicyon.py b/epicyon.py
index 453e6eb11..f2c9afbbb 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -407,10 +407,10 @@ parser.add_argument("--debug", type=str2bool, nargs='?',
parser.add_argument("--notificationSounds", type=str2bool, nargs='?',
const=True, default=True,
help="Play notification sounds")
-parser.add_argument("--authenticatedFetch", type=str2bool, nargs='?',
+parser.add_argument("--authorizedFetch", type=str2bool, nargs='?',
const=True, default=False,
- help="Enable authentication on GET requests" +
- " for json (authenticated fetch)")
+ help="Enable authorization on GET requests" +
+ " for json (authorized fetch)")
parser.add_argument("--instanceOnlySkillsSearch", type=str2bool, nargs='?',
const=True, default=False,
help="Skills searches only return " +
@@ -2993,7 +2993,7 @@ if __name__ == "__main__":
args.YTReplacementDomain,
port, proxyPort, httpPrefix,
federationList, args.maxMentions,
- args.maxEmoji, args.authenticatedFetch,
+ args.maxEmoji, args.authorizedFetch,
proxyType, args.maxReplies,
args.domainMaxPostsPerDay,
args.accountMaxPostsPerDay,
From 212b3341f39dda916204159ad9f09c07434feba3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 10:52:36 +0100
Subject: [PATCH 088/385] authorized
---
daemon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 9127fbbb7..e50d87d34 100644
--- a/daemon.py
+++ b/daemon.py
@@ -15879,7 +15879,7 @@ def runDaemon(lowBandwidth: bool,
print('serverAddress: ' + str(serverAddress))
return False
- # initialize authenticated fetch key
+ # initialize authorized fetch key
httpd.signingPrivateKeyPem = None
httpd.showNodeInfoAccounts = showNodeInfoAccounts
From 66410d9704b6ac20799327fac5bdfd1e9d0ca839 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 10:55:22 +0100
Subject: [PATCH 089/385] Authorized
---
daemon.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/daemon.py b/daemon.py
index e50d87d34..adc39078e 100644
--- a/daemon.py
+++ b/daemon.py
@@ -8393,7 +8393,7 @@ class PubServer(BaseHTTPRequestHandler):
'show status done',
'show inbox')
else:
- # don't need authenticated fetch here because
+ # don't need authorized fetch here because
# there is already the authorization check
msg = json.dumps(inboxFeed, ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -8527,7 +8527,7 @@ class PubServer(BaseHTTPRequestHandler):
'show inbox done',
'show dms')
else:
- # don't need authenticated fetch here because
+ # don't need authorized fetch here because
# there is already the authorization check
msg = json.dumps(inboxDMFeed, ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -8662,7 +8662,7 @@ class PubServer(BaseHTTPRequestHandler):
'show dms done',
'show replies 2')
else:
- # don't need authenticated fetch here because there is
+ # don't need authorized fetch here because there is
# already the authorization check
msg = json.dumps(inboxRepliesFeed,
ensure_ascii=False)
@@ -8796,7 +8796,7 @@ class PubServer(BaseHTTPRequestHandler):
'show replies 2 done',
'show media 2')
else:
- # don't need authenticated fetch here because there is
+ # don't need authorized fetch here because there is
# already the authorization check
msg = json.dumps(inboxMediaFeed,
ensure_ascii=False)
@@ -8930,7 +8930,7 @@ class PubServer(BaseHTTPRequestHandler):
'show media 2 done',
'show blogs 2')
else:
- # don't need authenticated fetch here because there is
+ # don't need authorized fetch here because there is
# already the authorization check
msg = json.dumps(inboxBlogsFeed,
ensure_ascii=False)
@@ -9073,7 +9073,7 @@ class PubServer(BaseHTTPRequestHandler):
'show blogs 2 done',
'show news 2')
else:
- # don't need authenticated fetch here because there is
+ # don't need authorized fetch here because there is
# already the authorization check
msg = json.dumps(inboxNewsFeed,
ensure_ascii=False)
@@ -9214,7 +9214,7 @@ class PubServer(BaseHTTPRequestHandler):
'show blogs 2 done',
'show news 2')
else:
- # don't need authenticated fetch here because there is
+ # don't need authorized fetch here because there is
# already the authorization check
msg = json.dumps(inboxFeaturesFeed,
ensure_ascii=False)
@@ -9515,7 +9515,7 @@ class PubServer(BaseHTTPRequestHandler):
'show shares 2 done',
'show bookmarks 2')
else:
- # don't need authenticated fetch here because
+ # don't need authorized fetch here because
# there is already the authorization check
msg = json.dumps(bookmarksFeed,
ensure_ascii=False)
@@ -9767,7 +9767,7 @@ class PubServer(BaseHTTPRequestHandler):
'show outbox done',
'show moderation')
else:
- # don't need authenticated fetch here because
+ # don't need authorized fetch here because
# there is already the authorization check
msg = json.dumps(moderationFeed,
ensure_ascii=False)
@@ -13882,7 +13882,7 @@ class PubServer(BaseHTTPRequestHandler):
None, callingDomain, False)
self._write(msg)
self._benchmarkGETtimings(GETstartTime, GETtimings,
- 'authenticated fetch',
+ 'authorized fetch',
'arbitrary json')
else:
if self.server.debug:
From e6fbee9ca8dcc73772efe8b9cddac28f4ab450f4 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 11:29:35 +0100
Subject: [PATCH 090/385] Handle none inputs
---
posts.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/posts.py b/posts.py
index 78dd2fecd..394ccd0ca 100644
--- a/posts.py
+++ b/posts.py
@@ -133,6 +133,8 @@ def noOfFollowersOnDomain(baseDir: str, handle: str,
def _getLocalPrivateKey(baseDir: str, nickname: str, domain: str) -> str:
"""Returns the private key for a local account
"""
+ if not domain or not nickname:
+ return None
handle = nickname + '@' + domain
keyFilename = baseDir + '/keys/private/' + handle.lower() + '.key'
if not os.path.isfile(keyFilename):
@@ -152,6 +154,8 @@ def getInstanceActorKey(baseDir: str, domain: str) -> str:
def _getLocalPublicKey(baseDir: str, nickname: str, domain: str) -> str:
"""Returns the public key for a local account
"""
+ if not domain or not nickname:
+ return None
handle = nickname + '@' + domain
keyFilename = baseDir + '/keys/public/' + handle.lower() + '.key'
if not os.path.isfile(keyFilename):
From 187a2b9dd56daf316f22e4af5a0d255bb04c0e46 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 12:20:16 +0100
Subject: [PATCH 091/385] Remove debug
---
daemon.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/daemon.py b/daemon.py
index adc39078e..e0e00afe5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11094,10 +11094,6 @@ class PubServer(BaseHTTPRequestHandler):
return True
def do_GET(self):
- if self.headers.get('Signature'):
- print('Signed HTTP GET: path: ' + self.path + ', ' +
- str(self.headers).replace('\n', ', '))
-
callingDomain = self.server.domainFull
if self.headers.get('Host'):
callingDomain = decodedHost(self.headers['Host'])
From 33578b0842f181092586dea2bdcc3b8da4d4c78b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 2 Sep 2021 14:02:07 +0100
Subject: [PATCH 092/385] Getting instance actor
---
person.py | 63 ++++++++++++++++++++++++++++++-------------------------
utils.py | 1 +
2 files changed, 36 insertions(+), 28 deletions(-)
diff --git a/person.py b/person.py
index 756bcd7a0..9c5876157 100644
--- a/person.py
+++ b/person.py
@@ -1305,41 +1305,48 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
if nickname == 'inbox':
nickname = domain
- handle = nickname + '@' + domain
- wfRequest = webfingerHandle(session, handle,
- httpPrefix, cachedWebfingers,
- None, __version__, debug,
- groupAccount, signingPrivateKeyPem)
- if not wfRequest:
- if not quiet:
- print('getActorJson Unable to webfinger ' + handle)
- return None, None
- if not isinstance(wfRequest, dict):
- if not quiet:
- print('getActorJson Webfinger for ' + handle +
- ' did not return a dict. ' + str(wfRequest))
- return None, None
-
- if not quiet:
- pprint(wfRequest)
-
personUrl = None
- if wfRequest.get('errors'):
- if not quiet or debug:
- print('getActorJson wfRequest error: ' +
- str(wfRequest['errors']))
- if hasUsersPath(handle):
- personUrl = originalActor
- else:
- if debug:
- print('No users path in ' + handle)
+ wfRequest = None
+ if '://' in originalActor and \
+ originalActor.lower().endswith('/actor'):
+ if debug:
+ print(originalActor + ' is an instance actor')
+ personUrl = originalActor
+ else:
+ handle = nickname + '@' + domain
+ wfRequest = webfingerHandle(session, handle,
+ httpPrefix, cachedWebfingers,
+ None, __version__, debug,
+ groupAccount, signingPrivateKeyPem)
+ if not wfRequest:
+ if not quiet:
+ print('getActorJson Unable to webfinger ' + handle)
return None, None
+ if not isinstance(wfRequest, dict):
+ if not quiet:
+ print('getActorJson Webfinger for ' + handle +
+ ' did not return a dict. ' + str(wfRequest))
+ return None, None
+
+ if not quiet:
+ pprint(wfRequest)
+
+ if wfRequest.get('errors'):
+ if not quiet or debug:
+ print('getActorJson wfRequest error: ' +
+ str(wfRequest['errors']))
+ if hasUsersPath(handle):
+ personUrl = originalActor
+ else:
+ if debug:
+ print('No users path in ' + handle)
+ return None, None
profileStr = 'https://www.w3.org/ns/activitystreams'
headersList = (
"activity+json", "ld+json", "jrd+json"
)
- if not personUrl:
+ if not personUrl and wfRequest:
personUrl = getUserUrl(wfRequest, 0, debug)
if nickname == domain:
paths = getUserPaths()
diff --git a/utils.py b/utils.py
index 063f48977..6b0276533 100644
--- a/utils.py
+++ b/utils.py
@@ -1648,6 +1648,7 @@ def _getReservedWords() -> str:
'tlblogs', 'tlfeatures',
'moderation', 'moderationaction',
'activity', 'undo', 'pinned',
+ 'actor', 'Actor',
'reply', 'replies', 'question', 'like',
'likes', 'users', 'statuses', 'tags',
'accounts', 'headers',
From 46b8f60dfcf183c2379508b8537164c9722571b1 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 10:07:18 +0100
Subject: [PATCH 093/385] Try using only the cache for inbox timeline
---
webapp_post.py | 5 ++++-
webapp_timeline.py | 6 +++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/webapp_post.py b/webapp_post.py
index e17300b0c..be70bf37e 100644
--- a/webapp_post.py
+++ b/webapp_post.py
@@ -1118,7 +1118,8 @@ def individualPostAsHtml(signingPrivateKeyPem: str,
showIcons: bool = False,
manuallyApprovesFollowers: bool = False,
showPublicOnly: bool = False,
- storeToCache: bool = True) -> str:
+ storeToCache: bool = True,
+ useCacheOnly: bool = False) -> str:
""" Shows a single post as html
"""
if not postJsonObject:
@@ -1176,6 +1177,8 @@ def individualPostAsHtml(signingPrivateKeyPem: str,
signingPrivateKeyPem)
if postHtml:
return postHtml
+ if useCacheOnly:
+ return ''
_logPostTiming(enableTimingLog, postStartTime, '4')
diff --git a/webapp_timeline.py b/webapp_timeline.py
index 2bac74ea6..14e1d45d5 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -810,6 +810,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
+ boxName + ' ' + str(timelineJson))
return ''
+ useCacheOnly = False
+ if boxName == 'inbox':
+ useCacheOnly = True
+
if timelineJson:
# if this is the media timeline then add an extra gallery container
if boxName == 'tlmedia':
@@ -876,7 +880,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
boxName != 'dm',
showIndividualPostIcons,
manuallyApproveFollowers,
- False, True)
+ False, True, useCacheOnly)
_logTimelineTiming(enableTimingLog,
timelineStartTime, boxName, '12')
From e819c28b037e58ca8318ecf498a2dbe0aa47cd17 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 12:30:23 +0100
Subject: [PATCH 094/385] Create html for posts sent to the outbox
---
daemon.py | 6 +-
inbox.py | 5 +-
outbox.py | 37 ++++++++++-
schedule.py | 6 +-
tests.py | 2 +-
webapp_post.py | 174 ++++++++++++++++++++++++++++++++++++-------------
6 files changed, 178 insertions(+), 52 deletions(-)
diff --git a/daemon.py b/daemon.py
index e0e00afe5..98d0c32bd 100644
--- a/daemon.py
+++ b/daemon.py
@@ -1195,7 +1195,11 @@ class PubServer(BaseHTTPRequestHandler):
self.server.sharedItemsFederatedDomains,
self.server.sharedItemFederationTokens,
self.server.lowBandwidth,
- self.server.signingPrivateKeyPem)
+ self.server.signingPrivateKeyPem,
+ self.server.peertubeInstances,
+ self.server.themeName,
+ self.server.maxLikeCount,
+ self.server.maxRecentPosts)
def _postToOutboxThread(self, messageJson: {}) -> bool:
"""Creates a thread to send a post
diff --git a/inbox.py b/inbox.py
index 16dd8314f..df51ab579 100644
--- a/inbox.py
+++ b/inbox.py
@@ -2025,6 +2025,7 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
if '/' in destinationFilename:
destinationFilename = destinationFilename.split('/')[-1]
+ written = False
if os.path.isfile(indexFilename):
try:
with open(indexFilename, 'r+') as indexFile:
@@ -2032,6 +2033,7 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
if destinationFilename + '\n' not in content:
indexFile.seek(0, 0)
indexFile.write(destinationFilename + '\n' + content)
+ written = True
return True
except Exception as e:
print('WARN: Failed to write entry to index ' + str(e))
@@ -2039,10 +2041,11 @@ def inboxUpdateIndex(boxname: str, baseDir: str, handle: str,
try:
with open(indexFilename, 'w+') as indexFile:
indexFile.write(destinationFilename + '\n')
+ written = True
except Exception as e:
print('WARN: Failed to write initial entry to index ' + str(e))
- return False
+ return written
def _updateLastSeen(baseDir: str, handle: str, actor: str) -> None:
diff --git a/outbox.py b/outbox.py
index 1e56d0446..de654dc77 100644
--- a/outbox.py
+++ b/outbox.py
@@ -40,6 +40,7 @@ from inbox import inboxUpdateIndex
from announce import outboxAnnounce
from announce import outboxUndoAnnounce
from follow import outboxUndoFollow
+from follow import followerApprovalActive
from skills import outboxSkills
from availability import outboxAvailability
from like import outboxLike
@@ -49,6 +50,7 @@ from bookmarks import outboxUndoBookmark
from delete import outboxDelete
from shares import outboxShareUpload
from shares import outboxUndoShareUpload
+from webapp_post import individualPostAsHtml
def _outboxPersonReceiveUpdate(recentPostsCache: {},
@@ -195,7 +197,10 @@ def postMessageToOutbox(session, translate: {},
sharedItemsFederatedDomains: [],
sharedItemFederationTokens: {},
lowBandwidth: bool,
- signingPrivateKeyPem: str) -> bool:
+ signingPrivateKeyPem: str,
+ peertubeInstances: str, theme: str,
+ maxLikeCount: int,
+ maxRecentPosts: int) -> bool:
"""post is received by the outbox
Client to server message post
https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery
@@ -425,6 +430,36 @@ def postMessageToOutbox(session, translate: {},
inboxUpdateIndex(boxNameIndex, baseDir,
postToNickname + '@' + domain,
savedFilename, debug)
+
+ # regenerate the html
+ useCacheOnly = False
+ pageNumber = 1
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, postToNickname, domain)
+ individualPostAsHtml(signingPrivateKeyPem,
+ False, recentPostsCache,
+ maxRecentPosts,
+ translate, pageNumber,
+ baseDir, session,
+ cachedWebfingers,
+ personCache,
+ postToNickname, domain, port,
+ messageJson, None, True,
+ allowDeletion,
+ httpPrefix, __version__,
+ boxNameIndex,
+ YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ theme, systemLanguage,
+ maxLikeCount,
+ boxNameIndex != 'dm',
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, useCacheOnly)
+
if outboxAnnounce(recentPostsCache,
baseDir, messageJson, debug):
if debug:
diff --git a/schedule.py b/schedule.py
index e29d46cff..165160ced 100644
--- a/schedule.py
+++ b/schedule.py
@@ -116,7 +116,11 @@ def _updatePostSchedule(baseDir: str, handle: str, httpd,
httpd.sharedItemsFederatedDomains,
httpd.sharedItemFederationTokens,
httpd.lowBandwidth,
- httpd.signingPrivateKeyPem):
+ httpd.signingPrivateKeyPem,
+ httpd.peertubeInstances,
+ httpd.themeName,
+ httpd.maxLikeCount,
+ httpd.maxRecentPosts):
indexLines.remove(line)
os.remove(postFilename)
continue
diff --git a/tests.py b/tests.py
index be014979e..4b5692f61 100644
--- a/tests.py
+++ b/tests.py
@@ -2874,7 +2874,7 @@ def testClientToServer():
showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
showTestBoxes('bob', bobInboxPath, bobOutboxPath)
assert len([name for name in os.listdir(bobOutboxPath)
- if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 3
+ if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 4
assert len([name for name in os.listdir(aliceInboxPath)
if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 1
print('EVENT: Post repeated')
diff --git a/webapp_post.py b/webapp_post.py
index be70bf37e..6c5ea8bad 100644
--- a/webapp_post.py
+++ b/webapp_post.py
@@ -245,13 +245,20 @@ def _getAvatarImageHtml(showAvatarOptions: bool,
avatarLink = ''
if '/users/news/' not in avatarUrl:
avatarLink = ' '
+ showProfileStr = 'Show profile'
+ if translate.get(showProfileStr):
+ showProfileStr = translate[showProfileStr]
avatarLink += \
' \n'
if showAvatarOptions and \
domainFull + '/users/' + nickname not in postActor:
+ showOptionsForThisPersonStr = 'Show options for this person'
+ if translate.get(showOptionsForThisPersonStr):
+ showOptionsForThisPersonStr = \
+ translate[showOptionsForThisPersonStr]
if '/users/news/' not in avatarUrl:
avatarLink = \
' \n'
avatarLink += \
' \n'
else:
# don't link to the person options for the news account
avatarLink += \
' \n'
return avatarLink.strip()
@@ -305,7 +312,9 @@ def _getReplyIconHtml(nickname: str, isPublicRepeat: bool,
replyToLink += pageNumberParam
replyStr = ''
- replyToThisPostStr = translate['Reply to this post']
+ replyToThisPostStr = 'Reply to this post'
+ if translate.get(replyToThisPostStr):
+ replyToThisPostStr = translate[replyToThisPostStr]
conversationStr = ''
if conversationId:
conversationStr = '?conversationId=' + conversationId
@@ -363,7 +372,9 @@ def _getEditIconHtml(baseDir: str, nickname: str, domainFull: str,
return editStr
if isBlogPost(postJsonObject):
- editBlogPostStr = translate['Edit blog post']
+ editBlogPostStr = 'Edit blog post'
+ if translate.get(editBlogPostStr):
+ editBlogPostStr = translate[editBlogPostStr]
if not isNewsPost(postJsonObject):
editStr += \
' ' + \
@@ -388,7 +399,9 @@ def _getEditIconHtml(baseDir: str, nickname: str, domainFull: str,
editBlogPostStr + '" alt="' + editBlogPostStr + \
' |" src="/icons/edit.png"/>\n'
elif isEvent:
- editEventStr = translate['Edit event']
+ editEventStr = 'Edit event'
+ if translate.get(editEventStr):
+ editEventStr = translate[editEventStr]
editStr += \
' ' + \
'\n'
+ '" title="' + muteThisPostStr + '">\n'
muteStr += \
' ' + \
' \n'
else:
+ undoMuteStr = 'Undo mute'
+ if translate.get(undoMuteStr):
+ undoMuteStr = translate[undoMuteStr]
muteStr = \
' \n'
+ timelinePostBookmark + '" title="' + undoMuteStr + '">\n'
muteStr += \
' ' + \
- ' \n'
return muteStr
@@ -622,16 +654,19 @@ def _getDeleteIconHtml(nickname: str, domainFull: str,
messageId.startswith(postActor))):
if '/users/' + nickname + '/' in messageId:
if not isNewsPost(postJsonObject):
+ deleteThisPostStr = 'Delete this post'
+ if translate.get(deleteThisPostStr):
+ deleteThisPostStr = translate[deleteThisPostStr]
deleteStr = \
' \n'
+ '" title="' + deleteThisPostStr + '">\n'
deleteStr += \
' ' + \
' \n'
return deleteStr
@@ -699,7 +734,10 @@ def _getBlogCitationsHtml(boxName: str,
'' + tagJson['name'] + ' \n'
if citationsStr:
- citationsStr = '' + translate['Citations'] + ':
' + \
+ translatedCitationsStr = 'Citations'
+ if translate.get(translatedCitationsStr):
+ translatedCitationsStr = translate[translatedCitationsStr]
+ citationsStr = '' + translatedCitationsStr + ':
' + \
'\n'
return citationsStr
@@ -707,9 +745,12 @@ def _getBlogCitationsHtml(boxName: str,
def _boostOwnPostHtml(translate: {}) -> str:
"""The html title for announcing your own post
"""
+ announcesStr = 'announces'
+ if translate.get(announcesStr):
+ announcesStr = translate[announcesStr]
return ' \n'
@@ -719,9 +760,12 @@ def _announceUnattributedHtml(translate: {},
"""Returns the html for an announce title where there
is no attribution on the announced post
"""
+ announcesStr = 'announces'
+ if translate.get(announcesStr):
+ announcesStr = translate[announcesStr]
return ' \n' + \
' \n' + \
' \n' \
' ' \
' \n \n'
return (titleStr, replyAvatarImageInPost,
@@ -842,9 +893,12 @@ def _getPostTitleAnnounceHtml(baseDir: str,
def _replyToYourselfHtml(translate: {}) -> str:
"""Returns html for a title which is a reply to yourself
"""
+ replyingToThemselvesStr = 'replying to themselves'
+ if translate.get(replyingToThemselvesStr):
+ replyingToThemselvesStr = translate[replyingToThemselvesStr]
return ' \n'
@@ -853,9 +907,12 @@ def _replyToUnknownHtml(translate: {},
postJsonObject: {}) -> str:
"""Returns the html title for a reply to an unknown handle
"""
+ replyingToStr = 'replying to'
+ if translate.get(replyingToStr):
+ replyingToStr = translate[replyingToStr]
return ' \n' + \
' \n' + \
' \n' + \
' \n' + \
' \n' + \
' \n \n'
@@ -1542,7 +1608,10 @@ def individualPostAsHtml(signingPrivateKeyPem: str,
postIsSensitive = postJsonObject['object']['sensitive']
else:
# add a generic summary if none is provided
- postJsonObject['object']['summary'] = translate['Sensitive']
+ sensitiveStr = 'Sensitive'
+ if translate.get(sensitiveStr):
+ sensitiveStr = translate[sensitiveStr]
+ postJsonObject['object']['summary'] = sensitiveStr
# add an extra line if there is a content warning,
# for better vertical spacing on mobile
@@ -1594,7 +1663,10 @@ def individualPostAsHtml(signingPrivateKeyPem: str,
else:
objectContent = contentStr
else:
- objectContent = '🔒 ' + translate['Encrypted']
+ encryptedStr = 'Encrypted'
+ if translate.get(encryptedStr):
+ encryptedStr = translate[encryptedStr]
+ objectContent = '🔒 ' + encryptedStr
objectContent = '' + objectContent + ' '
@@ -1711,9 +1783,11 @@ def htmlIndividualPost(cssCache: {},
likedByDomain, likedByPort = getDomainFromActor(likedBy)
likedByDomain = getFullDomain(likedByDomain, likedByPort)
likedByHandle = likedByNickname + '@' + likedByDomain
+ likedByStr = 'Liked by'
+ if translate.get(likedByStr):
+ likedByStr = translate[likedByStr]
postStr += \
- '' + translate['Liked by'] + \
- ' @' + \
+ '' + likedByStr + ' @' + \
likedByHandle + ' \n'
domainFull = getFullDomain(domain, port)
@@ -1726,10 +1800,16 @@ def htmlIndividualPost(cssCache: {},
' \n'
if not isFollowingActor(baseDir, nickname, domainFull, likedBy):
+ translateFollowStr = 'Follow'
+ if translate.get(translateFollowStr):
+ translateFollowStr = translate[translateFollowStr]
followStr += ' ' + translate['Follow'] + ' \n'
+ 'name="submitSearch">' + translateFollowStr + '\n'
+ goBackStr = 'Go Back'
+ if translate.get(goBackStr):
+ goBackStr = translate[goBackStr]
followStr += ' ' + translate['Go Back'] + ' \n'
+ 'name="submitBack">' + goBackStr + '\n'
followStr += ' \n'
postStr += followStr + '
\n'
From 2b038256382dd124d4376282f874f2d4e3f191d3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 13:00:23 +0100
Subject: [PATCH 095/385] Move likes update to like module
---
daemon.py | 2 +-
inbox.py | 2 +-
like.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
utils.py | 61 -------------------------------------------------
4 files changed, 69 insertions(+), 64 deletions(-)
diff --git a/daemon.py b/daemon.py
index 98d0c32bd..aec07c50a 100644
--- a/daemon.py
+++ b/daemon.py
@@ -228,6 +228,7 @@ from categories import setHashtagCategory
from categories import updateHashtagCategories
from languages import getActorLanguages
from languages import setActorLanguages
+from like import updateLikesCollection
from utils import replaceUsersWithAt
from utils import localActorUrl
from utils import isfloat
@@ -265,7 +266,6 @@ from utils import isSystemAccount
from utils import setConfigParam
from utils import getConfigParam
from utils import removeIdEnding
-from utils import updateLikesCollection
from utils import undoLikesCollectionEntry
from utils import deletePost
from utils import isBlogPost
diff --git a/inbox.py b/inbox.py
index df51ab579..cc1dc105e 100644
--- a/inbox.py
+++ b/inbox.py
@@ -14,6 +14,7 @@ import time
import random
from linked_data_sig import verifyJsonSignature
from languages import understoodPostLanguage
+from like import updateLikesCollection
from utils import getUserPaths
from utils import getBaseContentFromPost
from utils import acctDir
@@ -43,7 +44,6 @@ from utils import deletePost
from utils import removeModerationPostFromIndex
from utils import loadJson
from utils import saveJson
-from utils import updateLikesCollection
from utils import undoLikesCollectionEntry
from utils import hasGroupType
from utils import localActorUrl
diff --git a/like.py b/like.py
index b0617c942..e928e6bcb 100644
--- a/like.py
+++ b/like.py
@@ -7,6 +7,8 @@ __email__ = "bob@freedombone.net"
__status__ = "Production"
__module_group__ = "ActivityPub"
+import os
+from pprint import pprint
from utils import removeDomainPort
from utils import hasObjectDict
from utils import hasUsersPath
@@ -16,10 +18,13 @@ from utils import urlPermitted
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import locatePost
-from utils import updateLikesCollection
from utils import undoLikesCollectionEntry
from utils import hasGroupType
from utils import localActorUrl
+from utils import loadJson
+from utils import saveJson
+from utils import removePostFromCache
+from utils import getCachedPostFilename
from posts import sendSignedJson
from session import postJson
from webfinger import webfingerHandle
@@ -407,3 +412,64 @@ def outboxUndoLike(recentPostsCache: {},
domain, debug)
if debug:
print('DEBUG: post undo liked via c2s - ' + postFilename)
+
+
+def updateLikesCollection(recentPostsCache: {},
+ baseDir: str, postFilename: str,
+ objectUrl: str, actor: str,
+ nickname: str, domain: str, debug: bool) -> None:
+ """Updates the likes collection within a post
+ """
+ postJsonObject = loadJson(postFilename)
+ if not postJsonObject:
+ return
+ # remove any cached version of this post so that the
+ # like icon is changed
+ removePostFromCache(postJsonObject, recentPostsCache)
+ cachedPostFilename = getCachedPostFilename(baseDir, nickname,
+ domain, postJsonObject)
+ if cachedPostFilename:
+ if os.path.isfile(cachedPostFilename):
+ os.remove(cachedPostFilename)
+
+ if not hasObjectDict(postJsonObject):
+ if debug:
+ pprint(postJsonObject)
+ print('DEBUG: post ' + objectUrl + ' has no object')
+ return
+ if not objectUrl.endswith('/likes'):
+ objectUrl = objectUrl + '/likes'
+ if not postJsonObject['object'].get('likes'):
+ if debug:
+ print('DEBUG: Adding initial like to ' + objectUrl)
+ likesJson = {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ 'id': objectUrl,
+ 'type': 'Collection',
+ "totalItems": 1,
+ 'items': [{
+ 'type': 'Like',
+ 'actor': actor
+ }]
+ }
+ postJsonObject['object']['likes'] = likesJson
+ else:
+ if not postJsonObject['object']['likes'].get('items'):
+ postJsonObject['object']['likes']['items'] = []
+ for likeItem in postJsonObject['object']['likes']['items']:
+ if likeItem.get('actor'):
+ if likeItem['actor'] == actor:
+ # already liked
+ return
+ newLike = {
+ 'type': 'Like',
+ 'actor': actor
+ }
+ postJsonObject['object']['likes']['items'].append(newLike)
+ itlen = len(postJsonObject['object']['likes']['items'])
+ postJsonObject['object']['likes']['totalItems'] = itlen
+
+ if debug:
+ print('DEBUG: saving post with likes added')
+ pprint(postJsonObject)
+ saveJson(postJsonObject, postFilename)
diff --git a/utils.py b/utils.py
index 6b0276533..749c89c98 100644
--- a/utils.py
+++ b/utils.py
@@ -2065,67 +2065,6 @@ def undoLikesCollectionEntry(recentPostsCache: {},
saveJson(postJsonObject, postFilename)
-def updateLikesCollection(recentPostsCache: {},
- baseDir: str, postFilename: str,
- objectUrl: str, actor: str,
- nickname: str, domain: str, debug: bool) -> None:
- """Updates the likes collection within a post
- """
- postJsonObject = loadJson(postFilename)
- if not postJsonObject:
- return
- # remove any cached version of this post so that the
- # like icon is changed
- removePostFromCache(postJsonObject, recentPostsCache)
- cachedPostFilename = getCachedPostFilename(baseDir, nickname,
- domain, postJsonObject)
- if cachedPostFilename:
- if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
-
- if not hasObjectDict(postJsonObject):
- if debug:
- pprint(postJsonObject)
- print('DEBUG: post ' + objectUrl + ' has no object')
- return
- if not objectUrl.endswith('/likes'):
- objectUrl = objectUrl + '/likes'
- if not postJsonObject['object'].get('likes'):
- if debug:
- print('DEBUG: Adding initial like to ' + objectUrl)
- likesJson = {
- "@context": "https://www.w3.org/ns/activitystreams",
- 'id': objectUrl,
- 'type': 'Collection',
- "totalItems": 1,
- 'items': [{
- 'type': 'Like',
- 'actor': actor
- }]
- }
- postJsonObject['object']['likes'] = likesJson
- else:
- if not postJsonObject['object']['likes'].get('items'):
- postJsonObject['object']['likes']['items'] = []
- for likeItem in postJsonObject['object']['likes']['items']:
- if likeItem.get('actor'):
- if likeItem['actor'] == actor:
- # already liked
- return
- newLike = {
- 'type': 'Like',
- 'actor': actor
- }
- postJsonObject['object']['likes']['items'].append(newLike)
- itlen = len(postJsonObject['object']['likes']['items'])
- postJsonObject['object']['likes']['totalItems'] = itlen
-
- if debug:
- print('DEBUG: saving post with likes added')
- pprint(postJsonObject)
- saveJson(postJsonObject, postFilename)
-
-
def undoAnnounceCollectionEntry(recentPostsCache: {},
baseDir: str, postFilename: str,
actor: str, domain: str, debug: bool) -> None:
From 0b63b0fadcb1943cafca0db1707cbe4efa17eb51 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 13:44:20 +0100
Subject: [PATCH 096/385] Regenerate html after like/unlike button
---
daemon.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++----
like.py | 1 +
2 files changed, 67 insertions(+), 4 deletions(-)
diff --git a/daemon.py b/daemon.py
index aec07c50a..12c950059 100644
--- a/daemon.py
+++ b/daemon.py
@@ -96,6 +96,7 @@ from inbox import runInboxQueue
from inbox import runInboxQueueWatchdog
from inbox import savePostToInboxQueue
from inbox import populateReplies
+from follow import followerApprovalActive
from follow import isFollowingActor
from follow import getFollowingFeed
from follow import sendFollowRequest
@@ -186,6 +187,7 @@ from webapp_confirm import htmlConfirmFollow
from webapp_confirm import htmlConfirmUnfollow
from webapp_post import htmlPostReplies
from webapp_post import htmlIndividualPost
+from webapp_post import individualPostAsHtml
from webapp_profile import htmlEditProfile
from webapp_profile import htmlProfileAfterSearch
from webapp_profile import htmlProfile
@@ -229,6 +231,7 @@ from categories import updateHashtagCategories
from languages import getActorLanguages
from languages import setActorLanguages
from like import updateLikesCollection
+from utils import isDM
from utils import replaceUsersWithAt
from utils import localActorUrl
from utils import isfloat
@@ -7136,11 +7139,40 @@ class PubServer(BaseHTTPRequestHandler):
if debug:
print('Updating likes for ' + likedPostFilename)
updateLikesCollection(self.server.recentPostsCache,
- baseDir,
- likedPostFilename, likeUrl,
- likeActor,
- self.postToNickname, domain,
+ baseDir, likedPostFilename, likeUrl,
+ likeActor, self.postToNickname, domain,
debug)
+ if debug:
+ print('Regenerating html post for changed likes collection')
+ pageNumber = 1
+ likedPostJson = loadJson(likedPostFilename)
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, likedPostJson, None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, 'inbox',
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(likedPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('like.png'):
del self.server.iconsCache['like.png']
@@ -7244,6 +7276,36 @@ class PubServer(BaseHTTPRequestHandler):
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('like_inactive.png'):
del self.server.iconsCache['like_inactive.png']
+ if debug:
+ print('Regenerating html post for changed likes collection')
+ pageNumber = 1
+ likedPostJson = loadJson(likedPostFilename)
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, likedPostJson, None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, 'inbox',
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(likedPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
# send out the undo like to followers
self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
self.server.GETbusy = False
diff --git a/like.py b/like.py
index e928e6bcb..55ef0ebbb 100644
--- a/like.py
+++ b/like.py
@@ -423,6 +423,7 @@ def updateLikesCollection(recentPostsCache: {},
postJsonObject = loadJson(postFilename)
if not postJsonObject:
return
+
# remove any cached version of this post so that the
# like icon is changed
removePostFromCache(postJsonObject, recentPostsCache)
From 192018cde6cdbc7d36c5bc02cdb553f72b0e9bb0 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 13:50:12 +0100
Subject: [PATCH 097/385] Only try once
---
daemon.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/daemon.py b/daemon.py
index 12c950059..e8c75c3e1 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7145,7 +7145,7 @@ class PubServer(BaseHTTPRequestHandler):
if debug:
print('Regenerating html post for changed likes collection')
pageNumber = 1
- likedPostJson = loadJson(likedPostFilename)
+ likedPostJson = loadJson(likedPostFilename, 0, 1)
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir, self.postToNickname, domain)
@@ -7279,7 +7279,7 @@ class PubServer(BaseHTTPRequestHandler):
if debug:
print('Regenerating html post for changed likes collection')
pageNumber = 1
- likedPostJson = loadJson(likedPostFilename)
+ likedPostJson = loadJson(likedPostFilename, 0, 1)
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir, self.postToNickname, domain)
From e036cc15decd3987e28a4587340a0498cf23a90d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 13:51:46 +0100
Subject: [PATCH 098/385] Check that json was returned
---
daemon.py | 110 ++++++++++++++++++++++++++++--------------------------
1 file changed, 58 insertions(+), 52 deletions(-)
diff --git a/daemon.py b/daemon.py
index e8c75c3e1..ec6488c29 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7146,32 +7146,35 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
pageNumber = 1
likedPostJson = loadJson(likedPostFilename, 0, 1)
- showIndividualPostIcons = True
- manuallyApproveFollowers = \
- followerApprovalActive(baseDir, self.postToNickname, domain)
- individualPostAsHtml(self.server.signingPrivateKeyPem, True,
- self.server.recentPostsCache,
- self.server.maxRecentPosts,
- self.server.translate,
- pageNumber, baseDir,
- self.server.session,
- self.server.cachedWebfingers,
- self.server.personCache,
- self.postToNickname, domain,
- self.server.port, likedPostJson, None, True,
- self.server.allowDeletion,
- httpPrefix, __version__, 'inbox',
- self.server.YTReplacementDomain,
- self.server.showPublishedDateOnly,
- self.server.peertubeInstances,
- self.server.allowLocalNetworkAccess,
- self.server.themeName,
- self.server.systemLanguage,
- self.server.maxLikeCount,
- not isDM(likedPostJson),
- showIndividualPostIcons,
- manuallyApproveFollowers,
- False, True, False)
+ if likedPostJson:
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, likedPostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, 'inbox',
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(likedPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('like.png'):
@@ -7280,32 +7283,35 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
pageNumber = 1
likedPostJson = loadJson(likedPostFilename, 0, 1)
- showIndividualPostIcons = True
- manuallyApproveFollowers = \
- followerApprovalActive(baseDir, self.postToNickname, domain)
- individualPostAsHtml(self.server.signingPrivateKeyPem, True,
- self.server.recentPostsCache,
- self.server.maxRecentPosts,
- self.server.translate,
- pageNumber, baseDir,
- self.server.session,
- self.server.cachedWebfingers,
- self.server.personCache,
- self.postToNickname, domain,
- self.server.port, likedPostJson, None, True,
- self.server.allowDeletion,
- httpPrefix, __version__, 'inbox',
- self.server.YTReplacementDomain,
- self.server.showPublishedDateOnly,
- self.server.peertubeInstances,
- self.server.allowLocalNetworkAccess,
- self.server.themeName,
- self.server.systemLanguage,
- self.server.maxLikeCount,
- not isDM(likedPostJson),
- showIndividualPostIcons,
- manuallyApproveFollowers,
- False, True, False)
+ if likedPostJson:
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, likedPostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, 'inbox',
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(likedPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
# send out the undo like to followers
self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
self.server.GETbusy = False
From 1711faaec952b5ef482d7a7b5ec2b9744b3a6ec0 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 13:55:34 +0100
Subject: [PATCH 099/385] Don't allow downloads
---
daemon.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/daemon.py b/daemon.py
index ec6488c29..b1740fba5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7144,14 +7144,14 @@ class PubServer(BaseHTTPRequestHandler):
debug)
if debug:
print('Regenerating html post for changed likes collection')
- pageNumber = 1
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
+ pageNumber = 1
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
self.postToNickname, domain)
- individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
self.server.recentPostsCache,
self.server.maxRecentPosts,
self.server.translate,
@@ -7288,7 +7288,7 @@ class PubServer(BaseHTTPRequestHandler):
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
self.postToNickname, domain)
- individualPostAsHtml(self.server.signingPrivateKeyPem, True,
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
self.server.recentPostsCache,
self.server.maxRecentPosts,
self.server.translate,
From 5a4a2a4ab234e29661957be017a1a5d221bdb104 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 14:01:35 +0100
Subject: [PATCH 100/385] Tidying
---
daemon.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/daemon.py b/daemon.py
index b1740fba5..690ad3563 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7146,6 +7146,7 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
+ boxName = 'inbox'
pageNumber = 1
showIndividualPostIcons = True
manuallyApproveFollowers = \
@@ -7163,7 +7164,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, 'inbox',
+ httpPrefix, __version__, boxName,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7281,9 +7282,10 @@ class PubServer(BaseHTTPRequestHandler):
del self.server.iconsCache['like_inactive.png']
if debug:
print('Regenerating html post for changed likes collection')
- pageNumber = 1
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
+ boxName = 'inbox'
+ pageNumber = 1
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
@@ -7300,7 +7302,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, 'inbox',
+ httpPrefix, __version__, boxName,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
From 2a89554bb00958207d59c40726f759139c20513d Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 15:15:03 +0100
Subject: [PATCH 101/385] Debug
---
daemon.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 690ad3563..057805741 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7146,6 +7146,9 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
+ print('Liked post json: ' + str(likedPostJson))
+ print('Liked post nickname: ' +
+ self.postToNickname + ' ' + domain)
boxName = 'inbox'
pageNumber = 1
showIndividualPostIcons = True
@@ -7176,7 +7179,8 @@ class PubServer(BaseHTTPRequestHandler):
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
-
+ else:
+ print('WARN: Liked post not found: ' + likedPostFilename)
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('like.png'):
del self.server.iconsCache['like.png']
@@ -7314,6 +7318,8 @@ class PubServer(BaseHTTPRequestHandler):
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
+ else:
+ print('WARN: Unliked post not found: ' + likedPostFilename)
# send out the undo like to followers
self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
self.server.GETbusy = False
From f5e8cd1620eb13d0259c43b1a43f913b18acd1f7 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 15:16:40 +0100
Subject: [PATCH 102/385] Debug
---
daemon.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/daemon.py b/daemon.py
index 057805741..a3d30c0e3 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7146,9 +7146,13 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, likedPostJson)
print('Liked post json: ' + str(likedPostJson))
print('Liked post nickname: ' +
self.postToNickname + ' ' + domain)
+ print('Liked post cache: ' + str(cachedPostFilename))
boxName = 'inbox'
pageNumber = 1
showIndividualPostIcons = True
From 1a3c47bf02f89aec17e14d2d36632eea650793de Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 15:21:28 +0100
Subject: [PATCH 103/385] Use page number and timeline params
---
daemon.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/daemon.py b/daemon.py
index a3d30c0e3..7e1ff7ce9 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7153,8 +7153,6 @@ class PubServer(BaseHTTPRequestHandler):
print('Liked post nickname: ' +
self.postToNickname + ' ' + domain)
print('Liked post cache: ' + str(cachedPostFilename))
- boxName = 'inbox'
- pageNumber = 1
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
@@ -7171,7 +7169,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, boxName,
+ httpPrefix, __version__, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7292,8 +7290,6 @@ class PubServer(BaseHTTPRequestHandler):
print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
- boxName = 'inbox'
- pageNumber = 1
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
@@ -7310,7 +7306,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, boxName,
+ httpPrefix, __version__, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
From 4347ff75749f9bc2488cc5dcee199226e0a55c5e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 15:24:14 +0100
Subject: [PATCH 104/385] Debug
---
daemon.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/daemon.py b/daemon.py
index 7e1ff7ce9..9a516b5c5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7130,20 +7130,21 @@ class PubServer(BaseHTTPRequestHandler):
'to': [actorLiked],
'object': likeUrl
}
+ print('Locating liked post')
# directly like the post file
likedPostFilename = locatePost(baseDir,
self.postToNickname,
domain,
likeUrl)
if likedPostFilename:
- if debug:
- print('Updating likes for ' + likedPostFilename)
+ #if debug:
+ print('Updating likes for ' + likedPostFilename)
updateLikesCollection(self.server.recentPostsCache,
baseDir, likedPostFilename, likeUrl,
likeActor, self.postToNickname, domain,
debug)
- if debug:
- print('Regenerating html post for changed likes collection')
+ #if debug:
+ print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
cachedPostFilename = \
From 68c6241b442f4b3524e5ce77c15821b58080b014 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 15:56:01 +0100
Subject: [PATCH 105/385] Regenerate html for muted posts
---
daemon.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 105 insertions(+), 2 deletions(-)
diff --git a/daemon.py b/daemon.py
index 9a516b5c5..892d105c5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7137,13 +7137,13 @@ class PubServer(BaseHTTPRequestHandler):
domain,
likeUrl)
if likedPostFilename:
- #if debug:
+ # if debug:
print('Updating likes for ' + likedPostFilename)
updateLikesCollection(self.server.recentPostsCache,
baseDir, likedPostFilename, likeUrl,
likeActor, self.postToNickname, domain,
debug)
- #if debug:
+ # if debug:
print('Regenerating html post for changed likes collection')
likedPostJson = loadJson(likedPostFilename, 0, 1)
if likedPostJson:
@@ -7636,12 +7636,64 @@ class PubServer(BaseHTTPRequestHandler):
timelineStr = path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr = timelineStr.split('?')[0]
+ pageNumber = 1
+ if '?page=' in path:
+ pageNumberStr = path.split('?page=')[1]
+ if '?' in pageNumberStr:
+ pageNumberStr = pageNumberStr.split('?')[0]
+ if '#' in pageNumberStr:
+ pageNumberStr = pageNumberStr.split('#')[0]
+ if pageNumberStr.isdigit():
+ pageNumber = int(pageNumberStr)
actor = \
httpPrefix + '://' + domainFull + path.split('?mute=')[0]
nickname = getNicknameFromActor(actor)
mutePost(baseDir, nickname, domain, port,
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
+ muteFilename = locatePost(baseDir, nickname, domain, muteUrl)
+ if muteFilename:
+ print('Regenerating html post for changed mute status')
+ mutePostJson = loadJson(muteFilename, 0, 1)
+ if mutePostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, mutePostJson)
+ print('Muted post json: ' + str(mutePostJson))
+ print('Muted post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Muted post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, mutePostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(mutePostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Muted post not found: ' + muteFilename)
+
self.server.GETbusy = False
if callingDomain.endswith('.onion') and onionDomain:
actor = \
@@ -7681,12 +7733,63 @@ class PubServer(BaseHTTPRequestHandler):
timelineStr = path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr = timelineStr.split('?')[0]
+ pageNumber = 1
+ if '?page=' in path:
+ pageNumberStr = path.split('?page=')[1]
+ if '?' in pageNumberStr:
+ pageNumberStr = pageNumberStr.split('?')[0]
+ if '#' in pageNumberStr:
+ pageNumberStr = pageNumberStr.split('#')[0]
+ if pageNumberStr.isdigit():
+ pageNumber = int(pageNumberStr)
actor = \
httpPrefix + '://' + domainFull + path.split('?unmute=')[0]
nickname = getNicknameFromActor(actor)
unmutePost(baseDir, nickname, domain, port,
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
+ muteFilename = locatePost(baseDir, nickname, domain, muteUrl)
+ if muteFilename:
+ print('Regenerating html post for changed unmute status')
+ mutePostJson = loadJson(muteFilename, 0, 1)
+ if mutePostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, mutePostJson)
+ print('Unmuted post json: ' + str(mutePostJson))
+ print('Unmuted post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Unmuted post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, mutePostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(mutePostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Unmuted post not found: ' + muteFilename)
self.server.GETbusy = False
if callingDomain.endswith('.onion') and onionDomain:
actor = \
From a54527e00bd5a6c0fc84c4b35e50543e7c293525 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 16:08:28 +0100
Subject: [PATCH 106/385] Regenerate html for bookmarked posts
---
daemon.py | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/daemon.py b/daemon.py
index 892d105c5..ebb3fc89b 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7409,6 +7409,49 @@ class PubServer(BaseHTTPRequestHandler):
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('bookmark.png'):
del self.server.iconsCache['bookmark.png']
+ bookmarkFilename = \
+ locatePost(baseDir, self.postToNickname, domain, bookmarkUrl)
+ if bookmarkFilename:
+ print('Regenerating html post for changed bookmark')
+ bookmarkPostJson = loadJson(bookmarkFilename, 0, 1)
+ if bookmarkPostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, bookmarkPostJson)
+ print('Bookmarked post json: ' + str(bookmarkPostJson))
+ print('Bookmarked post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Bookmarked post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, bookmarkPostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(bookmarkPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Bookmarked post not found: ' + bookmarkFilename)
# self._postToOutbox(bookmarkJson, self.server.projectVersion, None)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
@@ -7497,6 +7540,49 @@ class PubServer(BaseHTTPRequestHandler):
del self.server.iconsCache['bookmark_inactive.png']
# self._postToOutbox(undoBookmarkJson,
# self.server.projectVersion, None)
+ bookmarkFilename = \
+ locatePost(baseDir, self.postToNickname, domain, bookmarkUrl)
+ if bookmarkFilename:
+ print('Regenerating html post for changed unbookmark')
+ bookmarkPostJson = loadJson(bookmarkFilename, 0, 1)
+ if bookmarkPostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, bookmarkPostJson)
+ print('Unbookmarked post json: ' + str(bookmarkPostJson))
+ print('Unbookmarked post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Unbookmarked post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, bookmarkPostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, __version__, timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(bookmarkPostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Unbookmarked post not found: ' + bookmarkFilename)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7651,7 +7737,8 @@ class PubServer(BaseHTTPRequestHandler):
mutePost(baseDir, nickname, domain, port,
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
- muteFilename = locatePost(baseDir, nickname, domain, muteUrl)
+ muteFilename = \
+ locatePost(baseDir, self.postToNickname, domain, muteUrl)
if muteFilename:
print('Regenerating html post for changed mute status')
mutePostJson = loadJson(muteFilename, 0, 1)
@@ -7748,7 +7835,8 @@ class PubServer(BaseHTTPRequestHandler):
unmutePost(baseDir, nickname, domain, port,
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
- muteFilename = locatePost(baseDir, nickname, domain, muteUrl)
+ muteFilename = \
+ locatePost(baseDir, self.postToNickname, domain, muteUrl)
if muteFilename:
print('Regenerating html post for changed unmute status')
mutePostJson = loadJson(muteFilename, 0, 1)
From fd3a06956d278d2c2024ac37cccde8d3b1933232 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 16:49:00 +0100
Subject: [PATCH 107/385] Use nickname
---
daemon.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/daemon.py b/daemon.py
index ebb3fc89b..23eee7a38 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7738,13 +7738,13 @@ class PubServer(BaseHTTPRequestHandler):
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
muteFilename = \
- locatePost(baseDir, self.postToNickname, domain, muteUrl)
+ locatePost(baseDir, nickname, domain, muteUrl)
if muteFilename:
print('Regenerating html post for changed mute status')
mutePostJson = loadJson(muteFilename, 0, 1)
if mutePostJson:
cachedPostFilename = \
- getCachedPostFilename(baseDir, self.postToNickname,
+ getCachedPostFilename(baseDir, nickname,
domain, mutePostJson)
print('Muted post json: ' + str(mutePostJson))
print('Muted post nickname: ' +
@@ -7753,7 +7753,7 @@ class PubServer(BaseHTTPRequestHandler):
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
- self.postToNickname, domain)
+ nickname, domain)
individualPostAsHtml(self.server.signingPrivateKeyPem, False,
self.server.recentPostsCache,
self.server.maxRecentPosts,
@@ -7762,7 +7762,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.session,
self.server.cachedWebfingers,
self.server.personCache,
- self.postToNickname, domain,
+ nickname, domain,
self.server.port, mutePostJson,
None, True,
self.server.allowDeletion,
@@ -7836,13 +7836,13 @@ class PubServer(BaseHTTPRequestHandler):
httpPrefix, muteUrl,
self.server.recentPostsCache, debug)
muteFilename = \
- locatePost(baseDir, self.postToNickname, domain, muteUrl)
+ locatePost(baseDir, nickname, domain, muteUrl)
if muteFilename:
print('Regenerating html post for changed unmute status')
mutePostJson = loadJson(muteFilename, 0, 1)
if mutePostJson:
cachedPostFilename = \
- getCachedPostFilename(baseDir, self.postToNickname,
+ getCachedPostFilename(baseDir, nickname,
domain, mutePostJson)
print('Unmuted post json: ' + str(mutePostJson))
print('Unmuted post nickname: ' +
@@ -7851,7 +7851,7 @@ class PubServer(BaseHTTPRequestHandler):
showIndividualPostIcons = True
manuallyApproveFollowers = \
followerApprovalActive(baseDir,
- self.postToNickname, domain)
+ nickname, domain)
individualPostAsHtml(self.server.signingPrivateKeyPem, False,
self.server.recentPostsCache,
self.server.maxRecentPosts,
@@ -7860,7 +7860,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.session,
self.server.cachedWebfingers,
self.server.personCache,
- self.postToNickname, domain,
+ nickname, domain,
self.server.port, mutePostJson,
None, True,
self.server.allowDeletion,
From 5be6608610b8da71d03a91292db9136ad43e3b5e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 16:51:24 +0100
Subject: [PATCH 108/385] Use nickname
---
daemon.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/daemon.py b/daemon.py
index 23eee7a38..15c94b2a0 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7748,7 +7748,7 @@ class PubServer(BaseHTTPRequestHandler):
domain, mutePostJson)
print('Muted post json: ' + str(mutePostJson))
print('Muted post nickname: ' +
- self.postToNickname + ' ' + domain)
+ nickname + ' ' + domain)
print('Muted post cache: ' + str(cachedPostFilename))
showIndividualPostIcons = True
manuallyApproveFollowers = \
@@ -7846,7 +7846,7 @@ class PubServer(BaseHTTPRequestHandler):
domain, mutePostJson)
print('Unmuted post json: ' + str(mutePostJson))
print('Unmuted post nickname: ' +
- self.postToNickname + ' ' + domain)
+ nickname + ' ' + domain)
print('Unmuted post cache: ' + str(cachedPostFilename))
showIndividualPostIcons = True
manuallyApproveFollowers = \
From 12dfd89ba92ef129286ea930b9f4f7dc1b1b5ef2 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 17:02:29 +0100
Subject: [PATCH 109/385] Update likes collection after sending
---
daemon.py | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)
diff --git a/daemon.py b/daemon.py
index 15c94b2a0..39ad0c6e5 100644
--- a/daemon.py
+++ b/daemon.py
@@ -7130,7 +7130,11 @@ class PubServer(BaseHTTPRequestHandler):
'to': [actorLiked],
'object': likeUrl
}
- print('Locating liked post')
+
+ # send out the like to followers
+ self._postToOutbox(likeJson, self.server.projectVersion, None)
+
+ print('Locating liked post ' + likeUrl)
# directly like the post file
likedPostFilename = locatePost(baseDir,
self.postToNickname,
@@ -7170,7 +7174,9 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix,
+ self.server.projectVersion,
+ timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7190,8 +7196,7 @@ class PubServer(BaseHTTPRequestHandler):
else:
print('WARN: unable to locate file for liked post ' +
likeUrl)
- # send out the like to followers
- self._postToOutbox(likeJson, self.server.projectVersion, None)
+
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7273,6 +7278,10 @@ class PubServer(BaseHTTPRequestHandler):
'object': likeUrl
}
}
+
+ # send out the undo like to followers
+ self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
+
# directly undo the like within the post file
likedPostFilename = locatePost(baseDir,
self.postToNickname,
@@ -7307,7 +7316,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, likedPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix,
+ self.server.projectVersion, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7321,8 +7331,7 @@ class PubServer(BaseHTTPRequestHandler):
False, True, False)
else:
print('WARN: Unliked post not found: ' + likedPostFilename)
- # send out the undo like to followers
- self._postToOutbox(undoLikeJson, self.server.projectVersion, None)
+
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
@@ -7438,7 +7447,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, bookmarkPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix, self.server.projectVersion,
+ timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7569,7 +7579,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, bookmarkPostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix,
+ self.server.projectVersion, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7766,7 +7777,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, mutePostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix,
+ self.server.projectVersion, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
@@ -7864,7 +7876,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.port, mutePostJson,
None, True,
self.server.allowDeletion,
- httpPrefix, __version__, timelineStr,
+ httpPrefix,
+ self.server.projectVersion, timelineStr,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
self.server.peertubeInstances,
From d932b750948b7291ca5fbe2960268a72e348414a Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 18:49:16 +0100
Subject: [PATCH 110/385] Sending announce
---
daemon.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/daemon.py b/daemon.py
index 39ad0c6e5..7d655e2ee 100644
--- a/daemon.py
+++ b/daemon.py
@@ -71,6 +71,7 @@ from person import removeAccount
from person import canRemovePost
from person import personSnooze
from person import personUnsnooze
+from posts import savePostToBox
from posts import getInstanceActorKey
from posts import removePostInteractions
from posts import outboxMessageCreateWrap
@@ -89,6 +90,7 @@ from posts import createDirectMessagePost
from posts import populateRepliesJson
from posts import addToField
from posts import expireCache
+from inbox import inboxUpdateIndex
from inbox import clearQueueItems
from inbox import inboxPermittedMessage
from inbox import inboxMessageHasParams
@@ -6730,11 +6732,72 @@ class PubServer(BaseHTTPRequestHandler):
debug,
self.server.projectVersion,
self.server.signingPrivateKeyPem)
+ announceFilename = None
if announceJson:
+ # save the announce straight to the outbox
+ # This is because the subsequent send is within a separate thread
+ # but the html still needs to be generated before this call ends
+ announceId = removeIdEnding(announceJson['id'])
+ announceFilename = \
+ savePostToBox(baseDir, httpPrefix, announceId,
+ self.postToNickname, domainFull,
+ announceJson, 'outbox')
+ # also copy the post id to the inbox index
+ inboxUpdateIndex('inbox', baseDir,
+ self.postToNickname + '@' + domain,
+ announceFilename, debug)
+
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('repeat.png'):
del self.server.iconsCache['repeat.png']
+
+ # send out the announce within a separate thread
self._postToOutboxThread(announceJson)
+
+ # generate the html for the announce
+ if announceFilename:
+ print('Generating html post for announce')
+ announcePostJson = loadJson(announceFilename, 0, 1)
+ if announcePostJson:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, announcePostJson)
+ print('Announced post json: ' + str(announcePostJson))
+ print('Announced post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Announced post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, announcePostJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, self.server.projectVersion,
+ timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(announcePostJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Announced post not found: ' + announceFilename)
+
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
actorPathStr = \
From a80aa22a21fb1b28815b5c310e6036c57c5ae357 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 19:22:27 +0100
Subject: [PATCH 111/385] Update inbox and outbox
---
daemon.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/daemon.py b/daemon.py
index 7d655e2ee..127cada9c 100644
--- a/daemon.py
+++ b/daemon.py
@@ -6743,9 +6743,11 @@ class PubServer(BaseHTTPRequestHandler):
self.postToNickname, domainFull,
announceJson, 'outbox')
# also copy the post id to the inbox index
- inboxUpdateIndex('inbox', baseDir,
- self.postToNickname + '@' + domain,
- announceFilename, debug)
+ indexes = ['outbox', 'inbox']
+ for boxNameIndex in indexes:
+ inboxUpdateIndex(boxNameIndex, baseDir,
+ self.postToNickname + '@' + domain,
+ announceFilename, debug)
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('repeat.png'):
From 36077696a88acc7319a584bf1d872751a5e4f96e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 19:57:06 +0100
Subject: [PATCH 112/385] Version number
---
daemon.py | 51 ++++++++++++++++++++++++++++++++++++---------------
1 file changed, 36 insertions(+), 15 deletions(-)
diff --git a/daemon.py b/daemon.py
index 127cada9c..117a0c499 100644
--- a/daemon.py
+++ b/daemon.py
@@ -462,7 +462,8 @@ class PubServer(BaseHTTPRequestHandler):
if messageJson:
# name field contains the answer
messageJson['object']['name'] = answer
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion, nickname):
postFilename = \
locatePost(self.server.baseDir, nickname,
self.server.domain, messageId)
@@ -648,7 +649,7 @@ class PubServer(BaseHTTPRequestHandler):
pubKey = \
getPersonPubKey(self.server.baseDir, self.server.session, keyId,
self.server.personCache, self.server.debug,
- __version__, self.server.httpPrefix,
+ self.server.projectVersion, self.server.httpPrefix,
self.server.domain, self.server.onionDomain,
self.server.signingPrivateKeyPem)
if not pubKey:
@@ -1226,7 +1227,8 @@ class PubServer(BaseHTTPRequestHandler):
print('Creating outbox thread')
self.server.outboxThread[accountOutboxThreadName] = \
threadWithTrace(target=self._postToOutbox,
- args=(messageJson.copy(), __version__, None),
+ args=(messageJson.copy(),
+ self.server.projectVersion, None),
daemon=True)
print('Starting outbox thread')
self.server.outboxThread[accountOutboxThreadName].start()
@@ -5683,7 +5685,8 @@ class PubServer(BaseHTTPRequestHandler):
}
print('Sending actor update: ' + str(updateActorJson))
self._postToOutbox(updateActorJson,
- __version__, nickname)
+ self.server.projectVersion,
+ nickname)
# deactivate the account
if fields.get('deactivateThisAccount'):
@@ -7747,7 +7750,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.translate, pageNumber,
self.server.session, baseDir,
deleteUrl, httpPrefix,
- __version__, self.server.cachedWebfingers,
+ self.server.projectVersion,
+ self.server.cachedWebfingers,
self.server.personCache, callingDomain,
self.server.YTReplacementDomain,
self.server.showPublishedDateOnly,
@@ -14508,7 +14512,9 @@ class PubServer(BaseHTTPRequestHandler):
pinPost(self.server.baseDir,
nickname, self.server.domain, contentStr)
return 1
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
populateReplies(self.server.baseDir,
self.server.httpPrefix,
self.server.domainFull,
@@ -14581,7 +14587,9 @@ class PubServer(BaseHTTPRequestHandler):
if messageJson:
if fields['schedulePost']:
return 1
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
refreshNewswire(self.server.baseDir)
populateReplies(self.server.baseDir,
self.server.httpPrefix,
@@ -14721,7 +14729,9 @@ class PubServer(BaseHTTPRequestHandler):
if messageJson:
if fields['schedulePost']:
return 1
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
populateReplies(self.server.baseDir,
self.server.httpPrefix,
self.server.domain,
@@ -14770,7 +14780,9 @@ class PubServer(BaseHTTPRequestHandler):
if messageJson:
if fields['schedulePost']:
return 1
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
populateReplies(self.server.baseDir,
self.server.httpPrefix,
self.server.domain,
@@ -14825,7 +14837,9 @@ class PubServer(BaseHTTPRequestHandler):
return 1
print('Sending new DM to ' +
str(messageJson['object']['to']))
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
populateReplies(self.server.baseDir,
self.server.httpPrefix,
self.server.domain,
@@ -14876,7 +14890,9 @@ class PubServer(BaseHTTPRequestHandler):
return 1
print('DEBUG: new reminder to ' +
str(messageJson['object']['to']))
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
return 1
else:
return -1
@@ -14906,7 +14922,9 @@ class PubServer(BaseHTTPRequestHandler):
self.server.systemLanguage,
self.server.lowBandwidth)
if messageJson:
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
return 1
else:
return -1
@@ -14947,7 +14965,9 @@ class PubServer(BaseHTTPRequestHandler):
if messageJson:
if self.server.debug:
print('DEBUG: new Question')
- if self._postToOutbox(messageJson, __version__, nickname):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion,
+ nickname):
return 1
return -1
elif postType == 'newshare' or postType == 'newwanted':
@@ -15947,7 +15967,8 @@ class PubServer(BaseHTTPRequestHandler):
# https://www.w3.org/TR/activitypub/#object-without-create
if self.outboxAuthenticated:
- if self._postToOutbox(messageJson, __version__, None):
+ if self._postToOutbox(messageJson,
+ self.server.projectVersion, None):
if messageJson.get('id'):
locnStr = removeIdEnding(messageJson['id'])
self.headers['Location'] = locnStr
@@ -16551,7 +16572,7 @@ def runDaemon(lowBandwidth: bool,
print('Creating expire thread for shared items')
httpd.thrSharesExpire = \
threadWithTrace(target=runSharesExpire,
- args=(__version__, baseDir), daemon=True)
+ args=(projectVersion, baseDir), daemon=True)
if not unitTest:
httpd.thrSharesExpireWatchdog = \
threadWithTrace(target=runSharesExpireWatchdog,
From e97dc8c8ceefbd23899bee8651b5b65a64ca6983 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 20:57:22 +0100
Subject: [PATCH 113/385] Use full post id
---
daemon.py | 83 +++++++++++++++++++++++++++----------------------------
1 file changed, 40 insertions(+), 43 deletions(-)
diff --git a/daemon.py b/daemon.py
index 117a0c499..32c0b4f88 100644
--- a/daemon.py
+++ b/daemon.py
@@ -6740,13 +6740,14 @@ class PubServer(BaseHTTPRequestHandler):
# save the announce straight to the outbox
# This is because the subsequent send is within a separate thread
# but the html still needs to be generated before this call ends
- announceId = removeIdEnding(announceJson['id'])
+ # announceId = removeIdEnding(announceJson['id'])
+ announceId = announceJson['id']
announceFilename = \
savePostToBox(baseDir, httpPrefix, announceId,
self.postToNickname, domainFull,
announceJson, 'outbox')
# also copy the post id to the inbox index
- indexes = ['outbox', 'inbox']
+ indexes = ['inbox']
for boxNameIndex in indexes:
inboxUpdateIndex(boxNameIndex, baseDir,
self.postToNickname + '@' + domain,
@@ -6760,48 +6761,44 @@ class PubServer(BaseHTTPRequestHandler):
self._postToOutboxThread(announceJson)
# generate the html for the announce
- if announceFilename:
+ if announceJson and announceFilename:
print('Generating html post for announce')
- announcePostJson = loadJson(announceFilename, 0, 1)
- if announcePostJson:
- cachedPostFilename = \
- getCachedPostFilename(baseDir, self.postToNickname,
- domain, announcePostJson)
- print('Announced post json: ' + str(announcePostJson))
- print('Announced post nickname: ' +
- self.postToNickname + ' ' + domain)
- print('Announced post cache: ' + str(cachedPostFilename))
- showIndividualPostIcons = True
- manuallyApproveFollowers = \
- followerApprovalActive(baseDir,
- self.postToNickname, domain)
- individualPostAsHtml(self.server.signingPrivateKeyPem, False,
- self.server.recentPostsCache,
- self.server.maxRecentPosts,
- self.server.translate,
- pageNumber, baseDir,
- self.server.session,
- self.server.cachedWebfingers,
- self.server.personCache,
- self.postToNickname, domain,
- self.server.port, announcePostJson,
- None, True,
- self.server.allowDeletion,
- httpPrefix, self.server.projectVersion,
- timelineStr,
- self.server.YTReplacementDomain,
- self.server.showPublishedDateOnly,
- self.server.peertubeInstances,
- self.server.allowLocalNetworkAccess,
- self.server.themeName,
- self.server.systemLanguage,
- self.server.maxLikeCount,
- not isDM(announcePostJson),
- showIndividualPostIcons,
- manuallyApproveFollowers,
- False, True, False)
- else:
- print('WARN: Announced post not found: ' + announceFilename)
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, self.postToNickname,
+ domain, announceJson)
+ print('Announced post json: ' + str(announceJson))
+ print('Announced post nickname: ' +
+ self.postToNickname + ' ' + domain)
+ print('Announced post cache: ' + str(cachedPostFilename))
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir,
+ self.postToNickname, domain)
+ individualPostAsHtml(self.server.signingPrivateKeyPem, False,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, baseDir,
+ self.server.session,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ self.postToNickname, domain,
+ self.server.port, announceJson,
+ None, True,
+ self.server.allowDeletion,
+ httpPrefix, self.server.projectVersion,
+ timelineStr,
+ self.server.YTReplacementDomain,
+ self.server.showPublishedDateOnly,
+ self.server.peertubeInstances,
+ self.server.allowLocalNetworkAccess,
+ self.server.themeName,
+ self.server.systemLanguage,
+ self.server.maxLikeCount,
+ not isDM(announceJson),
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
self.server.GETbusy = False
actorAbsolute = self._getInstalceUrl(callingDomain) + actor
From 8c49696bd07c78562de0f6de18fad0c70c467284 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 23:04:50 +0100
Subject: [PATCH 114/385] Generate html for incoming likes
---
inbox.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 58 insertions(+), 5 deletions(-)
diff --git a/inbox.py b/inbox.py
index cc1dc105e..555f4313e 100644
--- a/inbox.py
+++ b/inbox.py
@@ -51,6 +51,7 @@ from categories import getHashtagCategories
from categories import setHashtagCategory
from httpsig import verifyPostHeaders
from session import createSession
+from follow import followerApprovalActive
from follow import isFollowingActor
from follow import receiveFollowRequest
from follow import getFollowersOfActor
@@ -180,6 +181,7 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
if boxname != 'outbox':
boxname = 'inbox'
+ notDM = not isDM(postJsonObject)
individualPostAsHtml(signingPrivateKeyPem,
True, recentPostsCache, maxRecentPosts,
translate, pageNumber,
@@ -191,8 +193,7 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly,
peertubeInstances, allowLocalNetworkAccess,
themeName, systemLanguage, maxLikeCount,
- not isDM(postJsonObject),
- True, True, False, True)
+ notDM, True, True, False, True)
def validInbox(baseDir: str, nickname: str, domain: str) -> bool:
@@ -914,7 +915,15 @@ def _receiveLike(recentPostsCache: {},
onionDomain: str,
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [],
- debug: bool) -> bool:
+ debug: bool,
+ signingPrivateKeyPem: str,
+ maxRecentPosts: int, translate: {},
+ allowDeletion: bool,
+ YTReplacementDomain: str,
+ peertubeInstances: [],
+ allowLocalNetworkAccess: bool,
+ themeName: str, systemLanguage: str,
+ maxLikeCount: int) -> bool:
"""Receives a Like activity within the POST section of HTTPServer
"""
if messageJson['type'] != 'Like':
@@ -971,6 +980,41 @@ def _receiveLike(recentPostsCache: {},
updateLikesCollection(recentPostsCache, baseDir, postFilename,
postLikedId, messageJson['actor'],
handleName, domain, debug)
+ # regenerate the html
+ likedPostJson = loadJson(postFilename, 0, 1)
+ if likedPostJson:
+ if debug:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, handleName, domain,
+ likedPostJson)
+ print('Liked post json: ' + str(likedPostJson))
+ print('Liked post nickname: ' + handleName + ' ' + domain)
+ print('Liked post cache: ' + str(cachedPostFilename))
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, handleName, domain)
+ notDM = not isDM(likedPostJson)
+ individualPostAsHtml(signingPrivateKeyPem, False,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ handleName, domain, port, likedPostJson,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Liked post not found: ' + postFilename)
+
return True
@@ -1029,6 +1073,7 @@ def _receiveUndoLike(recentPostsCache: {},
undoLikesCollectionEntry(recentPostsCache, baseDir, postFilename,
messageJson['object'],
messageJson['actor'], domain, debug)
+ # TODO regenerate the html
return True
@@ -2275,7 +2320,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
lastBounceMessage: [],
themeName: str, systemLanguage: str,
- maxLikeCount: int, signingPrivateKeyPem: str) -> bool:
+ maxLikeCount: int,
+ signingPrivateKeyPem: str) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
actor = keyId
@@ -2297,7 +2343,14 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
personCache,
messageJson,
federationList,
- debug):
+ debug, signingPrivateKeyPem,
+ maxRecentPosts, translate,
+ allowDeletion,
+ YTReplacementDomain,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount):
if debug:
print('DEBUG: Like accepted from ' + actor)
return False
From 199cc46e1a2c6734751ff6216496347f9a36854e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 23:10:54 +0100
Subject: [PATCH 115/385] Generate html for incoming undo likes
---
inbox.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 51 insertions(+), 3 deletions(-)
diff --git a/inbox.py b/inbox.py
index 555f4313e..57431fe1c 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1023,7 +1023,15 @@ def _receiveUndoLike(recentPostsCache: {},
httpPrefix: str, domain: str, port: int,
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [],
- debug: bool) -> bool:
+ debug: bool,
+ signingPrivateKeyPem: str,
+ maxRecentPosts: int, translate: {},
+ allowDeletion: bool,
+ YTReplacementDomain: str,
+ peertubeInstances: [],
+ allowLocalNetworkAccess: bool,
+ themeName: str, systemLanguage: str,
+ maxLikeCount: int) -> bool:
"""Receives an undo like activity within the POST section of HTTPServer
"""
if messageJson['type'] != 'Undo':
@@ -1073,7 +1081,40 @@ def _receiveUndoLike(recentPostsCache: {},
undoLikesCollectionEntry(recentPostsCache, baseDir, postFilename,
messageJson['object'],
messageJson['actor'], domain, debug)
- # TODO regenerate the html
+ # regenerate the html
+ likedPostJson = loadJson(postFilename, 0, 1)
+ if likedPostJson:
+ if debug:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, handleName, domain,
+ likedPostJson)
+ print('Unliked post json: ' + str(likedPostJson))
+ print('Unliked post nickname: ' + handleName + ' ' + domain)
+ print('Unliked post cache: ' + str(cachedPostFilename))
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, handleName, domain)
+ notDM = not isDM(likedPostJson)
+ individualPostAsHtml(signingPrivateKeyPem, False,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ handleName, domain, port, likedPostJson,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ else:
+ print('WARN: Unliked post not found: ' + postFilename)
return True
@@ -2364,7 +2405,14 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
personCache,
messageJson,
federationList,
- debug):
+ debug, signingPrivateKeyPem,
+ maxRecentPosts, translate,
+ allowDeletion,
+ YTReplacementDomain,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount):
if debug:
print('DEBUG: Undo like accepted from ' + actor)
return False
From 80a6ce9398944f1df546f346e5827998dddd36b3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 3 Sep 2021 23:28:50 +0100
Subject: [PATCH 116/385] Regenerate html for received bookmarks
---
inbox.py | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 96 insertions(+), 8 deletions(-)
diff --git a/inbox.py b/inbox.py
index 57431fe1c..2f3d95584 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1012,8 +1012,6 @@ def _receiveLike(recentPostsCache: {},
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
- else:
- print('WARN: Liked post not found: ' + postFilename)
return True
@@ -1113,8 +1111,6 @@ def _receiveUndoLike(recentPostsCache: {},
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
- else:
- print('WARN: Unliked post not found: ' + postFilename)
return True
@@ -1123,7 +1119,14 @@ def _receiveBookmark(recentPostsCache: {},
httpPrefix: str, domain: str, port: int,
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [],
- debug: bool) -> bool:
+ debug: bool, signingPrivateKeyPem: str,
+ maxRecentPosts: int, translate: {},
+ allowDeletion: bool,
+ YTReplacementDomain: str,
+ peertubeInstances: [],
+ allowLocalNetworkAccess: bool,
+ themeName: str, systemLanguage: str,
+ maxLikeCount: int) -> bool:
"""Receives a bookmark activity within the POST section of HTTPServer
"""
if not messageJson.get('type'):
@@ -1189,6 +1192,38 @@ def _receiveBookmark(recentPostsCache: {},
updateBookmarksCollection(recentPostsCache, baseDir, postFilename,
messageJson['object']['url'],
messageJson['actor'], domain, debug)
+ # regenerate the html
+ bookmarkedPostJson = loadJson(postFilename, 0, 1)
+ if bookmarkedPostJson:
+ if debug:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, nickname, domain,
+ bookmarkedPostJson)
+ print('Bookmarked post json: ' + str(bookmarkedPostJson))
+ print('Bookmarked post nickname: ' + nickname + ' ' + domain)
+ print('Bookmarked post cache: ' + str(cachedPostFilename))
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, nickname, domain)
+ notDM = not isDM(bookmarkedPostJson)
+ individualPostAsHtml(signingPrivateKeyPem, False,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ nickname, domain, port, bookmarkedPostJson,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
return True
@@ -1197,7 +1232,14 @@ def _receiveUndoBookmark(recentPostsCache: {},
httpPrefix: str, domain: str, port: int,
sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [],
- debug: bool) -> bool:
+ debug: bool, signingPrivateKeyPem: str,
+ maxRecentPosts: int, translate: {},
+ allowDeletion: bool,
+ YTReplacementDomain: str,
+ peertubeInstances: [],
+ allowLocalNetworkAccess: bool,
+ themeName: str, systemLanguage: str,
+ maxLikeCount: int) -> bool:
"""Receives an undo bookmark activity within the POST section of HTTPServer
"""
if not messageJson.get('type'):
@@ -1264,6 +1306,38 @@ def _receiveUndoBookmark(recentPostsCache: {},
undoBookmarksCollectionEntry(recentPostsCache, baseDir, postFilename,
messageJson['object']['url'],
messageJson['actor'], domain, debug)
+ # regenerate the html
+ bookmarkedPostJson = loadJson(postFilename, 0, 1)
+ if bookmarkedPostJson:
+ if debug:
+ cachedPostFilename = \
+ getCachedPostFilename(baseDir, nickname, domain,
+ bookmarkedPostJson)
+ print('Unbookmarked post json: ' + str(bookmarkedPostJson))
+ print('Unbookmarked post nickname: ' + nickname + ' ' + domain)
+ print('Unbookmarked post cache: ' + str(cachedPostFilename))
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, nickname, domain)
+ notDM = not isDM(bookmarkedPostJson)
+ individualPostAsHtml(signingPrivateKeyPem, False,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ nickname, domain, port, bookmarkedPostJson,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
return True
@@ -2426,7 +2500,14 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
personCache,
messageJson,
federationList,
- debug):
+ debug, signingPrivateKeyPem,
+ maxRecentPosts, translate,
+ allowDeletion,
+ YTReplacementDomain,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount):
if debug:
print('DEBUG: Bookmark accepted from ' + actor)
return False
@@ -2440,7 +2521,14 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
personCache,
messageJson,
federationList,
- debug):
+ debug, signingPrivateKeyPem,
+ maxRecentPosts, translate,
+ allowDeletion,
+ YTReplacementDomain,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount):
if debug:
print('DEBUG: Undo bookmark accepted from ' + actor)
return False
From 6b18120f882cbe2279f2cc2efae091be85f7cc30 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 11:11:46 +0100
Subject: [PATCH 117/385] Generate html after receiving announce
---
inbox.py | 36 ++++++++++++++++++++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/inbox.py b/inbox.py
index 2f3d95584..5f063a97f 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1434,7 +1434,11 @@ def _receiveAnnounce(recentPostsCache: {},
YTReplacementDomain: str,
allowLocalNetworkAccess: bool,
themeName: str, systemLanguage: str,
- signingPrivateKeyPem: str) -> bool:
+ signingPrivateKeyPem: str,
+ maxRecentPosts: int,
+ allowDeletion: bool,
+ peertubeInstances: [],
+ maxLikeCount: int) -> bool:
"""Receives an announce activity within the POST section of HTTPServer
"""
if messageJson['type'] != 'Announce':
@@ -1600,6 +1604,30 @@ def _receiveAnnounce(recentPostsCache: {},
time.sleep(5)
if debug:
print('DEBUG: announced/repeated post arrived in inbox')
+ if postJsonObject:
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, nickname, domain)
+ notDM = True
+ individualPostAsHtml(signingPrivateKeyPem, True,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ nickname, domain, port, postJsonObject,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+
return True
@@ -2546,7 +2574,11 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
YTReplacementDomain,
allowLocalNetworkAccess,
themeName, systemLanguage,
- signingPrivateKeyPem):
+ signingPrivateKeyPem,
+ maxRecentPosts,
+ allowDeletion,
+ peertubeInstances,
+ maxLikeCount):
if debug:
print('DEBUG: Announce accepted from ' + actor)
From 38cc4cbf4ba1d0c5f2ede4e48ae40c280a5774e5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 11:33:44 +0100
Subject: [PATCH 118/385] Different message
---
inbox.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/inbox.py b/inbox.py
index 5f063a97f..dc99dd172 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1604,7 +1604,7 @@ def _receiveAnnounce(recentPostsCache: {},
time.sleep(5)
if debug:
print('DEBUG: announced/repeated post arrived in inbox')
- if postJsonObject:
+ if messageJson:
pageNumber = 1
showPublishedDateOnly = False
showIndividualPostIcons = True
@@ -1615,7 +1615,7 @@ def _receiveAnnounce(recentPostsCache: {},
recentPostsCache, maxRecentPosts,
translate, pageNumber, baseDir,
session, cachedWebfingers, personCache,
- nickname, domain, port, postJsonObject,
+ nickname, domain, port, messageJson,
None, True, allowDeletion,
httpPrefix, __version__,
'inbox', YTReplacementDomain,
From cffc8089c85c21b2a4cd86d525e72d2687978d85 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 13:33:10 +0100
Subject: [PATCH 119/385] Announce debug
---
inbox.py | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/inbox.py b/inbox.py
index dc99dd172..efeba1c31 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1604,18 +1604,20 @@ def _receiveAnnounce(recentPostsCache: {},
time.sleep(5)
if debug:
print('DEBUG: announced/repeated post arrived in inbox')
- if messageJson:
- pageNumber = 1
- showPublishedDateOnly = False
- showIndividualPostIcons = True
- manuallyApproveFollowers = \
- followerApprovalActive(baseDir, nickname, domain)
- notDM = True
+
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, nickname, domain)
+ notDM = True
+ print('Generating html for announce ' + postJsonObject['id'])
+ announceHtml = \
individualPostAsHtml(signingPrivateKeyPem, True,
recentPostsCache, maxRecentPosts,
translate, pageNumber, baseDir,
session, cachedWebfingers, personCache,
- nickname, domain, port, messageJson,
+ nickname, domain, port, postJsonObject,
None, True, allowDeletion,
httpPrefix, __version__,
'inbox', YTReplacementDomain,
@@ -1627,7 +1629,7 @@ def _receiveAnnounce(recentPostsCache: {},
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
-
+ print('Generating html for announce ' + str(announceHtml))
return True
From ce5c50f544e39a648f63eb4ff9585752423ff874 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 13:49:50 +0100
Subject: [PATCH 120/385] Debug if html could not be generated for announce
---
inbox.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/inbox.py b/inbox.py
index efeba1c31..a04ddfbc1 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1611,7 +1611,8 @@ def _receiveAnnounce(recentPostsCache: {},
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
notDM = True
- print('Generating html for announce ' + postJsonObject['id'])
+ if debug:
+ print('Generating html for announce ' + postJsonObject['id'])
announceHtml = \
individualPostAsHtml(signingPrivateKeyPem, True,
recentPostsCache, maxRecentPosts,
@@ -1629,7 +1630,9 @@ def _receiveAnnounce(recentPostsCache: {},
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, False)
- print('Generating html for announce ' + str(announceHtml))
+ if not announceHtml:
+ print('WARN: Unable to generate html for announce ' +
+ postJsonObject['id'])
return True
From 5926b17472d61c56ca47b8545d3b9f2325b423c3 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 14:27:01 +0100
Subject: [PATCH 121/385] Debug
---
inbox.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/inbox.py b/inbox.py
index a04ddfbc1..eb4b50dce 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1611,8 +1611,8 @@ def _receiveAnnounce(recentPostsCache: {},
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
notDM = True
- if debug:
- print('Generating html for announce ' + postJsonObject['id'])
+ #if debug:
+ print('Generating html for announce ' + postJsonObject['id'])
announceHtml = \
individualPostAsHtml(signingPrivateKeyPem, True,
recentPostsCache, maxRecentPosts,
From c5d89455fbba962b2e93cef89b6f1c7e84c6fce5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 15:32:02 +0100
Subject: [PATCH 122/385] Debug
---
inbox.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/inbox.py b/inbox.py
index eb4b50dce..1fc522f93 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1541,6 +1541,7 @@ def _receiveAnnounce(recentPostsCache: {},
domainFull, personCache,
signingPrivateKeyPem)
if not postJsonObject:
+ print('WARN: unable to download announce: ' + str(messageJson))
notInOnion = True
if onionDomain:
if onionDomain in messageJson['object']:
From 4a7c6c69767b1d2d76509540f17fbb49db00c636 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 17:11:48 +0100
Subject: [PATCH 123/385] Generate html for announce first
---
inbox.py | 63 ++++++++++++++++++++++++++++++--------------------------
1 file changed, 34 insertions(+), 29 deletions(-)
diff --git a/inbox.py b/inbox.py
index 1fc522f93..cce871569 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1529,6 +1529,40 @@ def _receiveAnnounce(recentPostsCache: {},
print('DEBUG: Downloading announce post ' + messageJson['actor'] +
' -> ' + messageJson['object'])
domainFull = getFullDomain(domain, port)
+
+ # generate html
+ pageNumber = 1
+ showPublishedDateOnly = False
+ showIndividualPostIcons = True
+ manuallyApproveFollowers = \
+ followerApprovalActive(baseDir, nickname, domain)
+ notDM = True
+ # if debug:
+ print('Generating html for announce ' + messageJson['id'])
+ announceHtml = \
+ individualPostAsHtml(signingPrivateKeyPem, True,
+ recentPostsCache, maxRecentPosts,
+ translate, pageNumber, baseDir,
+ session, cachedWebfingers, personCache,
+ nickname, domain, port, messageJson,
+ None, True, allowDeletion,
+ httpPrefix, __version__,
+ 'inbox', YTReplacementDomain,
+ showPublishedDateOnly,
+ peertubeInstances,
+ allowLocalNetworkAccess,
+ themeName, systemLanguage,
+ maxLikeCount, notDM,
+ showIndividualPostIcons,
+ manuallyApproveFollowers,
+ False, True, False)
+ if not announceHtml:
+ print('WARN: Unable to generate html for announce ' +
+ str(messageJson))
+ else:
+ # if debug:
+ print('Generated announce html ' + announceHtml.replace('\n', ''))
+
postJsonObject = downloadAnnounce(session, baseDir,
httpPrefix,
nickname, domain,
@@ -1605,35 +1639,6 @@ def _receiveAnnounce(recentPostsCache: {},
time.sleep(5)
if debug:
print('DEBUG: announced/repeated post arrived in inbox')
-
- pageNumber = 1
- showPublishedDateOnly = False
- showIndividualPostIcons = True
- manuallyApproveFollowers = \
- followerApprovalActive(baseDir, nickname, domain)
- notDM = True
- #if debug:
- print('Generating html for announce ' + postJsonObject['id'])
- announceHtml = \
- individualPostAsHtml(signingPrivateKeyPem, True,
- recentPostsCache, maxRecentPosts,
- translate, pageNumber, baseDir,
- session, cachedWebfingers, personCache,
- nickname, domain, port, postJsonObject,
- None, True, allowDeletion,
- httpPrefix, __version__,
- 'inbox', YTReplacementDomain,
- showPublishedDateOnly,
- peertubeInstances,
- allowLocalNetworkAccess,
- themeName, systemLanguage,
- maxLikeCount, notDM,
- showIndividualPostIcons,
- manuallyApproveFollowers,
- False, True, False)
- if not announceHtml:
- print('WARN: Unable to generate html for announce ' +
- postJsonObject['id'])
return True
From 6cd0d0aa859a0f80f91da76f062894fa3e9a455f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 18:10:29 +0100
Subject: [PATCH 124/385] Allow downloads
---
inbox.py | 2 +-
webapp_timeline.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/inbox.py b/inbox.py
index cce871569..90409a9e6 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1530,7 +1530,7 @@ def _receiveAnnounce(recentPostsCache: {},
' -> ' + messageJson['object'])
domainFull = getFullDomain(domain, port)
- # generate html
+ # Generate html. This also downloads the announced post.
pageNumber = 1
showPublishedDateOnly = False
showIndividualPostIcons = True
diff --git a/webapp_timeline.py b/webapp_timeline.py
index 14e1d45d5..4391222e5 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -860,7 +860,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# read the post from disk
currTlStr = \
individualPostAsHtml(signingPrivateKeyPem,
- False, recentPostsCache,
+ True, recentPostsCache,
maxRecentPosts,
translate, pageNumber,
baseDir, session,
From f564ee71c6a5355ec6f2841d263a18546962d9cc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 18:14:51 +0100
Subject: [PATCH 125/385] Test
---
webapp_timeline.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/webapp_timeline.py b/webapp_timeline.py
index 4391222e5..7605905fe 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -840,12 +840,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if postId in recentPostsCache['index']:
if not item.get('muted'):
if recentPostsCache['html'].get(postId):
- currTlStr = recentPostsCache['html'][postId]
- currTlStr = \
- preparePostFromHtmlCache(nickname,
- currTlStr,
- boxName,
- pageNumber)
+ # currTlStr = recentPostsCache['html'][postId]
+ # currTlStr = \
+ # preparePostFromHtmlCache(nickname,
+ # currTlStr,
+ # boxName,
+ # pageNumber)
_logTimelineTiming(enableTimingLog,
timelineStartTime,
boxName, '10')
@@ -860,7 +860,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
# read the post from disk
currTlStr = \
individualPostAsHtml(signingPrivateKeyPem,
- True, recentPostsCache,
+ False, recentPostsCache,
maxRecentPosts,
translate, pageNumber,
baseDir, session,
From d34e2d256f36df93d9009ed96ef3a3f1108e7dce Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 18:16:50 +0100
Subject: [PATCH 126/385] Remove test
---
webapp_timeline.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/webapp_timeline.py b/webapp_timeline.py
index 7605905fe..14e1d45d5 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -840,12 +840,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if postId in recentPostsCache['index']:
if not item.get('muted'):
if recentPostsCache['html'].get(postId):
- # currTlStr = recentPostsCache['html'][postId]
- # currTlStr = \
- # preparePostFromHtmlCache(nickname,
- # currTlStr,
- # boxName,
- # pageNumber)
+ currTlStr = recentPostsCache['html'][postId]
+ currTlStr = \
+ preparePostFromHtmlCache(nickname,
+ currTlStr,
+ boxName,
+ pageNumber)
_logTimelineTiming(enableTimingLog,
timelineStartTime,
boxName, '10')
From ddeb492ac848128dc2b1815528a8ee82a4483e85 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 18:26:05 +0100
Subject: [PATCH 127/385] Show announces
---
webapp_post.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/webapp_post.py b/webapp_post.py
index 6c5ea8bad..d2c88f5af 100644
--- a/webapp_post.py
+++ b/webapp_post.py
@@ -1243,7 +1243,7 @@ def individualPostAsHtml(signingPrivateKeyPem: str,
signingPrivateKeyPem)
if postHtml:
return postHtml
- if useCacheOnly:
+ if useCacheOnly and postJsonObject['type'] != 'Announce':
return ''
_logPostTiming(enableTimingLog, postStartTime, '4')
From 20273b0c91f7bee120571a40561f17f0d14794bd Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 19:05:52 +0100
Subject: [PATCH 128/385] Tidying
---
inbox.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/inbox.py b/inbox.py
index 90409a9e6..c785e90ad 100644
--- a/inbox.py
+++ b/inbox.py
@@ -1537,8 +1537,8 @@ def _receiveAnnounce(recentPostsCache: {},
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
notDM = True
- # if debug:
- print('Generating html for announce ' + messageJson['id'])
+ if debug:
+ print('Generating html for announce ' + messageJson['id'])
announceHtml = \
individualPostAsHtml(signingPrivateKeyPem, True,
recentPostsCache, maxRecentPosts,
@@ -1560,8 +1560,8 @@ def _receiveAnnounce(recentPostsCache: {},
print('WARN: Unable to generate html for announce ' +
str(messageJson))
else:
- # if debug:
- print('Generated announce html ' + announceHtml.replace('\n', ''))
+ if debug:
+ print('Generated announce html ' + announceHtml.replace('\n', ''))
postJsonObject = downloadAnnounce(session, baseDir,
httpPrefix,
From 851accdb3e130f54e3e50968e1c389c608dc0ac5 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sat, 4 Sep 2021 19:26:08 +0100
Subject: [PATCH 129/385] Avoid announce inbox double entry
---
daemon.py | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/daemon.py b/daemon.py
index 32c0b4f88..9c17d9259 100644
--- a/daemon.py
+++ b/daemon.py
@@ -90,7 +90,6 @@ from posts import createDirectMessagePost
from posts import populateRepliesJson
from posts import addToField
from posts import expireCache
-from inbox import inboxUpdateIndex
from inbox import clearQueueItems
from inbox import inboxPermittedMessage
from inbox import inboxMessageHasParams
@@ -6740,18 +6739,11 @@ class PubServer(BaseHTTPRequestHandler):
# save the announce straight to the outbox
# This is because the subsequent send is within a separate thread
# but the html still needs to be generated before this call ends
- # announceId = removeIdEnding(announceJson['id'])
- announceId = announceJson['id']
+ announceId = removeIdEnding(announceJson['id'])
announceFilename = \
savePostToBox(baseDir, httpPrefix, announceId,
self.postToNickname, domainFull,
announceJson, 'outbox')
- # also copy the post id to the inbox index
- indexes = ['inbox']
- for boxNameIndex in indexes:
- inboxUpdateIndex(boxNameIndex, baseDir,
- self.postToNickname + '@' + domain,
- announceFilename, debug)
# clear the icon from the cache so that it gets updated
if self.server.iconsCache.get('repeat.png'):
From 3a94d7fb41d353a7ffb54c9420ee136cd304fea6 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sun, 5 Sep 2021 11:17:43 +0100
Subject: [PATCH 130/385] Exception handling when deleting files
This can fail if a file is manually deleted or deleted in another thread
---
blocking.py | 27 +++++--
bookmarks.py | 10 ++-
categories.py | 5 +-
content.py | 15 +++-
daemon.py | 161 +++++++++++++++++++++++++++++++---------
devices.py | 5 +-
follow.py | 5 +-
inbox.py | 60 ++++++++++++---
like.py | 5 +-
manualapprove.py | 10 ++-
newsdaemon.py | 5 +-
newswire.py | 5 +-
outbox.py | 5 +-
person.py | 35 +++++++--
posts.py | 10 ++-
schedule.py | 20 ++++-
shares.py | 15 +++-
tests.py | 15 +++-
theme.py | 35 +++++++--
utils.py | 70 +++++++++++++----
webapp_calendar.py | 5 +-
webapp_minimalbutton.py | 5 +-
webapp_timeline.py | 25 +++++--
webapp_utils.py | 5 +-
24 files changed, 444 insertions(+), 114 deletions(-)
diff --git a/blocking.py b/blocking.py
index 406b79717..d39b17c70 100644
--- a/blocking.py
+++ b/blocking.py
@@ -514,7 +514,10 @@ def mutePost(baseDir: str, nickname: str, domain: str, port: int,
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
with open(postFilename + '.muted', 'w+') as muteFile:
muteFile.write('\n')
@@ -550,7 +553,10 @@ def unmutePost(baseDir: str, nickname: str, domain: str, port: int,
muteFilename = postFilename + '.muted'
if os.path.isfile(muteFilename):
- os.remove(muteFilename)
+ try:
+ os.remove(muteFilename)
+ except BaseException:
+ pass
print('UNMUTE: ' + muteFilename + ' file removed')
if hasObjectDict(postJsonObject):
@@ -588,7 +594,10 @@ def unmutePost(baseDir: str, nickname: str, domain: str, port: int,
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
# if the post is in the recent posts cache then mark it as unmuted
if recentPostsCache.get('index'):
@@ -740,7 +749,10 @@ def setBrochMode(baseDir: str, domainFull: str, enabled: bool) -> None:
if not enabled:
# remove instance allow list
if os.path.isfile(allowFilename):
- os.remove(allowFilename)
+ try:
+ os.remove(allowFilename)
+ except BaseException:
+ pass
print('Broch mode turned off')
else:
if os.path.isfile(allowFilename):
@@ -799,11 +811,14 @@ def brochModeLapses(baseDir: str, lapseDays: int = 7) -> bool:
currTime = datetime.datetime.utcnow()
daysSinceBroch = (currTime - modifiedDate).days
if daysSinceBroch >= lapseDays:
+ removed = False
try:
os.remove(allowFilename)
+ removed = True
+ except BaseException:
+ pass
+ if removed:
setConfigParam(baseDir, "brochMode", False)
print('Broch mode has elapsed')
return True
- except BaseException:
- pass
return False
diff --git a/bookmarks.py b/bookmarks.py
index 0e45edce8..3cfc09fd0 100644
--- a/bookmarks.py
+++ b/bookmarks.py
@@ -47,7 +47,10 @@ def undoBookmarksCollectionEntry(recentPostsCache: {},
domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
removePostFromCache(postJsonObject, recentPostsCache)
# remove from the index
@@ -152,7 +155,10 @@ def updateBookmarksCollection(recentPostsCache: {},
domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
removePostFromCache(postJsonObject, recentPostsCache)
if not postJsonObject.get('object'):
diff --git a/categories.py b/categories.py
index f60520b7e..b21a15e25 100644
--- a/categories.py
+++ b/categories.py
@@ -93,7 +93,10 @@ def updateHashtagCategories(baseDir: str) -> None:
hashtagCategories = getHashtagCategories(baseDir)
if not hashtagCategories:
if os.path.isfile(categoryListFilename):
- os.remove(categoryListFilename)
+ try:
+ os.remove(categoryListFilename)
+ except BaseException:
+ pass
return
categoryList = []
diff --git a/content.py b/content.py
index 0c13f0e23..e7af2e76f 100644
--- a/content.py
+++ b/content.py
@@ -938,9 +938,15 @@ def saveMediaInFormPOST(mediaBytes, debug: bool,
for ex in extensionTypes:
possibleOtherFormat = filenameBase + '.' + ex
if os.path.isfile(possibleOtherFormat):
- os.remove(possibleOtherFormat)
+ try:
+ os.remove(possibleOtherFormat)
+ except BaseException:
+ pass
if os.path.isfile(filenameBase):
- os.remove(filenameBase)
+ try:
+ os.remove(filenameBase)
+ except BaseException:
+ pass
if debug:
print('DEBUG: No media found within POST')
@@ -1006,7 +1012,10 @@ def saveMediaInFormPOST(mediaBytes, debug: bool,
detectedExtension, '.' +
ex)
if os.path.isfile(possibleOtherFormat):
- os.remove(possibleOtherFormat)
+ try:
+ os.remove(possibleOtherFormat)
+ except BaseException:
+ pass
with open(filename, 'wb') as fp:
fp.write(mediaBytes[startPos:])
diff --git a/daemon.py b/daemon.py
index 9c17d9259..038a27be1 100644
--- a/daemon.py
+++ b/daemon.py
@@ -488,7 +488,10 @@ class PubServer(BaseHTTPRequestHandler):
postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
# remove from memory cache
removePostFromCache(postJsonObject,
self.server.recentPostsCache)
@@ -2256,7 +2259,10 @@ class PubServer(BaseHTTPRequestHandler):
newswireBlockedFilename = accountDir + '/.nonewswire'
if postsToNews == 'on':
if os.path.isfile(newswireBlockedFilename):
- os.remove(newswireBlockedFilename)
+ try:
+ os.remove(newswireBlockedFilename)
+ except BaseException:
+ pass
refreshNewswire(self.server.baseDir)
else:
if os.path.isdir(accountDir):
@@ -2291,7 +2297,10 @@ class PubServer(BaseHTTPRequestHandler):
featuresBlockedFilename = accountDir + '/.nofeatures'
if postsToFeatures == 'on':
if os.path.isfile(featuresBlockedFilename):
- os.remove(featuresBlockedFilename)
+ try:
+ os.remove(featuresBlockedFilename)
+ except BaseException:
+ pass
refreshNewswire(self.server.baseDir)
else:
if os.path.isdir(accountDir):
@@ -2326,7 +2335,10 @@ class PubServer(BaseHTTPRequestHandler):
newswireModFilename = accountDir + '/.newswiremoderated'
if modPostsToNews != 'on':
if os.path.isfile(newswireModFilename):
- os.remove(newswireModFilename)
+ try:
+ os.remove(newswireModFilename)
+ except BaseException:
+ pass
else:
if os.path.isdir(accountDir):
nwFilename = newswireModFilename
@@ -3739,7 +3751,10 @@ class PubServer(BaseHTTPRequestHandler):
linksFile.write(linksStr)
else:
if os.path.isfile(linksFilename):
- os.remove(linksFilename)
+ try:
+ os.remove(linksFilename)
+ except BaseException:
+ pass
adminNickname = \
getConfigParam(baseDir, 'admin')
@@ -3752,7 +3767,10 @@ class PubServer(BaseHTTPRequestHandler):
aboutFile.write(aboutStr)
else:
if os.path.isfile(aboutFilename):
- os.remove(aboutFilename)
+ try:
+ os.remove(aboutFilename)
+ except BaseException:
+ pass
if fields.get('editedTOS'):
TOSStr = fields['editedTOS']
@@ -3762,7 +3780,10 @@ class PubServer(BaseHTTPRequestHandler):
TOSFile.write(TOSStr)
else:
if os.path.isfile(TOSFilename):
- os.remove(TOSFilename)
+ try:
+ os.remove(TOSFilename)
+ except BaseException:
+ pass
# redirect back to the default timeline
self._redirect_headers(actorStr + '/' + defaultTimeline,
@@ -3860,7 +3881,10 @@ class PubServer(BaseHTTPRequestHandler):
else:
categoryFilename = baseDir + '/tags/' + hashtag + '.category'
if os.path.isfile(categoryFilename):
- os.remove(categoryFilename)
+ try:
+ os.remove(categoryFilename)
+ except BaseException:
+ pass
# redirect back to the default timeline
self._redirect_headers(tagScreenStr,
@@ -3938,7 +3962,10 @@ class PubServer(BaseHTTPRequestHandler):
newswireFile.write(newswireStr)
else:
if os.path.isfile(newswireFilename):
- os.remove(newswireFilename)
+ try:
+ os.remove(newswireFilename)
+ except BaseException:
+ pass
# save filtered words list for the newswire
filterNewswireFilename = \
@@ -3949,7 +3976,10 @@ class PubServer(BaseHTTPRequestHandler):
filterfile.write(fields['filteredWordsNewswire'])
else:
if os.path.isfile(filterNewswireFilename):
- os.remove(filterNewswireFilename)
+ try:
+ os.remove(filterNewswireFilename)
+ except BaseException:
+ pass
# save news tagging rules
hashtagRulesFilename = \
@@ -3959,7 +3989,10 @@ class PubServer(BaseHTTPRequestHandler):
rulesfile.write(fields['hashtagRulesList'])
else:
if os.path.isfile(hashtagRulesFilename):
- os.remove(hashtagRulesFilename)
+ try:
+ os.remove(hashtagRulesFilename)
+ except BaseException:
+ pass
newswireTrustedFilename = baseDir + '/accounts/newswiretrusted.txt'
if fields.get('trustedNewswire'):
@@ -3970,7 +4003,10 @@ class PubServer(BaseHTTPRequestHandler):
trustFile.write(newswireTrusted)
else:
if os.path.isfile(newswireTrustedFilename):
- os.remove(newswireTrustedFilename)
+ try:
+ os.remove(newswireTrustedFilename)
+ except BaseException:
+ pass
# redirect back to the default timeline
self._redirect_headers(actorStr + '/' + defaultTimeline,
@@ -3995,7 +4031,10 @@ class PubServer(BaseHTTPRequestHandler):
acctDir(baseDir, nickname, domain) + '/.citations.txt'
# remove any existing citations file
if os.path.isfile(citationsFilename):
- os.remove(citationsFilename)
+ try:
+ os.remove(citationsFilename)
+ except BaseException:
+ pass
if newswire and \
' boundary=' in self.headers['Content-type']:
@@ -4297,7 +4336,10 @@ class PubServer(BaseHTTPRequestHandler):
filenameBase = \
baseDir + '/imports/newtheme.zip'
if os.path.isfile(filenameBase):
- os.remove(filenameBase)
+ try:
+ os.remove(filenameBase)
+ except BaseException:
+ pass
else:
filenameBase = \
acctDir(baseDir, nickname, domain) + \
@@ -5334,14 +5376,20 @@ class PubServer(BaseHTTPRequestHandler):
for ext in fontExt:
if os.path.isfile(baseDir +
'/fonts/custom.' + ext):
- os.remove(baseDir +
- '/fonts/custom.' + ext)
+ try:
+ os.remove(baseDir +
+ '/fonts/custom.' + ext)
+ except BaseException:
+ pass
if os.path.isfile(baseDir +
'/fonts/custom.' + ext +
'.etag'):
- os.remove(baseDir +
- '/fonts/custom.' + ext +
- '.etag')
+ try:
+ os.remove(baseDir +
+ '/fonts/custom.' + ext +
+ '.etag')
+ except BaseException:
+ pass
currTheme = getTheme(baseDir)
if currTheme:
self.server.themeName = currTheme
@@ -5389,7 +5437,10 @@ class PubServer(BaseHTTPRequestHandler):
fFile.write('\n')
if not followDMsActive:
if os.path.isfile(followDMsFilename):
- os.remove(followDMsFilename)
+ try:
+ os.remove(followDMsFilename)
+ except BaseException:
+ pass
# remove Twitter retweets
removeTwitterFilename = \
@@ -5404,7 +5455,10 @@ class PubServer(BaseHTTPRequestHandler):
rFile.write('\n')
if not removeTwitterActive:
if os.path.isfile(removeTwitterFilename):
- os.remove(removeTwitterFilename)
+ try:
+ os.remove(removeTwitterFilename)
+ except BaseException:
+ pass
# hide Like button
hideLikeButtonFile = \
@@ -5421,10 +5475,16 @@ class PubServer(BaseHTTPRequestHandler):
rFile.write('\n')
# remove notify likes selection
if os.path.isfile(notifyLikesFilename):
- os.remove(notifyLikesFilename)
+ try:
+ os.remove(notifyLikesFilename)
+ except BaseException:
+ pass
if not hideLikeButtonActive:
if os.path.isfile(hideLikeButtonFile):
- os.remove(hideLikeButtonFile)
+ try:
+ os.remove(hideLikeButtonFile)
+ except BaseException:
+ pass
# notify about new Likes
if onFinalWelcomeScreen:
@@ -5442,7 +5502,10 @@ class PubServer(BaseHTTPRequestHandler):
rFile.write('\n')
if not notifyLikesActive:
if os.path.isfile(notifyLikesFilename):
- os.remove(notifyLikesFilename)
+ try:
+ os.remove(notifyLikesFilename)
+ except BaseException:
+ pass
# this account is a bot
if fields.get('isBot'):
@@ -5501,7 +5564,10 @@ class PubServer(BaseHTTPRequestHandler):
filterfile.write(fields['filteredWords'])
else:
if os.path.isfile(filterFilename):
- os.remove(filterFilename)
+ try:
+ os.remove(filterFilename)
+ except BaseException:
+ pass
# word replacements
switchFilename = \
@@ -5512,7 +5578,10 @@ class PubServer(BaseHTTPRequestHandler):
switchfile.write(fields['switchWords'])
else:
if os.path.isfile(switchFilename):
- os.remove(switchFilename)
+ try:
+ os.remove(switchFilename)
+ except BaseException:
+ pass
# autogenerated tags
autoTagsFilename = \
@@ -5523,7 +5592,10 @@ class PubServer(BaseHTTPRequestHandler):
autoTagsFile.write(fields['autoTags'])
else:
if os.path.isfile(autoTagsFilename):
- os.remove(autoTagsFilename)
+ try:
+ os.remove(autoTagsFilename)
+ except BaseException:
+ pass
# autogenerated content warnings
autoCWFilename = \
@@ -5534,7 +5606,10 @@ class PubServer(BaseHTTPRequestHandler):
autoCWFile.write(fields['autoCW'])
else:
if os.path.isfile(autoCWFilename):
- os.remove(autoCWFilename)
+ try:
+ os.remove(autoCWFilename)
+ except BaseException:
+ pass
# save blocked accounts list
blockedFilename = \
@@ -5545,7 +5620,10 @@ class PubServer(BaseHTTPRequestHandler):
blockedfile.write(fields['blocked'])
else:
if os.path.isfile(blockedFilename):
- os.remove(blockedFilename)
+ try:
+ os.remove(blockedFilename)
+ except BaseException:
+ pass
# Save DM allowed instances list.
# The allow list for incoming DMs,
@@ -5558,7 +5636,10 @@ class PubServer(BaseHTTPRequestHandler):
aFile.write(fields['dmAllowedInstances'])
else:
if os.path.isfile(dmAllowedInstancesFilename):
- os.remove(dmAllowedInstancesFilename)
+ try:
+ os.remove(dmAllowedInstancesFilename)
+ except BaseException:
+ pass
# save allowed instances list
# This is the account level allow list
@@ -5570,7 +5651,10 @@ class PubServer(BaseHTTPRequestHandler):
aFile.write(fields['allowedInstances'])
else:
if os.path.isfile(allowedInstancesFilename):
- os.remove(allowedInstancesFilename)
+ try:
+ os.remove(allowedInstancesFilename)
+ except BaseException:
+ pass
# save blocked user agents
# This is admin lebel and global to the instance
@@ -5615,7 +5699,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.peertubeInstances.append(url)
else:
if os.path.isfile(peertubeInstancesFile):
- os.remove(peertubeInstancesFile)
+ try:
+ os.remove(peertubeInstancesFile)
+ except BaseException:
+ pass
self.server.peertubeInstances.clear()
# save git project names list
@@ -5627,7 +5714,10 @@ class PubServer(BaseHTTPRequestHandler):
aFile.write(fields['gitProjects'].lower())
else:
if os.path.isfile(gitProjectsFilename):
- os.remove(gitProjectsFilename)
+ try:
+ os.remove(gitProjectsFilename)
+ except BaseException:
+ pass
# save actor json file within accounts
if actorChanged:
@@ -15025,7 +15115,10 @@ class PubServer(BaseHTTPRequestHandler):
self.server.lowBandwidth)
if filename:
if os.path.isfile(filename):
- os.remove(filename)
+ try:
+ os.remove(filename)
+ except BaseException:
+ pass
self.postToNickname = nickname
return 1
return -1
diff --git a/devices.py b/devices.py
index 8b56e5d37..1075f8582 100644
--- a/devices.py
+++ b/devices.py
@@ -44,7 +44,10 @@ def E2EEremoveDevice(baseDir: str, nickname: str, domain: str,
personDir = acctDir(baseDir, nickname, domain)
deviceFilename = personDir + '/devices/' + deviceId + '.json'
if os.path.isfile(deviceFilename):
- os.remove(deviceFilename)
+ try:
+ os.remove(deviceFilename)
+ except BaseException:
+ pass
return True
return False
diff --git a/follow.py b/follow.py
index 5182f1ff5..211960e96 100644
--- a/follow.py
+++ b/follow.py
@@ -308,7 +308,10 @@ def clearFollows(baseDir: str, nickname: str, domain: str,
os.mkdir(baseDir + '/accounts/' + handle)
filename = baseDir + '/accounts/' + handle + '/' + followFile
if os.path.isfile(filename):
- os.remove(filename)
+ try:
+ os.remove(filename)
+ except BaseException:
+ pass
def clearFollowers(baseDir: str, nickname: str, domain: str) -> None:
diff --git a/inbox.py b/inbox.py
index c785e90ad..5161f4ca8 100644
--- a/inbox.py
+++ b/inbox.py
@@ -821,7 +821,10 @@ def _receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {},
getCachedPostFilename(baseDir, nickname, domain, messageJson)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
# remove from memory cache
removePostFromCache(messageJson, recentPostsCache)
@@ -1583,7 +1586,10 @@ def _receiveAnnounce(recentPostsCache: {},
if domain not in messageJson['object'] and notInOnion:
if os.path.isfile(postFilename):
# if the announce can't be downloaded then remove it
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
else:
if debug:
print('DEBUG: Announce post downloaded for ' +
@@ -1693,7 +1699,10 @@ def _receiveUndoAnnounce(recentPostsCache: {},
undoAnnounceCollectionEntry(recentPostsCache, baseDir, postFilename,
messageJson['actor'], domain, debug)
if os.path.isfile(postFilename):
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
return True
@@ -3276,7 +3285,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
if debug:
print('Queue: public key could not be obtained from ' + keyId)
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3324,7 +3336,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
if httpSignatureFailed or verifyAllSignatures:
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3341,7 +3356,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
print('WARN: jsonld inbox signature check failed ' +
keyId)
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3367,7 +3385,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
debug):
print('Queue: Undo accepted from ' + keyId)
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3385,7 +3406,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
maxFollowers, onionDomain,
signingPrivateKeyPem):
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
print('Queue: Follow activity for ' + keyId +
@@ -3403,7 +3427,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
federationList, debug):
print('Queue: Accept/Reject received from ' + keyId)
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3421,7 +3448,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
if debug:
print('Queue: Update accepted from ' + keyId)
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3436,7 +3466,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
print('Queue: no recipients were resolved ' +
'for post arriving in inbox')
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
continue
@@ -3506,6 +3539,9 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
pprint(queueJson['post'])
print('Queue: Queue post accepted')
if os.path.isfile(queueFilename):
- os.remove(queueFilename)
+ try:
+ os.remove(queueFilename)
+ except BaseException:
+ pass
if len(queue) > 0:
queue.pop(0)
diff --git a/like.py b/like.py
index 55ef0ebbb..6765d6c1b 100644
--- a/like.py
+++ b/like.py
@@ -431,7 +431,10 @@ def updateLikesCollection(recentPostsCache: {},
domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
if not hasObjectDict(postJsonObject):
if debug:
diff --git a/manualapprove.py b/manualapprove.py
index 97b6ae7b6..0d85bd1e6 100644
--- a/manualapprove.py
+++ b/manualapprove.py
@@ -222,6 +222,12 @@ def manualApproveFollowRequest(session, baseDir: str,
# remove the .follow file
if followActivityfilename:
if os.path.isfile(followActivityfilename):
- os.remove(followActivityfilename)
+ try:
+ os.remove(followActivityfilename)
+ except BaseException:
+ pass
else:
- os.remove(approveFollowsFilename + '.new')
+ try:
+ os.remove(approveFollowsFilename + '.new')
+ except BaseException:
+ pass
diff --git a/newsdaemon.py b/newsdaemon.py
index 6e529471b..63de2e116 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -702,7 +702,10 @@ def _convertRSStoActivityPub(baseDir: str, httpPrefix: str,
blog['object']['arrived'])
else:
if os.path.isfile(filename + '.arrived'):
- os.remove(filename + '.arrived')
+ try:
+ os.remove(filename + '.arrived')
+ except BaseException:
+ pass
# setting the url here links to the activitypub object
# stored locally
diff --git a/newswire.py b/newswire.py
index 5f8a1ba0b..15ca99516 100644
--- a/newswire.py
+++ b/newswire.py
@@ -1028,7 +1028,10 @@ def _addBlogsToNewswire(baseDir: str, domain: str, newswire: {},
else:
# remove the file if there is nothing to moderate
if os.path.isfile(newswireModerationFilename):
- os.remove(newswireModerationFilename)
+ try:
+ os.remove(newswireModerationFilename)
+ except BaseException:
+ pass
def getDictFromNewswire(session, baseDir: str, domain: str,
diff --git a/outbox.py b/outbox.py
index de654dc77..45b11d35e 100644
--- a/outbox.py
+++ b/outbox.py
@@ -390,7 +390,10 @@ def postMessageToOutbox(session, translate: {},
baseDir + '/accounts/' + \
postToNickname + '@' + domain + '/.citations.txt'
if os.path.isfile(citationsFilename):
- os.remove(citationsFilename)
+ try:
+ os.remove(citationsFilename)
+ except BaseException:
+ pass
# The following activity types get added to the index files
indexedActivities = (
diff --git a/person.py b/person.py
index 9c5876157..a45d09a11 100644
--- a/person.py
+++ b/person.py
@@ -919,10 +919,16 @@ def suspendAccount(baseDir: str, nickname: str, domain: str) -> None:
saltFilename = acctDir(baseDir, nickname, domain) + '/.salt'
if os.path.isfile(saltFilename):
- os.remove(saltFilename)
+ try:
+ os.remove(saltFilename)
+ except BaseException:
+ pass
tokenFilename = acctDir(baseDir, nickname, domain) + '/.token'
if os.path.isfile(tokenFilename):
- os.remove(tokenFilename)
+ try:
+ os.remove(tokenFilename)
+ except BaseException:
+ pass
suspendedFilename = baseDir + '/accounts/suspended.txt'
if os.path.isfile(suspendedFilename):
@@ -1025,17 +1031,32 @@ def removeAccount(baseDir: str, nickname: str,
if os.path.isdir(baseDir + '/accounts/' + handle):
shutil.rmtree(baseDir + '/accounts/' + handle)
if os.path.isfile(baseDir + '/accounts/' + handle + '.json'):
- os.remove(baseDir + '/accounts/' + handle + '.json')
+ try:
+ os.remove(baseDir + '/accounts/' + handle + '.json')
+ except BaseException:
+ pass
if os.path.isfile(baseDir + '/wfendpoints/' + handle + '.json'):
- os.remove(baseDir + '/wfendpoints/' + handle + '.json')
+ try:
+ os.remove(baseDir + '/wfendpoints/' + handle + '.json')
+ except BaseException:
+ pass
if os.path.isfile(baseDir + '/keys/private/' + handle + '.key'):
- os.remove(baseDir + '/keys/private/' + handle + '.key')
+ try:
+ os.remove(baseDir + '/keys/private/' + handle + '.key')
+ except BaseException:
+ pass
if os.path.isfile(baseDir + '/keys/public/' + handle + '.pem'):
- os.remove(baseDir + '/keys/public/' + handle + '.pem')
+ try:
+ os.remove(baseDir + '/keys/public/' + handle + '.pem')
+ except BaseException:
+ pass
if os.path.isdir(baseDir + '/sharefiles/' + nickname):
shutil.rmtree(baseDir + '/sharefiles/' + nickname)
if os.path.isfile(baseDir + '/wfdeactivated/' + handle + '.json'):
- os.remove(baseDir + '/wfdeactivated/' + handle + '.json')
+ try:
+ os.remove(baseDir + '/wfdeactivated/' + handle + '.json')
+ except BaseException:
+ pass
if os.path.isdir(baseDir + '/sharefilesdeactivated/' + nickname):
shutil.rmtree(baseDir + '/sharefilesdeactivated/' + nickname)
diff --git a/posts.py b/posts.py
index 394ccd0ca..df0839a21 100644
--- a/posts.py
+++ b/posts.py
@@ -1470,7 +1470,10 @@ def undoPinnedPost(baseDir: str, nickname: str, domain: str) -> None:
accountDir = acctDir(baseDir, nickname, domain)
pinnedFilename = accountDir + '/pinToProfile.txt'
if os.path.isfile(pinnedFilename):
- os.remove(pinnedFilename)
+ try:
+ os.remove(pinnedFilename)
+ except BaseException:
+ pass
def getPinnedPostAsJson(baseDir: str, httpPrefix: str,
@@ -3766,7 +3769,10 @@ def archivePostsForPerson(httpPrefix: str, nickname: str, domain: str,
postCacheFilename = \
os.path.join(postCacheDir, postFilename).replace('.json', '.html')
if os.path.isfile(postCacheFilename):
- os.remove(postCacheFilename)
+ try:
+ os.remove(postCacheFilename)
+ except BaseException:
+ pass
noOfPosts -= 1
removeCtr += 1
diff --git a/schedule.py b/schedule.py
index 165160ced..48d4bd035 100644
--- a/schedule.py
+++ b/schedule.py
@@ -46,7 +46,10 @@ def _updatePostSchedule(baseDir: str, handle: str, httpd,
if deleteSchedulePost:
# delete extraneous scheduled posts
if os.path.isfile(postFilename):
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
continue
# create the new index file
indexLines.append(line)
@@ -122,7 +125,10 @@ def _updatePostSchedule(baseDir: str, handle: str, httpd,
httpd.maxLikeCount,
httpd.maxRecentPosts):
indexLines.remove(line)
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
continue
# move to the outbox
@@ -190,7 +196,10 @@ def removeScheduledPosts(baseDir: str, nickname: str, domain: str) -> None:
scheduleIndexFilename = \
acctDir(baseDir, nickname, domain) + '/schedule.index'
if os.path.isfile(scheduleIndexFilename):
- os.remove(scheduleIndexFilename)
+ try:
+ os.remove(scheduleIndexFilename)
+ except BaseException:
+ pass
# remove the scheduled posts
scheduledDir = acctDir(baseDir, nickname, domain) + '/scheduled'
if not os.path.isdir(scheduledDir):
@@ -199,6 +208,9 @@ def removeScheduledPosts(baseDir: str, nickname: str, domain: str) -> None:
filePath = os.path.join(scheduledDir, scheduledPostFilename)
try:
if os.path.isfile(filePath):
- os.remove(filePath)
+ try:
+ os.remove(filePath)
+ except BaseException:
+ pass
except BaseException:
pass
diff --git a/shares.py b/shares.py
index dd40d583b..60096d2d2 100644
--- a/shares.py
+++ b/shares.py
@@ -142,7 +142,10 @@ def removeSharedItem(baseDir: str, nickname: str, domain: str,
for ext in formats:
if sharesJson[itemID]['imageUrl'].endswith('.' + ext):
if os.path.isfile(itemIDfile + '.' + ext):
- os.remove(itemIDfile + '.' + ext)
+ try:
+ os.remove(itemIDfile + '.' + ext)
+ except BaseException:
+ pass
# remove the item itself
del sharesJson[itemID]
saveJson(sharesJson, sharesFilename)
@@ -350,7 +353,10 @@ def addShare(baseDir: str,
imageFilename, itemIDfile + '.' + ext,
city)
if moveImage:
- os.remove(imageFilename)
+ try:
+ os.remove(imageFilename)
+ except BaseException:
+ pass
imageUrl = \
httpPrefix + '://' + domainFull + \
'/sharefiles/' + nickname + '/' + itemID + '.' + ext
@@ -419,7 +425,10 @@ def _expireSharesForAccount(baseDir: str, nickname: str, domain: str,
formats = getImageExtensions()
for ext in formats:
if os.path.isfile(itemIDfile + '.' + ext):
- os.remove(itemIDfile + '.' + ext)
+ try:
+ os.remove(itemIDfile + '.' + ext)
+ except BaseException:
+ pass
saveJson(sharesJson, sharesFilename)
diff --git a/tests.py b/tests.py
index 4b5692f61..2018886f1 100644
--- a/tests.py
+++ b/tests.py
@@ -3197,7 +3197,10 @@ def _testJsonString() -> None:
assert receivedJson['content'] == messageStr
encodedStr = json.dumps(testJson, ensure_ascii=False)
assert messageStr in encodedStr
- os.remove(filename)
+ try:
+ os.remove(filename)
+ except BaseException:
+ pass
def _testSaveLoadJson():
@@ -3208,7 +3211,10 @@ def _testSaveLoadJson():
}
testFilename = '.epicyon_tests_testSaveLoadJson.json'
if os.path.isfile(testFilename):
- os.remove(testFilename)
+ try:
+ os.remove(testFilename)
+ except BaseException:
+ pass
assert saveJson(testJson, testFilename)
assert os.path.isfile(testFilename)
testLoadJson = loadJson(testFilename)
@@ -3217,7 +3223,10 @@ def _testSaveLoadJson():
assert testLoadJson.get('param2')
assert testLoadJson['param1'] == 3
assert testLoadJson['param2'] == '"Crème brûlée यह एक परीक्षण ह"'
- os.remove(testFilename)
+ try:
+ os.remove(testFilename)
+ except BaseException:
+ pass
def _testTheme():
diff --git a/theme.py b/theme.py
index 7f8d96086..232898a9f 100644
--- a/theme.py
+++ b/theme.py
@@ -83,7 +83,10 @@ def exportTheme(baseDir: str, theme: str) -> bool:
os.mkdir(baseDir + '/exports')
exportFilename = baseDir + '/exports/' + theme + '.zip'
if os.path.isfile(exportFilename):
- os.remove(exportFilename)
+ try:
+ os.remove(exportFilename)
+ except BaseException:
+ pass
try:
make_archive(baseDir + '/exports/' + theme, 'zip', themeDir)
except BaseException:
@@ -250,7 +253,10 @@ def _removeTheme(baseDir: str):
themeFiles = _getThemeFiles()
for filename in themeFiles:
if os.path.isfile(baseDir + '/' + filename):
- os.remove(baseDir + '/' + filename)
+ try:
+ os.remove(baseDir + '/' + filename)
+ except BaseException:
+ pass
def setCSSparam(css: str, param: str, value: str) -> str:
@@ -432,7 +438,10 @@ def disableGrayscale(baseDir: str) -> None:
cssfile.write(css)
grayscaleFilename = baseDir + '/accounts/.grayscale'
if os.path.isfile(grayscaleFilename):
- os.remove(grayscaleFilename)
+ try:
+ os.remove(grayscaleFilename)
+ except BaseException:
+ pass
def _setCustomFont(baseDir: str):
@@ -587,7 +596,10 @@ def _setTextModeTheme(baseDir: str, name: str) -> None:
textModeBannerFilename = \
baseDir + '/theme/' + name + '/banner.txt'
if os.path.isfile(baseDir + '/accounts/banner.txt'):
- os.remove(baseDir + '/accounts/banner.txt')
+ try:
+ os.remove(baseDir + '/accounts/banner.txt')
+ except BaseException:
+ pass
if os.path.isfile(textModeBannerFilename):
try:
copyfile(textModeBannerFilename,
@@ -684,7 +696,10 @@ def _setThemeImages(baseDir: str, name: str) -> None:
else:
if os.path.isfile(accountDir +
'/left_col_image.png'):
- os.remove(accountDir + '/left_col_image.png')
+ try:
+ os.remove(accountDir + '/left_col_image.png')
+ except BaseException:
+ pass
except BaseException:
pass
@@ -696,7 +711,10 @@ def _setThemeImages(baseDir: str, name: str) -> None:
else:
if os.path.isfile(accountDir +
'/right_col_image.png'):
- os.remove(accountDir + '/right_col_image.png')
+ try:
+ os.remove(accountDir + '/right_col_image.png')
+ except BaseException:
+ pass
except BaseException:
pass
break
@@ -719,7 +737,10 @@ def setNewsAvatar(baseDir: str, name: str,
filename = baseDir + '/cache/avatars/' + avatarFilename
if os.path.isfile(filename):
- os.remove(filename)
+ try:
+ os.remove(filename)
+ except BaseException:
+ pass
if os.path.isdir(baseDir + '/cache/avatars'):
copyfile(newFilename, filename)
accountDir = acctDir(baseDir, nickname, domain)
diff --git a/utils.py b/utils.py
index 749c89c98..5dc621815 100644
--- a/utils.py
+++ b/utils.py
@@ -614,7 +614,10 @@ def removeAvatarFromCache(baseDir: str, actorStr: str) -> None:
avatarFilename = \
baseDir + '/cache/avatars/' + actorStr + '.' + extension
if os.path.isfile(avatarFilename):
- os.remove(avatarFilename)
+ try:
+ os.remove(avatarFilename)
+ except BaseException:
+ pass
def saveJson(jsonObject: {}, filename: str) -> bool:
@@ -1318,10 +1321,16 @@ def _removeAttachment(baseDir: str, httpPrefix: str, domain: str,
mediaFilename = baseDir + '/' + \
attachmentUrl.replace(httpPrefix + '://' + domain + '/', '')
if os.path.isfile(mediaFilename):
- os.remove(mediaFilename)
+ try:
+ os.remove(mediaFilename)
+ except BaseException:
+ pass
etagFilename = mediaFilename + '.etag'
if os.path.isfile(etagFilename):
- os.remove(etagFilename)
+ try:
+ os.remove(etagFilename)
+ except BaseException:
+ pass
postJson['attachment'] = []
@@ -1386,7 +1395,10 @@ def _deletePostRemoveReplies(baseDir: str, nickname: str, domain: str,
nickname, domain, replyFile, debug,
recentPostsCache)
# remove the replies file
- os.remove(repliesFilename)
+ try:
+ os.remove(repliesFilename)
+ except BaseException:
+ pass
def _isBookmarked(baseDir: str, nickname: str, domain: str,
@@ -1442,7 +1454,10 @@ def _deleteCachedHtml(baseDir: str, nickname: str, domain: str,
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
def _deleteHashtagsOnPost(baseDir: str, postJsonObject: {}) -> None:
@@ -1486,7 +1501,10 @@ def _deleteHashtagsOnPost(baseDir: str, postJsonObject: {}) -> None:
newlines += fileLine
if not newlines.strip():
# if there are no lines then remove the hashtag file
- os.remove(tagIndexFilename)
+ try:
+ os.remove(tagIndexFilename)
+ except BaseException:
+ pass
else:
# write the new hashtag index without the given post in it
with open(tagIndexFilename, 'w+') as f:
@@ -1521,8 +1539,14 @@ def _deleteConversationPost(baseDir: str, nickname: str, domain: str,
fp.write(conversationStr)
else:
if os.path.isfile(conversationFilename + '.muted'):
- os.remove(conversationFilename + '.muted')
- os.remove(conversationFilename)
+ try:
+ os.remove(conversationFilename + '.muted')
+ except BaseException:
+ pass
+ try:
+ os.remove(conversationFilename)
+ except BaseException:
+ pass
def deletePost(baseDir: str, httpPrefix: str,
@@ -1537,7 +1561,10 @@ def deletePost(baseDir: str, httpPrefix: str,
httpPrefix, postFilename,
recentPostsCache, debug)
# finally, remove the post itself
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
return
# don't allow deletion of bookmarked posts
@@ -1562,7 +1589,10 @@ def deletePost(baseDir: str, httpPrefix: str,
for ext in extensions:
extFilename = postFilename + '.' + ext
if os.path.isfile(extFilename):
- os.remove(extFilename)
+ try:
+ os.remove(extFilename)
+ except BaseException:
+ pass
# remove cached html version of the post
_deleteCachedHtml(baseDir, nickname, domain, postJsonObject)
@@ -1588,7 +1618,10 @@ def deletePost(baseDir: str, httpPrefix: str,
httpPrefix, postFilename,
recentPostsCache, debug)
# finally, remove the post itself
- os.remove(postFilename)
+ try:
+ os.remove(postFilename)
+ except BaseException:
+ pass
def isValidLanguage(text: str) -> bool:
@@ -2022,7 +2055,10 @@ def undoLikesCollectionEntry(recentPostsCache: {},
domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
removePostFromCache(postJsonObject, recentPostsCache)
if not postJsonObject.get('type'):
@@ -2083,7 +2119,10 @@ def undoAnnounceCollectionEntry(recentPostsCache: {},
postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
removePostFromCache(postJsonObject, recentPostsCache)
if not postJsonObject.get('type'):
@@ -2144,7 +2183,10 @@ def updateAnnounceCollection(recentPostsCache: {},
postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
- os.remove(cachedPostFilename)
+ try:
+ os.remove(cachedPostFilename)
+ except BaseException:
+ pass
removePostFromCache(postJsonObject, recentPostsCache)
if not hasObjectDict(postJsonObject):
diff --git a/webapp_calendar.py b/webapp_calendar.py
index 26a0be727..9fbe1eaed 100644
--- a/webapp_calendar.py
+++ b/webapp_calendar.py
@@ -110,7 +110,10 @@ def _htmlCalendarDay(personCache: {}, cssCache: {}, translate: {},
accountDir = acctDir(baseDir, nickname, domain)
calendarFile = accountDir + '/.newCalendar'
if os.path.isfile(calendarFile):
- os.remove(calendarFile)
+ try:
+ os.remove(calendarFile)
+ except BaseException:
+ pass
cssFilename = baseDir + '/epicyon-calendar.css'
if os.path.isfile(baseDir + '/calendar.css'):
diff --git a/webapp_minimalbutton.py b/webapp_minimalbutton.py
index b3e3ce40b..716f3ba55 100644
--- a/webapp_minimalbutton.py
+++ b/webapp_minimalbutton.py
@@ -34,7 +34,10 @@ def setMinimal(baseDir: str, domain: str, nickname: str,
minimalFilename = accountDir + '/.notminimal'
minimalFileExists = os.path.isfile(minimalFilename)
if minimal and minimalFileExists:
- os.remove(minimalFilename)
+ try:
+ os.remove(minimalFilename)
+ except BaseException:
+ pass
elif not minimal and not minimalFileExists:
with open(minimalFilename, 'w+') as fp:
fp.write('\n')
diff --git a/webapp_timeline.py b/webapp_timeline.py
index 14e1d45d5..f375272ba 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -451,7 +451,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if os.path.isfile(dmFile):
newDM = True
if boxName == 'dm':
- os.remove(dmFile)
+ try:
+ os.remove(dmFile)
+ except BaseException:
+ pass
# should the Replies button be highlighted?
newReply = False
@@ -459,7 +462,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if os.path.isfile(replyFile):
newReply = True
if boxName == 'tlreplies':
- os.remove(replyFile)
+ try:
+ os.remove(replyFile)
+ except BaseException:
+ pass
# should the Shares button be highlighted?
newShare = False
@@ -467,7 +473,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if os.path.isfile(newShareFile):
newShare = True
if boxName == 'tlshares':
- os.remove(newShareFile)
+ try:
+ os.remove(newShareFile)
+ except BaseException:
+ pass
# should the Wanted button be highlighted?
newWanted = False
@@ -475,7 +484,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if os.path.isfile(newWantedFile):
newWanted = True
if boxName == 'tlwanted':
- os.remove(newWantedFile)
+ try:
+ os.remove(newWantedFile)
+ except BaseException:
+ pass
# should the Moderation/reports button be highlighted?
newReport = False
@@ -483,7 +495,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if os.path.isfile(newReportFile):
newReport = True
if boxName == 'moderation':
- os.remove(newReportFile)
+ try:
+ os.remove(newReportFile)
+ except BaseException:
+ pass
separatorStr = ''
if boxName != 'tlmedia':
diff --git a/webapp_utils.py b/webapp_utils.py
index 3bb07355d..cae7cfd54 100644
--- a/webapp_utils.py
+++ b/webapp_utils.py
@@ -280,7 +280,10 @@ def updateAvatarImageCache(signingPrivateKeyPem: str,
str(result.status_code))
# remove partial download
if os.path.isfile(avatarImageFilename):
- os.remove(avatarImageFilename)
+ try:
+ os.remove(avatarImageFilename)
+ except BaseException:
+ pass
else:
with open(avatarImageFilename, 'wb') as f:
f.write(result.content)
From db213991fc8ae838f6bc941d4e49ea7e26884747 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Sun, 5 Sep 2021 12:04:06 +0100
Subject: [PATCH 131/385] Update default hashtag categories
---
defaultcategories/en.xml | 388 ++++++++++++++++++++-------------------
1 file changed, 197 insertions(+), 191 deletions(-)
diff --git a/defaultcategories/en.xml b/defaultcategories/en.xml
index 7dde26b01..78d080b82 100644
--- a/defaultcategories/en.xml
+++ b/defaultcategories/en.xml
@@ -4,674 +4,680 @@
#categories
-
retro
- retrocomputer kommunalwahl 90sretro A500 CreativeCommons ecommerce atarist SistersWithTransistors vax retroarch commodore retroffiting teletext Retromeme matariki floppy recommendation 8bit cassette arcade atari communicators atari800 oldschool trs80 communication atari8bit floppydisk retrocomputing recommended C64 nostalgia bbs ansi communicationtheory plan9 80s TransCrowdFund microcomputing kommunikation vaxvms retroarcade zdfretro cassette_tapes bonhomme omm acorn retrogaming z80 8bitdo retro atari800xl retrocom telekommunikation VollaCommunityDays retropie commodore64 cassettetapes retrogame Trans amiga bbcmicro retrofriday microcomputer bbsing commercial
+ retrocomputer kommunalwahl 90sretro A500 CreativeCommons ecommerce atarist SistersWithTransistors vax retroarch commodore retroffiting teletext Retromeme matariki floppy recommendation 8bit cassette arcade atari communicators atari800 oldschool trs80 communication atari8bit floppydisk retrocomputing recommended C64 nostalgia bbs ansi communicationtheory plan9 80s TransCrowdFund microcomputing kommunikation vaxvms retroarcade zdfretro cassette_tapes bonhomme omm acorn retrogaming z80 8bitdo retro atari800xl retrocom telekommunikation VollaCommunityDays retropie commodore64 cassettetapes retrogame Trans amiga bbcmicro retrofriday microcomputer bbsing commercial ansible
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
economics
- Europe workercoop InformationFriction cooperatives accounting bank cooplife bitcoin noplanetb theWorkshop feministeconomics WealthConcentration valueflows coops holochain valuesovereignty cooperativism greatplains platformcoop pico coopstack transcrowdfund usebitcoin shitcoin gigeconomy consommation workercoops economics cooperationjackson cooperation radical value business platformcooperatives exoplanets shopping displacement economic poplar shop companyculture plaintextaccounting MarketForLemons sovereignty crowdfund ethereum oops fairtrade RIPpla bankingCartel rope Datenbank Bitcoin startups radicalcooperation HenryGeorge scar plausible economíasolidaria disablitycrowdfund crowdfunding limitstogrowth ponzi companies theygrowupfast hermannplatz sharingiscaring techcoops plastikfrei woocommerce plantprotein meetcoop disability micropatronage boarsplaining merz lgbtcrowdfund mehrplatzfürsrad monetize sineadoconnor postscarcity cooperativas ua cryptocurrencies coopjobs degrowth a2pico smallbusiness deliveroo intellectualproperty pla kommerzialisierung GitPay Fedigrowth gdp coopsday deplatforming timebank coop cooperativismo smallbusinesses europeancentralbank banknotes whyBitcoin cryptocurrency infoshop sine grow telecoop growth DesignForDisassembly limits fuckfoodbanks btc values banks planetary plannedObsolence planet worldbank
+ Europe workercoop InformationFriction cooperatives accounting bank cooplife bitcoin noplanetb theWorkshop feministeconomics WealthConcentration valueflows coops holochain valuesovereignty cooperativism greatplains platformcoop pico coopstack transcrowdfund usebitcoin shitcoin gigeconomy consommation workercoops economics cooperationjackson cooperation radical value business platformcooperatives exoplanets shopping displacement economic poplar shop companyculture plaintextaccounting MarketForLemons sovereignty crowdfund ethereum oops fairtrade RIPpla bankingCartel rope Datenbank Bitcoin startups radicalcooperation HenryGeorge scar plausible economíasolidaria disablitycrowdfund crowdfunding limitstogrowth ponzi companies theygrowupfast hermannplatz sharingiscaring techcoops plastikfrei woocommerce plantprotein meetcoop disability micropatronage boarsplaining merz lgbtcrowdfund mehrplatzfürsrad monetize sineadoconnor postscarcity cooperativas ua cryptocurrencies coopjobs degrowth a2pico smallbusiness deliveroo intellectualproperty pla kommerzialisierung GitPay Fedigrowth gdp coopsday deplatforming timebank coop cooperativismo smallbusinesses europeancentralbank banknotes whyBitcoin cryptocurrency infoshop sine grow telecoop growth DesignForDisassembly limits fuckfoodbanks btc values banks planetary plannedObsolence planet worldbank scoop cooperativa AlternativeEconomics
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
climate
- YouStormOutlook metoffice heatwave energyconsumption consumption riscos energy energyuse SoilCarbon vampire renewables fuel clouds apollo racisme antira greenhousegas ClimateEmergency openscience overconsumption renewableenergy climatejustice ipcc ClimateMeme amp Nyurbinsky climateemergency climatechos gordoncampbell extremeweather ClimateAction climate climateracism renewable windenergy ClimateDenial ClimateProtection sciences ClimateStrike CycloneTauktae emissions coal climatecase climatestrike globalsouth ClimatePodcast weatherforecast kaspersky crisis foodcrisis vampiro voteclimate energyvisions klimaatcrisis environment skypack climatecrises sustainability risc ar6 fossilfuel history_of_science earthscience tramp globalwarming mitigation limitededition weather ragingqueerenergy fossilcriminals camps climatecamp ClimateRefigees Podcast windpower sealevelrise ClimateCase globally globalization climatechoas endfossilfuels emergency CarbonOffsets heatwaves basecamp exitpoll Tyskysour pollution global parisclimateagreement science fossil energyefficiency OABarcamp21 climatecatastrophe mitmachen fossilfuels Climate sky climatescience energytransition climateaction ClimateCrisis storms RacistClimate warm biofuel globalviews headlamp whisky climatemitigation environmentalism Ruttecrisis climatecrisis
+ YouStormOutlook metoffice heatwave energyconsumption consumption riscos energy energyuse SoilCarbon vampire renewables fuel clouds apollo racisme antira greenhousegas ClimateEmergency openscience overconsumption renewableenergy climatejustice ipcc ClimateMeme amp Nyurbinsky climateemergency climatechos extremeweather ClimateAction climate climateracism renewable windenergy ClimateDenial ClimateProtection sciences ClimateStrike CycloneTauktae emissions coal climatecase climatestrike globalsouth ClimatePodcast weatherforecast kaspersky crisis foodcrisis vampiro voteclimate energyvisions klimaatcrisis environment skypack climatecrises sustainability risc ar6 fossilfuel history_of_science earthscience tramp globalwarming mitigation limitededition weather ragingqueerenergy fossilcriminals camps climatecamp ClimateRefigees Podcast windpower sealevelrise ClimateCase globally globalization climatechoas endfossilfuels emergency CarbonOffsets heatwaves basecamp exitpoll Tyskysour pollution global parisclimateagreement science fossil energyefficiency OABarcamp21 climatecatastrophe mitmachen fossilfuels Climate sky climatescience energytransition climateaction ClimateCrisis storms RacistClimate warm biofuel globalviews headlamp whisky climatemitigation environmentalism Ruttecrisis climatecrisis environmentfriendlytech environmentfriendly greenenergy climatereport datascience
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
people
+ gordoncampbell Melissa harold paul Zachary JusticiaParaVictoria danielle dylan scott Barbara Kenneth theresa Denise FrankLeech louisrossmann Jesse Adam justin JonathanCulbreath elinorostrom katherine judith Karen Patricia russell Metalang99 juan diane Rebecca donna LouisRossmann olivia peter troy William denise NathanDufour Betty evelyn Christina brittany Jennifer Gregory Wayne Andrychów ethan Ralph Peter ecc americalatina jacobites jean laura betty nathan brownmark margaret alexanderlukashenko Bryan Virginia Jose Rose eric james BomberBradbury david Joshua christine haaland Billy CapitolRiot ostrom natalie daniel Jonathan Michael susan George johnny bookmark MichaelWood Lauren christina Amy kevin Natalie kenneth noahkathryn mannaggia Lawrence aaron donaldtrump gregory LindaLindas Amber alexa Robert Edward Patrick Rachel Verwaltunsgericht willemalexander bruce Forms dennis LegalCannabis Kayla frank KarenArmstrong Diane AliceHasters Donna Jack Paul Janice Brenda alexis sylvanasimons timothy vincent Alice sarah amy Daniel RobertKMerton jeff charlotte carolyn Emma Kyle Sean emily linda Olivia Eugene johnpilger Donald janet ryan Bookmarker stdavids RichardDWolff bryan DonnaStrickland Hannah anna doctorow MalcolmJohnson gretathunberg Catherine Alexander Christopher bob doris Anthony singlemarket Jean diana Beverly frances Sarah margaretthatcher Jordan peterrdevries JensStuhldreier Anna Ethan hackchrist Amanda jeremy donald NatashaAKelly mark matthew julie ryanair BenSchrader DrJessicaHutchings stephanie Jerry SEKFrankfurt Diana David Linda adam richard henry RoyalFamily Isabella elizabeth nachrichten steven jessica Walter dry jeffrey Kevin Justin mountanMaryland grace martinluther PeterGelderloos brandon mary anwarshaikh jamesbaldwin sharon nicholas Benjamin GeorgeFloyd amanda Emily Ruth heather stephenlawrence albert julianassange Julie marktwirtschaft nancy stephen Cannabis James CarlSpender Megan bettydog Raymond eugenetica michelle frankgehtran Nancy Fedimarket Frances Henry andrew kevinRuddCoup Jessica zurich IgorBancer julia marketing Dorothy BadVisualisation LoganGrendel Jason Charles JonathanMorris Danielle Brandon jose noamchomsky virginia beverly obituary ronald Bob BarbaraKay madison alberta ceph Helen MarkoBogoievski Jeff helen Sophia larry bookmarks dorothy Dennis JamesEPetts monbiot Nicholas Frank jack Stephen Janet ScottRosenberg georgemonbiot Alexis Pamela Jacqueline Dylan roy brenda jackal jesse Roger Jeffrey Brittany Shirley putkevinback Nathan christopher Carol Susan jason Philip Logan sandra jacob rose isabella Cynthia Joan jackieweaver aldoushuxley Maria martha Randy SarahEverard carl kyle karen raymond alice jerry carol RussellBrown Victoria Steven Douglas Lisa JonathanZittrain Julia joshua jacqueline Ashley assange eugene Bruce Albert Austin thomas Evelyn Gary Scott kimberly lawrence virgin jennifer Russell austin erdogan betterlatethannever ShhDontTellJack logan Laura Chris walters Teresa GeorgeGalloway Aaron Keith brian marktwain maryanning LamySafari maria Joseph Andrew Vincent Katherine Joyce NathanJRobinson lauren Ryan amber davidgraeber UrsulaFranklin alan ralph princephilip DennisTheMenace megan Kathleen sophia Cheryl abigail cynthia john richardstallman Alan AnnihilationOfCaste Debra GeorgeHoare arthurgloria mariadb LouisFurther Christine marilyn anthony chris Berichte Elizabeth sean Louis Larry AnnSophieBarwich christian deborah billy Abigail joesara AndreaBeste keith Jeremy CapitolRiots markkennedy zachary ruth Grace teresa Doris benjamin Willie george PeterHitchens methane barbara scottish Charlotte philip DaveCunliffe ethanzuckerman randy Margaret Heather Bradley Jacob shirley pamela Matthew Nicole joan judy Kelly savannah Brian melissa Sandra stallman markstone joseph oberverwaltungsgericht andrea shamelessselfplug Joe Sara robert alicevision aaronswartz better Bobby emma willie william angela rich SachaChua samuel Postmarketos tyler Thomas John kroger patricia ashley bobby roses kelly fuckamerica ThomasCahill hannah Carolyn Ann CrimsonRosella Jeangu gary wayne DavidRose Marilyn Deborah christenunie rms Sharon gare Mary frankfurt Samuel BreonnaTaylor Mark walter rebecca RaymondHill helendixon Madison Juan lisa cheryl janice ChristopherTrout jeffreyepstein Christian gerald Timothy roger edward bradley Gerald PiersMorgan patrickrachel framalang Kimberly steve Gabriel Marie EmmaFerris PeterHoffmann PaulBaran louis kathleen Arthur Gloria terry royals freejeremy bernardhickey Richard jonathan Harold shame Roy samantha DavidSeymour Carl chalice Eric AndreiKazimirov RebeccaHarvey relationships visuallyimpaired nicole Andrea Judith Terry Stephanie Johnny Angela Noah Kathryn RichardBoeth Ronald AskVanta Michelle Theresa gabrielmarie Samantha Judy michael charles GeorgeGerbner Tyler philipmorris amaryllis DouglasPFry kayla catherinealexander Martha debra JohnMichaelGreer stevewozniak joyce julialang JonathanDollimore ProfMichaelBaker ChristinePeterson bettytest JacobNielsen FrankfortSchool HannaArendt PaulWolfowitz JoschaBach LexFridman ShinzenYoung shameonyou bernard
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
art
- proudhon productivity cherrytree Fediverse oilpaint economiasolidaria arttips mastoartist paperart libreart cali TraditionalArt Linke subresourceintegrity glitchart Art ocart robincolors resource urban article penandink webcomics startpage CommissionsOpen glassart afrique martialarts watercolours artsurbains artalley artvsartist2020 circulareconomy abstract artreference commission horrorart Earthquakes poe nomadbsd proxmoxve MartyMacMarty tgif coloringpage dccomics colored inkscape blink modelrailway artificalintelligence draw circuitsculpture ttip watercolor proceduralart existentialcomics FediverseGuide resources poetesss memes pinksterlanddagen FediverseForum ghibligif speedpaint SankeyCharts bengarrison subpoena autisticartist barrigòtic art sona animalart krita foraBolsonaroGenocida insights FreeColouringPage anthroart urbanart sigh queerart deviantart communityresources desigualdad pastel fantasyart drawings 20thcenturyillustration grafana daria artdeco adultcolouring source J19forabolsonaro collective openstreeetmap cryptoart politicalprisoners fantasy collage jordanlynngribbleart theGalley ToryParty educpop TheArtsHole linksunten risograph pro links CodeZwart thinkabout dndmemes fanfic articles protein forabolsonaro PartyPooperPost harmreductionart adhdmeme MastoArtHeader openra demoscene witch FreeArtLicense wallpaper generative political streetart coverart streetcomplete fountainpen stickers partners watercolour economy combyne freeculture fiberart PalestinianPoliticalPrisoners jet labyrinth educators mermay dpa artsale edu MastoArt particl PrisonNews FediverseApp urbansketchers ParticlV3 creativetoots culture ganart evenepoel opencl fiberarts polArt ink painting Leitartikel marten opencoop digitalart comic flyingonthewater kenmurestreet libreculture sartre artwork mandala FediverseTown b3d politicalcartoon blackart artsderue makingcomics glitch politicalprisoner junkart wallpapers railway linker riso xkcd supportartists proctorio drawtober startinblox comics intelligence linkinbio conceptart mastoart urbanterror illustration artopencall Hinkley gnuimagemanipulationprogram os studioghibli 2MinuteSketch wireart cartoon artistontwittter oc csa AccidentalGraffiti eink OriginalCharacter educator farts hattip poezio webcomic fleischproduktion nekodoodle DigitalArt pinkwashing partnership potentieldememe oilpainting kickstarter furryart twinkle DisabledArtist unixstickers pink fursona afriquedusud comicsans openstreetmap inkjet generativeart VaccineApartheid sticker enbyart originalart arts heartmind artbreeder 17maart fart TsunderdogArt videoart ivalice adultcoloring djmartinpowers arttherapy Cartudy extreemrechts fractal enby TattoosOfTheFediverse doodle artikel WorldLocalizationDay colouringpage worldwaterday NFTart netart signalstickers memex artschool digitalpainting intel politicaltheatre artvsartist dorktower maart abstractart drawing sig circular adhd sculpture artist pcbart meme cultureshipnames concretepoetry artwithopensource pinkwug Streeck VTMtober commissions pronouns opencallforartists DesolateEarthForThePoor VizierOpLinks commissionsopen fanon KartaView alroeart article17 fountainpenink MartinVanBeynen peppertop speedpainting animalpainting visionaryart blackartist worldpay figureart zine artists heart quickdraw error supportthearts genart urbanfantasy stickerei CurzioMalaparte tree lineart smartcard pixelart alisajart openframeworks professor smolzine networknomicon openrailwaymap politicalpolicing Earthstar JuliaHartleyBrewer fan digitalArt artistsOfMastodon glitchsoc paintings railways mermay2021
+ proudhon productivity cherrytree Fediverse oilpaint economiasolidaria arttips mastoartist paperart libreart cali TraditionalArt Linke subresourceintegrity glitchart Art ocart robincolors resource urban article penandink webcomics startpage CommissionsOpen glassart afrique martialarts watercolours artsurbains artalley artvsartist2020 circulareconomy abstract artreference commission horrorart Earthquakes poe nomadbsd proxmoxve MartyMacMarty tgif coloringpage dccomics colored inkscape blink modelrailway artificalintelligence draw circuitsculpture ttip watercolor proceduralart existentialcomics FediverseGuide resources poetesss memes pinksterlanddagen FediverseForum ghibligif speedpaint SankeyCharts bengarrison subpoena autisticartist barrigòtic art sona animalart krita foraBolsonaroGenocida insights FreeColouringPage anthroart urbanart sigh queerart deviantart communityresources desigualdad pastel fantasyart drawings 20thcenturyillustration grafana daria artdeco adultcolouring source J19forabolsonaro collective openstreeetmap cryptoart politicalprisoners fantasy collage jordanlynngribbleart theGalley ToryParty educpop TheArtsHole linksunten risograph pro links CodeZwart thinkabout dndmemes fanfic articles protein forabolsonaro PartyPooperPost harmreductionart adhdmeme MastoArtHeader openra demoscene witch FreeArtLicense wallpaper generative political streetart coverart streetcomplete fountainpen stickers partners watercolour economy combyne freeculture fiberart PalestinianPoliticalPrisoners jet labyrinth educators mermay dpa artsale edu MastoArt particl PrisonNews FediverseApp urbansketchers ParticlV3 creativetoots culture ganart evenepoel opencl fiberarts polArt ink painting Leitartikel marten opencoop digitalart comic flyingonthewater kenmurestreet libreculture sartre artwork mandala FediverseTown b3d politicalcartoon blackart artsderue makingcomics glitch politicalprisoner junkart wallpapers railway linker riso xkcd supportartists proctorio drawtober startinblox comics intelligence linkinbio conceptart mastoart urbanterror illustration artopencall Hinkley gnuimagemanipulationprogram os studioghibli 2MinuteSketch wireart cartoon artistontwittter oc csa AccidentalGraffiti eink OriginalCharacter educator farts poezio webcomic fleischproduktion nekodoodle DigitalArt pinkwashing partnership potentieldememe oilpainting kickstarter furryart twinkle DisabledArtist unixstickers pink fursona afriquedusud comicsans openstreetmap inkjet generativeart VaccineApartheid sticker enbyart originalart arts heartmind artbreeder 17maart fart TsunderdogArt videoart ivalice adultcoloring djmartinpowers arttherapy Cartudy extreemrechts fractal enby TattoosOfTheFediverse doodle artikel WorldLocalizationDay colouringpage worldwaterday NFTart netart signalstickers memex artschool digitalpainting politicaltheatre artvsartist dorktower maart abstractart drawing sig circular adhd sculpture artist pcbart meme cultureshipnames concretepoetry artwithopensource pinkwug Streeck VTMtober commissions pronouns opencallforartists DesolateEarthForThePoor VizierOpLinks commissionsopen fanon KartaView alroeart article17 fountainpenink MartinVanBeynen peppertop speedpainting animalpainting visionaryart blackartist worldpay figureart zine artists heart quickdraw error supportthearts genart urbanfantasy stickerei CurzioMalaparte tree lineart smartcard pixelart alisajart openframeworks professor smolzine networknomicon openrailwaymap politicalpolicing Earthstar JuliaHartleyBrewer fan digitalArt artistsOfMastodon glitchsoc paintings railways mermay2021 fractionalScaling FediverseOrigins colors glitchSoc ResourceDepletion ADayOffTwitch ArtistResidency artresource
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
other
+ hattip ageassurance pentester bullshit klimaatbeleid justasleepypanda extinctionrebellion fail masseffect lastpass yolo nothingnew Lastpass extinction weareclosed happy efail bripe MasseyUniversity PassSanitaire solution dansenmetjanssen messageToSelf TagGegenAntimuslimischenRassismus quecksilber itscomplicated Erzvorkommen test isntreal gentests rzeźwołyńska massextinction misc tw rants manutentore frantzfanon shots assaultcube shitpost denachtvanjanssen biomassacentrale mining rising devilsadvocate ACA pinside xp impfpass cda rant Terrassen righttodisassemble rassismus MassoudBarzani koerden CovPass nahrungskette SomeUsefulAndRelvantHashtag LanguageHelpForMigrants nsfw dungeonsAndDragons biomass rassismustötet oversleep ass id Chiacoin futtermittel CubanProtests geo oerde m assassinfly migrantstruggles sleep PointlessGriping close decluttering OCUPACAOCARLOSMARIGHELLA happyfriday uxfail
+
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
hardware
+ intel plugandplay bluetooth printnightmare singleboardcomputer purism dating schematics opennic tektronix zomertijd librehardware BoBurnham restauration rmw riscv solarpower carbonFootprintSham mietendeckel PersonalComputer cyberdeck PineCUBE firmware tex keyboards debuerreotype electron ChromebookDuet AbolishFrontex webcam bond hibernation PneumaticLoudspeakers schreibmaschine imac Nottingham schwarmwissen elitesoldat handheld screenless megapixels BibliothekDerFreien KeepTheDiskSpinning homebrewcomputing FarmersTractorRally pinebook farming modem lowtech biblatex allwinner daten home pimeroni 68 lebensmittelsicherheit industrial hambibleibt analogcomputing homer TrueDelta keyboard screenprinting robotics Pinecil mutantC raspberrypi3 pocketchip oshw misterfpga noisebridge disapora T440p ArmWorkstation datensicherheit latexrun hardwarehacking mer picodisplay laptops electronics scuttlebutt ham teamdatenschutz charm SectorDisk wolnabiblioteka preprint permacomputing uart panasonic pcb almere armbian performance kopimi printmaker deck making hambi powerpc solar ssd acoustics ibmcompatible webcams modular larp tweedekamer cybredeck latex 3dprinted MacBook emmc ipadproart computing laptop solarpunk isa recycling modularsynth apparmor repairability macbook theatrelighting pc lenovo updates fairelectronics industrialmusic librem carbonsequestration electronica sed TokyoCameraClub MacBookProService pocket box86 JingPad righttorepair mac trackball fuse date solarpunkactionweek ibm 3dprinting electro carbon MechcanicalKeyboards netbook hardware m68k pisa retrohardware pinetab sicherheit openhardware raspberrypi irobot datenautobahn webtoprint 3dprinter barcode lüneburg Quartz64 PlanetComputer jtag ebu merseyside itsicherheit CompressedAirAmplification pinetime screens pinebookpro lebensmittel 3d batteries PinebookPro 3dprint pim Handprint modemmanager securescuttlebutt keyboardio mechanicalkeyboard electronicmusic solarpunks carbondioxide robot arm lowerdecks sonic ipad FireAlarms PinePower paperComputer amd openpower poweredSpeaker devopa a64 eeepc bahn F9600 rpi4 thinkpad RaspberryPiPico iot dat BeagleV arm64 merveilles repairable sbc circuitbending raspberrypi4 print displayport akihabara analog electronic FrameworkLaptop wireless filmmaking playdate mechanicalkeyboards svendsolar solpunk xiaomi
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
sport
- billiard darts olympics2020 swim olympics motorsport snooker sports locksport swimming trailrunning marathon hockey aikido bouldering diving baseball Millwall mma mammal sailing athletics nook olympic dumpsterdiving sportsball bing skating skiing sport footballers climbing football combatsports golf
+ billiard darts olympics2020 swim olympics motorsport snooker sports locksport swimming trailrunning marathon hockey aikido bouldering diving baseball Millwall mma mammal sailing athletics nook olympic dumpsterdiving sportsball bing skating skiing sport footballers climbing football combatsports golf tuebingen unitübingen tübingen
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
games
- miniature appdesign gameofshrooms minecraft soloRPG nbsdgames karma tetris99 gamestop libregaming ageofempires mondragon BiophilicDesign videogame ksp TerraNil productdesign dungeonmaster gogodotjam AudioGame runequest miniatures dragonfall boardgames computergames creature fucknintendo fudgedice angrydesigner gameassets gamestonk fossgaming videogames FediDesign gameboy puzzle indiegames gamedesign shadowrun spot godotengine adventuregames chess gamejam nintendoswitch mudrunner mud indiegame game 0ad dragon playlog gameart orca sdg lovewood designfail opengameart sign asset gilgamesh fudgerpg ttrpg fudge gamedev freegames guildwars2 creaturedesign bideogames adventuregame TetrisGore gaming gamemaker gameing nintendo roleplayinggames itch unvanquished gamesdonequick Gamesphere devilutionx rpg gamespot tetris dosgaming supertuxkart freegaming DnD socialdesign karmaisabitch cyber2077 godot gamestudies tarot cyberpunk2077 gamesforcats FreeNukum spelunkspoil boardgaming supermariomaker2 neopets minetest omake guildwars dice dnd games playing
+ miniature appdesign gameofshrooms minecraft soloRPG nbsdgames karma tetris99 gamestop libregaming ageofempires mondragon BiophilicDesign videogame ksp TerraNil productdesign dungeonmaster gogodotjam AudioGame runequest miniatures dragonfall boardgames computergames creature fucknintendo fudgedice angrydesigner gameassets gamestonk fossgaming videogames FediDesign gameboy puzzle indiegames gamedesign shadowrun spot godotengine adventuregames chess gamejam nintendoswitch mudrunner mud indiegame game 0ad dragon playlog gameart sdg lovewood designfail opengameart sign asset gilgamesh fudgerpg ttrpg fudge gamedev freegames guildwars2 creaturedesign bideogames adventuregame TetrisGore gaming gamemaker gameing nintendo roleplayinggames itch unvanquished gamesdonequick Gamesphere devilutionx rpg gamespot tetris dosgaming supertuxkart freegaming DnD socialdesign karmaisabitch cyber2077 godot gamestudies tarot cyberpunk2077 gamesforcats FreeNukum spelunkspoil boardgaming supermariomaker2 neopets minetest omake guildwars dice dnd games playing dungeonsanddragons subwaycreatures
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
accessibility
+ orca you a11y accessibility captionyourimages hardofhearing
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
bots
posthumanism mrrobot human dehumanification Militanzverbot nobot botanists humanity militanzverbot Sabot44 humanrobotinteraction therobots humanetechnow verbote humankind
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
events
- neverforget TuesdayVibe award daffodilday carbonemissions OONIbday waybackwednesday thursdayvibes fridayfilm todayilearned ShowYourStripesDay thursdaythought sun futuroparanissan IndigenousPeoplesDay5 notifications hissyfit ddosecrets solo throwbackthursday nissan valentinesday adventskalender live dos livehack Day deepthoughts solorpg thingaday idahobit screenshotsaturday warmingup thursdaythoughts fridays ipv hackathons thursdaymorning Gesundheitskrise throwback RomaDay assweek animalsweatersunday justwatched TooMuchScreenTime beethoven250thbirthday valentine humanrightsday time followfriday wednesdaythought afediversechristmas whydopeopledoshitlikethis birthdaypresent festivals wednesdaymotivation early MayDay2021 SwissOvershootDay IllustrationDay bigbrotherawards cccamp19 lovewhereyoulive screenshot thelibrary PostLikeYouThinkACrabWouldSunday showerthoughts BIJ1 worldpenguinday animal ScreenshotSaturday beethoven anarchymonday treibhausgasemissionen solokey tipoftheday Verkiezingsfestival future primeday IRL paperoftheday bundesnetzagentur thimblefulthursday FreeAssangeYesterday 100DaysToOffload iScreech hackathon ff kids holiday folklorethursday LURKbirthday tomorrowspaperstoday wenbiesday punday ipv4flagday ipv6 christmas livecoding verfassungsschutz weeknotes LINMOBlive week FlashFictionFriday mothersday gsd koningsdag scree concert folklore festival FridayFolklore pride poll screenshottuesday animals VerkiezingsfestivalBIJ1 motivation towertuesday doesliverpool fujifilmxt2 Docuthon Nakbaday kdenlive dontstarve onthisday GlobalMayDay2021 simplescreenrecorder insideoutsockday screenshots livestream blissos whiskerswednesday BowieDay morningcrew theskytoday InternationalAsexualityDay tzag TinyTuesday FridaysForFuture sunday notification Koning weekendvibes screenshotsunday worldenvironmentday2021 showerthought library koningshuis cree VerseThursday liverpool waitangiday esc2021 bigbrotheraward caturday adayinthelife goodmorning Caturday day InternationalCheetahDay flatfuckfriday songfestival ItchCreatorDay iss RabbitRoadTrip2021 interestingtimes sideprojectsunday birthday sixonsaturday supdate StPatricksDay2021 koningsdag2021 wordoftheday theweeknd christmaslights AfricaDay livefree CancelCanadaDay worldenvironmentday fridaysforfuture nationallibraryweek meetup FathersDay transpride sex kidsthesedays rechtsextreme
+ neverforget TuesdayVibe award daffodilday carbonemissions OONIbday waybackwednesday thursdayvibes fridayfilm todayilearned ShowYourStripesDay thursdaythought sun futuroparanissan IndigenousPeoplesDay5 notifications hissyfit ddosecrets solo throwbackthursday nissan valentinesday adventskalender live dos livehack Day deepthoughts solorpg thingaday idahobit screenshotsaturday warmingup thursdaythoughts fridays ipv hackathons thursdaymorning Gesundheitskrise throwback RomaDay assweek animalsweatersunday justwatched TooMuchScreenTime beethoven250thbirthday valentine humanrightsday time followfriday wednesdaythought afediversechristmas whydopeopledoshitlikethis birthdaypresent festivals wednesdaymotivation early MayDay2021 SwissOvershootDay IllustrationDay bigbrotherawards cccamp19 lovewhereyoulive screenshot thelibrary PostLikeYouThinkACrabWouldSunday showerthoughts BIJ1 worldpenguinday animal ScreenshotSaturday beethoven anarchymonday treibhausgasemissionen solokey tipoftheday Verkiezingsfestival future primeday IRL paperoftheday bundesnetzagentur thimblefulthursday FreeAssangeYesterday 100DaysToOffload iScreech hackathon ff kids holiday folklorethursday LURKbirthday tomorrowspaperstoday wenbiesday punday ipv4flagday ipv6 christmas livecoding verfassungsschutz weeknotes LINMOBlive week FlashFictionFriday mothersday gsd koningsdag scree concert folklore festival FridayFolklore pride poll screenshottuesday animals VerkiezingsfestivalBIJ1 motivation towertuesday doesliverpool fujifilmxt2 Docuthon Nakbaday kdenlive dontstarve onthisday GlobalMayDay2021 simplescreenrecorder insideoutsockday screenshots livestream blissos whiskerswednesday BowieDay morningcrew theskytoday InternationalAsexualityDay tzag TinyTuesday FridaysForFuture sunday notification Koning weekendvibes screenshotsunday worldenvironmentday2021 showerthought koningshuis cree VerseThursday liverpool waitangiday esc2021 bigbrotheraward caturday adayinthelife goodmorning Caturday day InternationalCheetahDay flatfuckfriday songfestival ItchCreatorDay iss RabbitRoadTrip2021 interestingtimes sideprojectsunday birthday sixonsaturday supdate StPatricksDay2021 koningsdag2021 wordoftheday theweeknd christmaslights AfricaDay livefree CancelCanadaDay worldenvironmentday fridaysforfuture nationallibraryweek meetup FathersDay transpride sex kidsthesedays rechtsextreme monday docshackathon ddos nowavailable
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
books
+ library readinggroup bookstore publicvoit bookbinding preview justhollythings secondhandbooks bookclub fake earthsea review ebooks docbook book notebook public amreading publishing republicday publichealth bookworm bookwyrm 5minsketch artbook republique bookreview reading sketching theLibrary audiobooks Gempub selfpublishing sketchbook wayfarers books peerreview bookreviews failbooks sketch ebook wikibooks booktodon epub cookbook bibliothèque AnarchoBookClub deepfakes monthlyreview
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
politics
- hate biometrics conspiracytheory TakeOurPowerBack redessociais solidarität trump Anarchy association cia socialjustice neoliberalisme eee workerowned alwaysantifascist sabotage qtibpoc VivotecniaCrueldadAnimal solidarityeconomy pressfreedom community systemicracism wageslavery immigration antifascismo liberal telemetry dissent liberation unions endprisonslavery laws fascism farmersrprotest techtuesday warc skyofmywindow techthursday nooneisillegal capitale freedomofspeech anarchist prochoice freeexpression EthnicCleansing anticapitalist RacialHealing fascisme liberalisme humanrights Anarchisme crime leftists turkish Socialism ukpol FreeKeithLamar Antifascisme copwatch capitalismkills fireworks homeless menschenrecht left petition BorisJohnson meteorología independant antifaschismus freedom EURvalues greens photomanipulation techtalk bikesforrefugees housingcrisis techdirt ontologicalanarchy labourabolition techsit union tories abolitionnow anarchism wegmetdemonarchie abuse DefundThePolice nazis earthship SocialCritique repression legaltech technews pelmets Jurastil meto devimage legal meeting polizeigewalt dannenröderwald venturecapital FediAntifa police nzpolitics multicast antifascists oilwars multiverse antropocene kommunismus censored postttruth technik rightorepair control nuclear bjp ThirdRunway conservatives multi seaslug UnitedInDiversity maidsafe testing nazisme hierarchy avatars chehalisrivermutualaidnetwork vat ImmigrationEnforcement election republicans opinie diversity solidarity chipstrike techwear communitycontrol metantispecismo hypocrits slavery sociaalDarwinisme metoo Avanti anticiv refugeeswelcome Coronariots seashepherd ecotech reform2 mybodymychoice generalstrike fuckBiden call2power DefendDemocracy personhood wildfire neoliberal antipolitics charity AntiLiberalisme abolition digitalfreedom transrightsarehumanrights ScottishElections2021 mayday unionyes again hatespeech fascists antropoceno policerepression LateStageOfCapitalism earth stopchasseacourre solawi ciencia smashturkishfascism afropessimism antivax cognition fedibikes Electricians apartheidisrael burntheprisons conservation seamonkey qt trumpism cyberlaw bossnapping peerproduction policiaasesina atlantik corporations iww pushbacksareillegal indianpirates DisabilityPolicy vice SomethingIsGoingWrongHere til labor intersectional commons choice depressionen feelthefreedom Riot corporatewatch postcapitalism intersectionalfeminism smalltechnology wageslave uspol frontex quarantine communism mutualaidpdx RemoveThePolice makecapitalismhistory deathvalley NewPoliticalMap chipocalypse criminalization abolishpolice nationalisme oist methaan anarchisten Immigration competition biometric brexitreality neoliberalism NeverTrustSimone socialecology wald whistleblower wroclawskierewolucjonistki icons MutualAid capitalism technology ACAB prisons unsolicitedadvice feministhackmeetings wealth supremecourt conspiracytheories corporatecrime DirectAction ChildLabour FossilFreeRevolution parliament communist daretocare KeirStarmer NoMoreEmptyPromises greenpeace digitalslavery bushfire censor decrecimiento helmet refugeesgr taoteching technopolice anarchismus policeviolence politiikka kapitalisme retrotechnology ZwartFront bipoc housing decriminalization decolonisation politics WarCommentary inclusivity parametric gravimetry bosch Megaprisons decreased publicknowledge antiracism government neocities greendatacenter SocialDarwinism repressions brightgreen poc privatisierung anarchisme wayfire feminist colonialism DominicCummings nzpol peoplepower homelessness Bookchin informationtechnology ClemencyNow Inauguration2021 arran Revolutionary techthoughts brexit anarchistaction antimonopoly privileged totalitarianism localelections raid privatisation stillwithher TyskySour Labour democraciasindical nonprofitindustrialcomplex death fires LabourLeaks riots freethemall bolsonarogenocida green SocialJustice neoliberaal corporateStateTotalitarianism labour BAME decolonizeyourmind alternative privilege antikapitalisme masssurveillance hamas legalcounsel AbolishPrisonsAbolishPolice despotism mntreform damangecontrol earthovershootday palantir DecentraliseThePlanet anti surfaceworldblows ecofascism opentechnologyfund depression nuclearpower popularitycontest usestandardpowerjacksforfucksake pdxmutualaid PoliceTenSeven LhubTV SocietalChange facialrecognition ModiFailsIndia cotech antisemitism politicaeattualità corruption florespondece hypocrisy BernieSandersMeme staterepression anarchy fire colonization Feminism propaganda dcc greenit endsars celebratingfreedom Antillia corporateState SocialCentres decolonization digitalrights feminism freepress Lhub HightechProblems datacenter osstotd academictwitter farm problem hochschwarzwald collaboration pentesting polizei neo democracy anarchistki Govts antikapitalismus powerpolitics bikes 18Source hungerstrike censorshipBook radicaltech 56aInfoshop saytheirnames witchesagainstwhitesupremacy gulag digitalmarketsact yes socialist conspiracy anarchistbookclub redandanarchistskinheads peace housingproject hostileenvironment technically lawyer corporate osint radicaldemocracy endmodernslaveryatsea PritiPatel nationaalparkdebiesbosch stonewallwasariot oiseau surveillance latestagecapitalism bos racist economiafeminista cancelculture postcolonial Syndicalism callfortesting dec AmbLastillaAlCor Selfsuffciency nonazis MexicanRevolution elections ACABPoland greatgreenwall RussellMaroonShoatz LhubSocial OctoberRevolution bigproblems logitech methods Flatseal repressionen commonspub warcrimes sea policing white governance waldstattasphalt prisoners earthday2021 warrants policebrutality techshit earthday antirepression capitalismo borisjohnson wildfires fritolaystrike ACABSpring2021 technopopulism Anarchist deepspeech notacopshop body johnson rhetoric press routerfreedom Anarchism mutuality StillTwitterpated whitehouse metropolitanpolice espresso LabourParty haltandcatchfire freedomofexpression censorship deathbycapitalism communities CancelCulture decolonize deconstruct HanauWarKeinEinzelfall musictechnology EatTheRich druglawreform keinmenschistillegal immigrationraids emmet racism fascisten decenterwhiteness Biden kapitalismus FossilFreePolitics ChineseAppBan multiplesklerose todoist cooperative trespass modi NtechLab antifa alternativen law prison chip LabourMovement deathtoamerica manipulation ParticipatoryCultureFoundation firetotheprisons consumer solidaritaet PlanetarySocial britpol financial gravimetrie BiodiversityDay Capitalism surveillancecapitalism leftist greenland general Revolution ukpolitics greenparty mdcommunity glenngreenwald support JeremyCorbyn blacklivesmatter freedomofthepress academicfreedom wled HeinsbergStudie apartheid FreeAlabamaMovement Anarchismus bundespolizei strike mononeon rentstrike evergreen equality dsa informationstechnik piracy liberty lawandorder feminismus migration power IndividualSovereignty oiseaux techmess neoist edtech capitalismenumérique mutualaid capital waldspaziergang cymru multipleexposure socialsolidarityeconomy humanetechnology criminal AbolishPrison solidaritynotcharity anarchists fascist righttochoice InformationAsymmetry inequality vim apocalypseworld DefundSurveillanceCapitalism feministserver prisonersupport platformcapitalism decolonizeconservation anarchistprisoners whistleblowers polizeiproblem notallmen opensoundcontrol hf prisonabolition fightthepower UniversalBasicServices fuckcapitalism speech uselection IDPol Antifa deathtofascism mediafreedom lesanarchistes libertarianism Slavetrade PostTrade met democracia antitrespass drugtesting populism selfcensorship consumerism greenwashing ourstreets reform MeToo failedstatesaxony extremist bright freespeech comune anticonsumerism kapital refugee neorodiversiteit whitesupremacy SueveillanceCapitalism refugees BlackProtestLegalSupport riot BernieSanders texttospeech acab ecology yesminister realcompetition antifascist SurveillanceCapitalism vimeo antifascism GlobalCapitalism Politics homeoffice bodyshaming empowerment whitepaper pdx seascape freewestpapua eris AnarchistUnionofIranandAfghanistan hambacherwald dui nyt justice powstaniewgetciewarszawskim sunnytech FolksWhoFailAtCapitalism expression feudalism espressif violence legalmatters academic tech capitalismodisorveglianza
+ hate biometrics conspiracytheory TakeOurPowerBack redessociais solidarität trump Anarchy association cia socialjustice neoliberalisme eee workerowned alwaysantifascist sabotage qtibpoc VivotecniaCrueldadAnimal solidarityeconomy pressfreedom community systemicracism wageslavery immigration antifascismo liberal telemetry dissent liberation unions endprisonslavery laws fascism farmersrprotest techtuesday warc skyofmywindow techthursday nooneisillegal capitale freedomofspeech anarchist prochoice freeexpression EthnicCleansing anticapitalist RacialHealing fascisme liberalisme humanrights Anarchisme crime leftists Socialism ukpol FreeKeithLamar Antifascisme copwatch capitalismkills fireworks homeless menschenrecht left petition BorisJohnson meteorología independant antifaschismus freedom EURvalues greens photomanipulation techtalk bikesforrefugees housingcrisis techdirt ontologicalanarchy labourabolition techsit union tories abolitionnow anarchism wegmetdemonarchie abuse DefundThePolice nazis earthship SocialCritique repression legaltech technews pelmets Jurastil meto devimage legal meeting polizeigewalt dannenröderwald venturecapital FediAntifa police nzpolitics multicast antifascists oilwars multiverse antropocene kommunismus censored postttruth technik rightorepair control nuclear bjp ThirdRunway conservatives multi seaslug UnitedInDiversity maidsafe testing nazisme hierarchy avatars chehalisrivermutualaidnetwork vat ImmigrationEnforcement election republicans opinie diversity solidarity chipstrike techwear communitycontrol metantispecismo hypocrits slavery sociaalDarwinisme metoo Avanti anticiv refugeeswelcome Coronariots seashepherd ecotech reform2 mybodymychoice generalstrike fuckBiden call2power DefendDemocracy personhood wildfire neoliberal antipolitics charity AntiLiberalisme abolition digitalfreedom transrightsarehumanrights ScottishElections2021 mayday unionyes again hatespeech fascists antropoceno policerepression LateStageOfCapitalism earth stopchasseacourre solawi ciencia smashturkishfascism afropessimism antivax cognition fedibikes Electricians apartheidisrael burntheprisons conservation seamonkey qt trumpism cyberlaw bossnapping peerproduction policiaasesina atlantik corporations iww pushbacksareillegal indianpirates DisabilityPolicy vice SomethingIsGoingWrongHere til labor intersectional commons choice depressionen feelthefreedom Riot corporatewatch postcapitalism intersectionalfeminism smalltechnology wageslave uspol frontex quarantine communism mutualaidpdx RemoveThePolice makecapitalismhistory deathvalley NewPoliticalMap chipocalypse criminalization abolishpolice nationalisme oist methaan anarchisten Immigration competition biometric brexitreality neoliberalism NeverTrustSimone socialecology wald whistleblower wroclawskierewolucjonistki icons MutualAid capitalism technology ACAB prisons unsolicitedadvice feministhackmeetings wealth supremecourt conspiracytheories corporatecrime DirectAction ChildLabour FossilFreeRevolution parliament communist daretocare KeirStarmer NoMoreEmptyPromises greenpeace digitalslavery bushfire censor decrecimiento helmet refugeesgr taoteching technopolice anarchismus policeviolence politiikka kapitalisme retrotechnology ZwartFront bipoc housing decriminalization decolonisation politics WarCommentary inclusivity parametric gravimetry bosch Megaprisons decreased publicknowledge antiracism government neocities greendatacenter SocialDarwinism repressions brightgreen poc privatisierung anarchisme wayfire feminist colonialism DominicCummings nzpol peoplepower homelessness Bookchin informationtechnology ClemencyNow Inauguration2021 arran Revolutionary techthoughts brexit anarchistaction antimonopoly privileged totalitarianism localelections raid privatisation stillwithher TyskySour Labour democraciasindical nonprofitindustrialcomplex death fires LabourLeaks riots freethemall bolsonarogenocida green SocialJustice neoliberaal corporateStateTotalitarianism labour BAME decolonizeyourmind alternative privilege antikapitalisme masssurveillance hamas legalcounsel AbolishPrisonsAbolishPolice despotism mntreform damangecontrol earthovershootday palantir DecentraliseThePlanet anti surfaceworldblows ecofascism opentechnologyfund depression nuclearpower popularitycontest usestandardpowerjacksforfucksake pdxmutualaid PoliceTenSeven LhubTV SocietalChange facialrecognition ModiFailsIndia cotech antisemitism politicaeattualità corruption florespondece hypocrisy BernieSandersMeme staterepression anarchy fire colonization Feminism propaganda dcc greenit endsars celebratingfreedom Antillia corporateState SocialCentres decolonization digitalrights feminism freepress Lhub HightechProblems datacenter osstotd academictwitter farm problem hochschwarzwald collaboration pentesting polizei neo democracy anarchistki Govts antikapitalismus powerpolitics bikes 18Source hungerstrike censorshipBook radicaltech 56aInfoshop saytheirnames witchesagainstwhitesupremacy gulag digitalmarketsact yes socialist conspiracy anarchistbookclub redandanarchistskinheads peace housingproject hostileenvironment technically lawyer corporate osint radicaldemocracy endmodernslaveryatsea PritiPatel nationaalparkdebiesbosch stonewallwasariot oiseau surveillance latestagecapitalism bos racist economiafeminista cancelculture postcolonial Syndicalism callfortesting dec AmbLastillaAlCor Selfsuffciency nonazis MexicanRevolution elections ACABPoland greatgreenwall RussellMaroonShoatz LhubSocial OctoberRevolution bigproblems logitech methods Flatseal repressionen commonspub warcrimes sea policing white governance waldstattasphalt prisoners earthday2021 warrants policebrutality techshit earthday antirepression capitalismo borisjohnson wildfires fritolaystrike ACABSpring2021 technopopulism Anarchist deepspeech notacopshop body johnson rhetoric press routerfreedom Anarchism mutuality StillTwitterpated whitehouse metropolitanpolice espresso LabourParty haltandcatchfire freedomofexpression censorship deathbycapitalism communities CancelCulture decolonize deconstruct HanauWarKeinEinzelfall musictechnology EatTheRich druglawreform keinmenschistillegal immigrationraids emmet racism fascisten decenterwhiteness Biden kapitalismus FossilFreePolitics ChineseAppBan multiplesklerose todoist cooperative trespass modi NtechLab antifa alternativen law prison chip LabourMovement deathtoamerica manipulation ParticipatoryCultureFoundation firetotheprisons consumer solidaritaet PlanetarySocial britpol financial gravimetrie BiodiversityDay Capitalism surveillancecapitalism leftist greenland general Revolution ukpolitics greenparty mdcommunity glenngreenwald support JeremyCorbyn blacklivesmatter freedomofthepress academicfreedom wled HeinsbergStudie apartheid FreeAlabamaMovement Anarchismus bundespolizei strike mononeon rentstrike evergreen equality dsa informationstechnik piracy liberty lawandorder feminismus migration power IndividualSovereignty oiseaux techmess neoist edtech capitalismenumérique mutualaid capital waldspaziergang cymru multipleexposure socialsolidarityeconomy humanetechnology criminal AbolishPrison solidaritynotcharity anarchists fascist righttochoice InformationAsymmetry inequality vim apocalypseworld DefundSurveillanceCapitalism feministserver prisonersupport platformcapitalism decolonizeconservation anarchistprisoners whistleblowers polizeiproblem notallmen opensoundcontrol hf prisonabolition fightthepower UniversalBasicServices fuckcapitalism speech uselection IDPol Antifa deathtofascism mediafreedom lesanarchistes libertarianism Slavetrade PostTrade met democracia antitrespass drugtesting populism selfcensorship consumerism greenwashing ourstreets reform MeToo failedstatesaxony extremist bright freespeech comune anticonsumerism kapital refugee neorodiversiteit whitesupremacy SueveillanceCapitalism refugees BlackProtestLegalSupport riot BernieSanders texttospeech acab ecology yesminister realcompetition antifascist SurveillanceCapitalism vimeo antifascism GlobalCapitalism Politics homeoffice bodyshaming empowerment whitepaper pdx seascape freewestpapua eris AnarchistUnionofIranandAfghanistan hambacherwald dui nyt justice powstaniewgetciewarszawskim sunnytech FolksWhoFailAtCapitalism expression feudalism espressif violence legalmatters academic tech capitalismodisorveglianza fediversity deathcult greenvps communityserver fucknazis okopress techtips Feminisme surveillanceware cybercrime notmetoo precrime humanetech greifswald MorteAlCapitalismo techjobs antibodies firstworldproblems powerpoint bonfire institutionalcorruption justiceforElijah monopoly deathsentence digitalfeudalism PiratesOfTheSingleMarket dueprocess apoc
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
countries
+ turkish roma thai romania korean burma lithuania solomon chile europeanparliament Instanz boycottisrael fiji tajikistan benin paraguay eeuu icelandtrip senegal ukraine italy brunei nicaragua guyana Pflanzenbestimmung grönland euphoria zambia PalestineStrike iceland europeancouncil morocco netherlands swaziland EuropeanUnion bosnian suriname welcome2america winningatlife elsalvador russia freeburma samoa StaatstrojanerParteiDeutschlands romanian asl european czech belarus hayabusa2 bw kyrgyzstan english uk translation sanmarino catalonia panama africa west indians unitedkingdom japan Netherlands buyused venezuela gambia freeNukem kuwait barbados papua greece switzerland brasilien uae mau england FuckIsrael nigeria usa angola honduras djibouti laos sierraleone nonprofit investigation artemis britain cambodia translators ych vietnam esperanto neofeud zealios seychelles marshall kazakhstan estonia investigate tonga stlucia burundi bangladesh egypt nachhaltigkeit japanese mali congo us IcelandicVerminControl jordan MusiciansForPalestine americangods digitaleurope speedrun grenada israel lowestoft psychic algeria ghana bosnia translations russian LateAmericanSentences eritrea bhutan armenian hama hungary Störungsverbot saudi slovenia tig czechosvlovakia bahamas america libadwaita australia kiribati togo DeathToAmerica koreanorth poland Überbevölkerung malawi AlwaysBetterTogether capeverde armenia american hautrauswasgeht bahrain mozambique WichtigerHinweis abcbelarus japaneseglare americanpinemarten beleuchtung southsudan adminlife citylife europehoax Martesamericana syria german micronesia maldives iran indigenous sweden bijîberxwedanarojava ethiopia sid cuba liberia canada burkina indian Südwestgrönland somalia Chile whatshappeningintigray scotland Enlgand russiaToday vaticancity easttimor austria EuropeanUnionNews turkey yemen Bolivia denmark USBased domesticabuse austrianworldsummit madagascar finland Wales Iran philippines ivorycoast haiti ecuador Portugal azerbaijan gasuk spain albania massachusetts afghanistan europe mauritania dominica ökonomisierung thailand belize westpapuauprising nerdlife macedonia montenegro BelarusProtests ChileDesperto thenetherlands qatar mongolia costarica boatingeurope birdsofkenya Australia boat latvia uzbekistan fatigue kabelaufklärung ireland iraq malaysia mexico investigations mauritius dezentralisierung oman chad nz de georgia zimbabwe france serbia lesotho romani halflife oddmuse tunisia argentina czechia cameroon namibia sudan indonesia lifeboat colombia worldwildlifeday kryptowährung tuvalu britainology merica beckychambers turkmenistan tanzania germany trojan neuhier norway comoros auteursrecht guatemala Thailand kosovo eastgermany andorra wales indiastrikes vanlife Palestine servus pakistan belgium china 3615malife antigua life europeanvalues koreasouth newzealand visiticeland einzelfall rwanda luxembourg libya indywales italyisntreal nauru moldova bad spanish eastindiacompany northernireland stigmergic palau taiwan kenya trinidad eu botswana Lebensmittelzusatzstoff CuriosidadesVariadas jamaica vanuatu cyprus aminus3 israele malta Icelandic psychedelia niger s3 westpapua busse unitedstates myanmar saintvincent guinea nepal peru uganda uruguay india pacificnorthwest lebanon neurodiversity southafrica writer arte croatia europeanunion writerslife bolivia chinese dominican europeancommission srilanka bulgaria etherpad slovakia speedrunning gabon psychedelicart ether palestine stkitts liechtenstein saveabkindonesia neofeudalism surinam brazil shutdowncanada southamerica studyingermany sliceoflife mychemicalromance study marshallmcluhan aether croatian deutschewelle
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
places
- communedeparis lapaz luanda klimakatastrophe asunción salisbury nouakchott conakry kyiv enviromentalism gadgetbridge moscow winchester cardiff saipan gibraltar dublin KlimaGerechtigkeit stuff catalunya dannibleibt avarua lilo wolverhampton hargeisa delhi niamey chișinău freestuff colombo dundee brasília StupidComparisons brushes phnompenh mbabane danni belgrade rotterdam stasaph belmopan detroit ghent pyongyang hannover strawinsky calls ulaanbaatar oranjestad kali Reykjavik Barliman gaborone seattle ndjamena lancaster chelmsford raw singapore classicalmusic tuberlin Lanarkshire feedbackwanted preston lincoln kingedwardpoint abidjan nuuk york asshole pretoria papeete DreamtimeVillage washingtonstate bradford malé rhetorical robberfly sunderland zagreb gitega abudhabi flyingfishcove castries revil georgetown suffolk wickr hagåtña podman lochlomond videoFreex oric ella lichtenberg videofeedback borikua basseterre hamburg southeastasia afrika kinshasa Schadensersatzforderung streetartparis suva klimaatverandering valparaíso athens roseau sheffield baku aberdeen charlotteamalie antananarivo domi pristina northumberland RadentscheidJena bordeaux diff MakoYass videocalls santiago fsb sukhumi berlin urk bristol uptronicsberlin funafuti libreville newry rush radentscheid puertorico ClimateChange hanoi philipsburg tehran banjul prague Stockente rawhide andorralavella daw yerevan portauprince videoprojects sensorica mewcastle dakar asu paramaribo tifariti durham CrisiClimatica capetown rigaer94 dma tirana klima ankara ipswich managua lisbon bishkek amsterdam climatchoas kent klimaat EastVirginia portonovo santodomingo wakefield bangkok texas coventry bucharest kathmandu aden buchtipp madrid cleanarchitecture paris14 sanjuan vienna kingston stuttgart Utrecht inverness kabul damascus stockholm douglas ClassOf2015 willemstad brighton klimaschutz klimaatnoodtoestand hibernoenglish thehague panamacity RassismusTötet beirut belfast amman newdelhi tórshavn nottingham nouméa oslo alofi gustavia paris fromembers cockburntown berlinale manchester dominio ottawa classical buch stepanakert portofspain klimakrise class fsberlin honiara berniememe asmara florida nicosia helsinki anywhere taipei salford tegucigalpa bridge tokyo tashkent larochelle vr gabocom MadeInEU sarajevo algiers southampton KlimaKrise nairobi muscat monaco riyadh flying lusaka perth wellington wick bissau juba mariehamn majuro parisagreement mumiaabujamal norwich buenosaires douglasrushkoff ngerulmud dhaka berlinhateigenbedarf guatemalacity washington bedarf vatican kuwaitcity martlesham Erdmannhausen Puntarella londonboaters SystemChangeNotClimateChange bern mexicocity amap bratislava myasstodontownhall bridgetown delhipolice stokeonTrent crowsnestpass leeds tunis manila warwickshire architecture rigaer94verteidigen arctic stanley matautu copenhagen hereford barcelona lomé videocall budapest ouagadougou mogadishu PrawnOS freetown victoria bangor lora brazzaville portmoresby ashgabat kampala Klimaatalarm gigabitvoucher kirigami webassembly yorkshire elaaiún kalimantan vilnius guineabissau ContourDrawing bloemfontein gnuassembly swansea classe sucre london passalong marseille berniesanders pagopago bradesestate oakland vaduz birmingham addis lisburn nürnberg naypyidaw derry CassetteNavigation khartoum baghdad bandar truro moroni cuirass rigaer lehavre klimaliste portvila kingstown armagh Klima ulm ChrisCrawford reykjavík lofi manama accra mewport windhoek fortworth nukualofa classic ciutatvella tbilisi canberra quito maputo cetinje adams putrajaya lichfield ramallah solimaske oslotown bogotá warming portsmouth dodoma berkeley harare stirling havana warsaw klimapolitik rigaer94bleibt münster valletta snes localberlin ljubljana bamako leicester kualalumpur peterborough podgorica rabat cotonou oranje plymouth seoul westminster neumünster Portland dushanbe bangui aotearoa theCellar canterbury westisland tskhinvali palikir caracas brussel jamestown rome gloucester munich cambridge ripon carlisle freestuffberlin wells chichester sãotomé jakarta floraspondence daressalaam sansalvador seo apia essex klimawandel yaren cairo jerusalem brussels kigali southtarawa beijing minsk montevideo vientiane philips maseru klimaatopwarming hamilton lorawan lurk doha klimaatwake worcester tripoli celtic portlouis stalbans lima adamstown deventer weimar abuja fuckalabamapower saw lilongwe nassau lobamba heathrow nyc oxford fly montreal klimaatzaakshell rawtherapee dili feedback thesprawl riga r94 assembly lesbos monrovia nursultan Neuzulassung caste gab sanjosé klimaatrechtvaardigheid marigot islamabad fb malabo tallinn sahara thimphu seattleprotestnetwork klimaatzaak exeter oranjeklanten klimanotstand chester brest yaoundé praia bujumbura strawberries washingtondc derby sofia skopje
+ communedeparis lapaz luanda klimakatastrophe asunción salisbury nouakchott conakry kyiv enviromentalism gadgetbridge moscow winchester cardiff saipan gibraltar dublin KlimaGerechtigkeit stuff catalunya dannibleibt avarua lilo wolverhampton hargeisa delhi niamey chișinău freestuff colombo dundee brasília StupidComparisons brushes phnompenh mbabane danni belgrade rotterdam stasaph belmopan detroit ghent pyongyang hannover strawinsky calls ulaanbaatar oranjestad kali Reykjavik Barliman gaborone seattle ndjamena lancaster chelmsford raw singapore classicalmusic tuberlin Lanarkshire feedbackwanted preston lincoln kingedwardpoint abidjan nuuk york asshole pretoria papeete DreamtimeVillage washingtonstate bradford malé rhetorical robberfly sunderland zagreb gitega abudhabi flyingfishcove castries revil georgetown suffolk wickr hagåtña podman lochlomond videoFreex oric ella lichtenberg videofeedback borikua basseterre hamburg southeastasia afrika kinshasa Schadensersatzforderung streetartparis suva klimaatverandering valparaíso athens roseau sheffield baku aberdeen charlotteamalie antananarivo domi pristina northumberland RadentscheidJena bordeaux diff MakoYass videocalls santiago fsb sukhumi berlin urk bristol uptronicsberlin funafuti libreville newry rush radentscheid puertorico ClimateChange hanoi philipsburg tehran banjul prague Stockente rawhide andorralavella daw yerevan portauprince videoprojects sensorica mewcastle dakar asu paramaribo tifariti durham CrisiClimatica capetown rigaer94 dma tirana klima ankara ipswich managua lisbon bishkek amsterdam climatchoas kent klimaat EastVirginia portonovo santodomingo wakefield bangkok texas coventry bucharest kathmandu aden buchtipp madrid cleanarchitecture paris14 sanjuan vienna kingston stuttgart Utrecht inverness kabul damascus stockholm douglas ClassOf2015 willemstad brighton klimaschutz klimaatnoodtoestand hibernoenglish thehague panamacity RassismusTötet beirut belfast amman newdelhi tórshavn nottingham nouméa oslo alofi gustavia paris fromembers cockburntown berlinale manchester dominio ottawa classical buch stepanakert portofspain klimakrise class fsberlin honiara berniememe asmara florida nicosia helsinki anywhere taipei salford tegucigalpa bridge tokyo tashkent larochelle vr gabocom MadeInEU sarajevo algiers southampton KlimaKrise nairobi muscat monaco riyadh flying lusaka perth wellington wick bissau juba mariehamn majuro parisagreement mumiaabujamal norwich buenosaires douglasrushkoff ngerulmud dhaka berlinhateigenbedarf guatemalacity washington bedarf vatican kuwaitcity martlesham Erdmannhausen Puntarella londonboaters SystemChangeNotClimateChange bern mexicocity amap bratislava myasstodontownhall bridgetown delhipolice stokeonTrent crowsnestpass leeds tunis manila warwickshire architecture rigaer94verteidigen arctic stanley matautu copenhagen hereford barcelona lomé videocall budapest ouagadougou mogadishu PrawnOS freetown victoria bangor lora brazzaville portmoresby ashgabat kampala Klimaatalarm gigabitvoucher kirigami webassembly yorkshire elaaiún kalimantan vilnius guineabissau ContourDrawing bloemfontein gnuassembly swansea classe sucre london passalong marseille berniesanders pagopago bradesestate oakland vaduz birmingham addis lisburn nürnberg naypyidaw derry CassetteNavigation khartoum baghdad bandar truro moroni cuirass rigaer lehavre klimaliste portvila kingstown armagh Klima ulm ChrisCrawford reykjavík lofi manama accra mewport windhoek fortworth nukualofa classic ciutatvella tbilisi canberra quito maputo cetinje adams putrajaya lichfield ramallah solimaske oslotown bogotá warming portsmouth dodoma berkeley harare stirling havana warsaw klimapolitik rigaer94bleibt münster valletta snes localberlin ljubljana bamako leicester kualalumpur peterborough podgorica rabat cotonou oranje plymouth seoul westminster neumünster Portland dushanbe bangui aotearoa theCellar canterbury westisland tskhinvali palikir caracas brussel jamestown rome gloucester munich cambridge ripon carlisle freestuffberlin wells chichester sãotomé jakarta floraspondence daressalaam sansalvador seo apia essex klimawandel yaren cairo jerusalem brussels kigali southtarawa beijing minsk montevideo vientiane philips maseru klimaatopwarming hamilton lorawan lurk doha klimaatwake worcester tripoli celtic portlouis stalbans lima adamstown deventer weimar abuja fuckalabamapower saw lilongwe nassau lobamba heathrow nyc oxford fly montreal klimaatzaakshell rawtherapee dili feedback thesprawl riga r94 assembly lesbos monrovia nursultan Neuzulassung caste gab sanjosé klimaatrechtvaardigheid marigot islamabad fb malabo tallinn sahara thimphu seattleprotestnetwork klimaatzaak exeter oranjeklanten klimanotstand chester brest yaoundé praia bujumbura strawberries washingtondc derby sofia skopje jest newyorkcity NewYorkflooding helm
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
internet
homeserver datasette onlinesafetybill linkeddata markdown selfsufficiency webgl LoveWattsBLM decentralised immersiveweb pep decentraliseren i2p sceptic earlyinternet Clubhouse CooperativeClouds spam firefox redecentralize NYCMesh decentral socializing Burocratic toxicmasculinity staticsitegenerator wikipedia zeitschriften maps rtmp PlasticFreeJuly dataprotection NNCP decentralization inclusiónsocial decentralize IPFSing w3c OsmFinds datacollection files dotConism offlineimap DutchPolitics internetaccess agnostic gotosocial geminispace archivists gaza selfhosted piratenpartij mapuche videohosting DarkPatternsFTC metafilter maille meta wikibase CooperativeTechnology torrent mailab geocaching freenode MollyBrown mailfence bot adblocker tox k9mail nylasmail smalltech data socialism basemap webarchive sitejs meshroom protocol anticolonial VerkehrsswendeJetzt thecloud Jabbber worldbusterssocialclub publicserviceinternet networks criticism bioinformatics online openddata centralisation flameshot internetarchaeology WordPress darkages hiddenServices chainmail datarecovery self elinks saferinternetday selfhost text SeattleHellDay contentmoderation distributed OperationPanopticon mappe mydata webhosting decentralizedweb mailman SOASJusticeforCleaners natto p2pleft socialdistancing router sysadminday protection rne dataretention speedtest ublockorigin bigdata routeros internetofthings -}
-data greenhosting selfhosting forkawesome communityhosting TikTok tilde CriminalJusticeBill networking brave panopticon aldi icann selfsustaining hosting mailart DAOs discourse digitalcolonialism weblate kinosocial libera coopserver PeerToPeer wikis dns decentralizetheweb stripe service openstandards economíasocial responsiveness nojs ejabberd amusewiki freifunk oauth Anticon tic foxes hypercore CDNsAreEvil meshtastic piratebay protonmail TubEdu standards StuffCircuit yourdataisyourdata internetfreedom mirroring onlineWhiteboard gemini antarctic zeit webui InternetCrimeFamily wlan boilemMashEmStickEmInAStew internetBanking SmallWeb fedwiki snikket redessociales fleenode ircd coopcloud cw internetshutdown democratic criticalmass masculinity datadetox mailpile clearnet cdn cloudflared liberapay pinterest brahmaputra distributedcoop xmpp semanticweb identicurse socialnetwork Disarchive selfie anticolonialism website datasets SaferInternetDay content splinternet participation highavailability webstandards mapa groenlinks domains ntp centralized cloudfront socialnetworks metadata wikileaks disconnect Meme aioxmpp database socialanxiety proton disco web3 cloudfirewall TLSmastery descentralizare icmp organicMaps oop videocast governement jabber cleanuptheweb webbrowsers webhook communications decentralized userdata selflove wiki cloudron bsi browserextensions Fragattacks RedditDown ssb darknet cookies Qute MattHancock darkweb netcat webInstaller liberachat safety uberspace map Konfekoop Reddit archiv recaptcha server browser cloudy IPFS p2p social chainmaille antisocial tiddlywiki www missioncritical FreenodeTakeover ageverification corne fortinet Pluralistic databreach opendata ilovewikipedia web WebsiteStatus ownyourdata battiato netshutdowns alttext xep callforparticipation twitch im darkmode 9front bbb quadraticvoting GaiaX gavcloud decentralise att jabberspam theserverroom antarctica shutdowns Watomatic datafree greenhost domain mesh selfemployed hackint OpenStreetMap gnusocial darkambient RudolfBerner slixmpp geminiprotocol statistics BurnermailIO irc osm eveonline pirate plaintext Graphika datacracy filesharing sysadminlife ownlittlebitofinternet squatting sysadmin misinformation rss ipns mozilla twitchbannedrevision voicemail gazaunderattack mapbox Nyxt legacyInternet yacy webrtc databases symbiotic debloattheweb crosspost fastmail sysadmins jmap mail tinycircuits bureaucratic i2pd aesthetic ipfs internetradio bravenewworld routers practice browsers wikidata selfpub decentralizeit ballpointpen puredata netscape SSH mixcloud RSS DecolonizeTheInternet gmail openculture websites letthenetwork cyberspace SwitchToXmpp messaging selfies offthegrid enxeñeríasocial cloud ddg bopwiki blabber snailmail cleanup selfdefense internet moderation decentralisation justcloudflarethings webinar metaverse qutebrowser _w3c_ socialcooling intox scholarsocial Seattle fox umap centralization ssbroom pihole serverMeddling sealioning missingmaps qtox puremaps archiving bravesearch sneakernet NatureNeedsJustice Nextcloud internetarchive dataintegration mydataismydata dweb kmail js metatext adblock dark captcha socialNetworks BlackHatSEO beakerbrowser LiberaChat openweb soulseek NetShutdown enigmail libervia failwhale onlineharms webp gooddata mailinglist kernelupgrade dot wifi Internet descentralizarea thepiratebay internetshutdowns fixtheweb mapporn contentid lazyweb servers atom kernel socialweb colonial AtomPub firewall shutdown ambient socialists kernenergie ebay zeitschrift mozillahubs instantmessaging publicservice interoperabilitate SolidProject tiktok Justice4MohamudHassan cloudflare
+data greenhosting selfhosting forkawesome communityhosting TikTok tilde CriminalJusticeBill networking brave panopticon aldi icann selfsustaining hosting mailart DAOs discourse digitalcolonialism weblate kinosocial libera coopserver PeerToPeer wikis dns decentralizetheweb stripe service openstandards economíasocial responsiveness nojs ejabberd amusewiki freifunk oauth Anticon tic foxes hypercore CDNsAreEvil meshtastic piratebay protonmail TubEdu standards StuffCircuit yourdataisyourdata internetfreedom mirroring onlineWhiteboard gemini antarctic zeit webui InternetCrimeFamily wlan boilemMashEmStickEmInAStew internetBanking SmallWeb fedwiki snikket redessociales fleenode ircd coopcloud cw internetshutdown democratic criticalmass masculinity datadetox mailpile clearnet cdn cloudflared liberapay pinterest brahmaputra distributedcoop xmpp semanticweb identicurse socialnetwork Disarchive selfie anticolonialism website datasets SaferInternetDay content splinternet participation highavailability webstandards mapa groenlinks domains ntp centralized cloudfront socialnetworks metadata wikileaks disconnect Meme aioxmpp database socialanxiety proton disco web3 cloudfirewall TLSmastery descentralizare icmp organicMaps oop videocast governement jabber cleanuptheweb webbrowsers webhook communications decentralized userdata selflove wiki cloudron bsi browserextensions Fragattacks RedditDown ssb darknet cookies Qute MattHancock darkweb netcat webInstaller liberachat safety uberspace map Konfekoop Reddit archiv recaptcha server browser cloudy IPFS p2p social chainmaille antisocial tiddlywiki www missioncritical FreenodeTakeover ageverification corne fortinet Pluralistic databreach opendata ilovewikipedia web WebsiteStatus ownyourdata battiato netshutdowns alttext xep callforparticipation twitch im darkmode 9front bbb quadraticvoting GaiaX gavcloud decentralise att jabberspam theserverroom antarctica shutdowns Watomatic datafree greenhost domain mesh selfemployed hackint OpenStreetMap gnusocial darkambient RudolfBerner slixmpp geminiprotocol statistics BurnermailIO irc osm eveonline pirate plaintext Graphika datacracy filesharing sysadminlife ownlittlebitofinternet squatting sysadmin misinformation rss ipns mozilla twitchbannedrevision voicemail gazaunderattack mapbox Nyxt legacyInternet yacy webrtc databases symbiotic debloattheweb crosspost fastmail sysadmins jmap mail tinycircuits bureaucratic i2pd aesthetic ipfs internetradio bravenewworld routers practice browsers wikidata selfpub decentralizeit ballpointpen puredata netscape SSH mixcloud RSS DecolonizeTheInternet gmail openculture websites letthenetwork cyberspace SwitchToXmpp messaging selfies offthegrid enxeñeríasocial cloud ddg bopwiki blabber snailmail cleanup selfdefense internet moderation decentralisation justcloudflarethings webinar metaverse qutebrowser _w3c_ socialcooling intox scholarsocial Seattle fox umap centralization ssbroom pihole serverMeddling sealioning missingmaps qtox puremaps archiving bravesearch sneakernet NatureNeedsJustice Nextcloud internetarchive dataintegration mydataismydata dweb kmail js metatext adblock dark captcha socialNetworks BlackHatSEO beakerbrowser LiberaChat openweb soulseek NetShutdown enigmail libervia failwhale onlineharms webp gooddata mailinglist kernelupgrade dot wifi Internet descentralizarea thepiratebay internetshutdowns fixtheweb mapporn contentid lazyweb servers atom kernel socialweb colonial AtomPub firewall shutdown ambient socialists kernenergie ebay zeitschrift mozillahubs instantmessaging publicservice interoperabilitate SolidProject tiktok Justice4MohamudHassan cloudflare taco dataareliability theinternetarchive undemocratic onlinesafety torbrowser Word oldmanyellingatcloud cisco TechWontSaveUs gajim groupcall
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
employment
justworked futureofwork InterviewQuestions jechercheunjob mywork remote employees hiring TheNetwork workingfromhome ProgrammingJob reproductivework frame workinprogress bullshitjobs car workplace DigitalNetwork antiwork workshops kreaturworks workers worklog sexworkers remotejob mainframe remotework remotejobs migrantworkers job culturalworkers DjangoJob teamwork framework hire KDEGear careers hirefedi career SocialNotworks workshop bedfordshire illustratorforhire OpenHospitalityNetwork tidyworkshops carework AtlasNetwork nowhiring KDE remoteaccess rds KDEGear21 obs workersrights obsolescence records KDEFrameworks plannedobsolescence work hertfordshire flossjobs jobs workflow precariousworkers carddav sexworker theworkshop nerdsnipe employee overwork
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
gafam
- zuckerberg caringissharing ads apple youtuberegrets antitrust SpringerEnteignen peoplefarming deletewhatsapp advertisingandmarketing chromevox GoogleDown aws AppleSearch Floc bankruptBezos googlesearch googleio mycologists bringBunysBack youtube Goggle twitterkurds banadvertising chromebook fuckfacebook headset arcgis ffs FacebookEvents AmazonMeansCops facebook wandering 100heads 20thcenturyadvertising amazon googlevoracle amazonprimeday dystopia microsoftgithub farcebook myco boycottinstagram FlocOff stopgafam genoegisgenoeg legislation amazonprime deletewhatsappday amazonring Gafam googleplus soldering GoogleForms weirdyoutuberecomendations HaringeyAnti delete FoodSharing lobbyregister degooglisation florespondance linkedin siri Apple Facebook LeiharbeitAbschaffen PoweringProgress advertising monopolies googleanalytics ausländerzentralregister adtech fuckgoogle storing plottertwitter failbook kadse microsoft deletechrome alanturing dtm poledance HeadscarfBan twitter skype azure chrome logistics googledoodles hildebrandt twitterblue corporateGiant Tracking uitkeringen FlocOffGoogle sidewalk plot zuck nogafam youtubedl degoogled Google youtubers google Microsoft stemverklaring gis walledgarden GAFCAM dt GooglevsOracle dotcoms deleteyoutube datafarms Instagram walledgardens agistri appleevent offseting Hypnagogist appleii facebookoversightboard fascistbook FuckGoogle degoogle boringdystopia fuschia ohneamazon appleiie deleteinstagram ungoogled ring stopgoogle affordances googledown decentring YouTube gafam inspiring oracle killedbygoogle fuckoffgoogle dance deletefacebook gradschool fakebook GoogleIsBad fuckoffgoogleandco office365 lordoftherings turingpi amazonas instagram TrackingFreeAds FlocBloc playstore synergistic bigtech boycottamazon amazonien whatsapp mytwitteranniversary deleteamazon bluesky Amazon
+ zuckerberg caringissharing ads apple youtuberegrets antitrust SpringerEnteignen peoplefarming deletewhatsapp advertisingandmarketing chromevox GoogleDown aws AppleSearch Floc bankruptBezos googlesearch googleio mycologists bringBunysBack youtube Goggle twitterkurds banadvertising chromebook fuckfacebook headset arcgis ffs FacebookEvents AmazonMeansCops facebook wandering 100heads 20thcenturyadvertising amazon googlevoracle amazonprimeday dystopia microsoftgithub farcebook myco boycottinstagram FlocOff stopgafam genoegisgenoeg legislation amazonprime deletewhatsappday amazonring Gafam googleplus soldering GoogleForms weirdyoutuberecomendations HaringeyAnti delete FoodSharing lobbyregister degooglisation florespondance linkedin siri Apple Facebook LeiharbeitAbschaffen PoweringProgress advertising monopolies googleanalytics ausländerzentralregister adtech fuckgoogle storing plottertwitter failbook kadse microsoft deletechrome alanturing dtm poledance HeadscarfBan twitter skype azure chrome logistics googledoodles hildebrandt twitterblue corporateGiant Tracking uitkeringen FlocOffGoogle sidewalk plot zuck nogafam youtubedl degoogled Google youtubers google Microsoft stemverklaring gis walledgarden GAFCAM dt GooglevsOracle dotcoms deleteyoutube datafarms Instagram walledgardens agistri appleevent offseting Hypnagogist appleii facebookoversightboard fascistbook FuckGoogle degoogle boringdystopia fuschia ohneamazon appleiie deleteinstagram ungoogled ring stopgoogle affordances googledown decentring YouTube gafam inspiring oracle killedbygoogle fuckoffgoogle dance deletefacebook gradschool fakebook GoogleIsBad fuckoffgoogleandco office365 lordoftherings turingpi amazonas instagram TrackingFreeAds FlocBloc playstore synergistic bigtech boycottamazon amazonien whatsapp mytwitteranniversary deleteamazon bluesky Amazon weird boycott AppleToo aiweirdness medtwitter twitterstorians
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
people
- Melissa harold paul Zachary JusticiaParaVictoria danielle dylan scott Barbara Kenneth theresa Denise FrankLeech louisrossmann Jesse Adam justin JonathanCulbreath elinorostrom katherine judith Karen Patricia russell Metalang99 juan diane Rebecca donna LouisRossmann olivia peter troy William denise NathanDufour Betty evelyn Christina brittany Jennifer Gregory Wayne Andrychów ethan Ralph Peter ecc americalatina jacobites jean laura betty nathan brownmark margaret alexanderlukashenko Bryan Virginia Jose Rose eric james BomberBradbury david Joshua christine haaland Billy CapitolRiot ostrom natalie daniel Jonathan Michael susan George johnny bookmark MichaelWood Lauren christina Amy kevin Natalie kenneth noahkathryn mannaggia Lawrence aaron donaldtrump gregory LindaLindas Amber alexa Robert Edward Patrick Rachel Verwaltunsgericht willemalexander bruce Forms dennis LegalCannabis Kayla frank KarenArmstrong Diane AliceHasters Donna Jack Paul Janice Brenda alexis sylvanasimons timothy vincent Alice sarah amy Daniel RobertKMerton jeff charlotte carolyn Emma Kyle Sean emily linda Olivia Eugene johnpilger Donald janet ryan Bookmarker stdavids RichardDWolff bryan DonnaStrickland Hannah anna doctorow MalcolmJohnson gretathunberg Catherine Alexander Christopher bob doris Anthony singlemarket Jean diana Beverly frances Sarah margaretthatcher Jordan peterrdevries JensStuhldreier Anna Ethan hackchrist Amanda jeremy donald NatashaAKelly mark matthew julie ryanair BenSchrader DrJessicaHutchings stephanie Jerry SEKFrankfurt Diana David Linda adam richard henry RoyalFamily Isabella elizabeth nachrichten steven jessica Walter dry jeffrey Kevin Justin mountanMaryland grace martinluther PeterGelderloos brandon mary anwarshaikh jamesbaldwin sharon nicholas Benjamin GeorgeFloyd amanda Emily Ruth heather stephenlawrence albert julianassange Julie marktwirtschaft nancy stephen Cannabis James CarlSpender Megan bettydog Raymond eugenetica michelle frankgehtran Nancy Fedimarket Frances Henry andrew kevinRuddCoup Jessica zurich IgorBancer julia marketing Dorothy BadVisualisation LoganGrendel Jason Charles JonathanMorris Danielle Brandon jose noamchomsky virginia beverly obituary ronald Bob BarbaraKay madison alberta ceph Helen MarkoBogoievski Jeff helen Sophia larry bookmarks dorothy Dennis JamesEPetts monbiot Nicholas Frank jack Stephen Janet ScottRosenberg georgemonbiot Alexis Pamela Jacqueline Dylan roy brenda jackal jesse Roger Jeffrey Brittany Shirley putkevinback Nathan christopher Carol Susan jason Philip Logan sandra jacob rose isabella Cynthia Joan jackieweaver aldoushuxley Maria martha Randy SarahEverard carl kyle karen raymond alice jerry carol RussellBrown Victoria Steven Douglas Lisa JonathanZittrain Julia joshua jacqueline Ashley assange eugene Bruce Albert Austin thomas Evelyn Gary Scott kimberly lawrence virgin jennifer Russell austin erdogan betterlatethannever ShhDontTellJack logan Laura Chris walters Teresa GeorgeGalloway Aaron Keith brian marktwain maryanning LamySafari maria Joseph Andrew Vincent Katherine Joyce NathanJRobinson lauren Ryan amber davidgraeber UrsulaFranklin alan ralph princephilip DennisTheMenace megan Kathleen sophia Cheryl abigail cynthia john richardstallman Alan AnnihilationOfCaste Debra GeorgeHoare arthurgloria mariadb LouisFurther Christine marilyn anthony chris Berichte Elizabeth sean Louis Larry AnnSophieBarwich christian deborah billy Abigail joesara AndreaBeste keith Jeremy CapitolRiots markkennedy zachary ruth Grace teresa Doris benjamin Willie george PeterHitchens methane barbara scottish Charlotte philip DaveCunliffe ethanzuckerman randy Margaret Heather Bradley Jacob shirley pamela Matthew Nicole joan judy Kelly savannah Brian melissa Sandra stallman markstone joseph oberverwaltungsgericht andrea shamelessselfplug Joe Sara robert alicevision aaronswartz better Bobby emma willie william angela rich SachaChua samuel Postmarketos tyler Thomas John kroger patricia ashley bobby roses kelly fuckamerica ThomasCahill hannah Carolyn Ann CrimsonRosella Jeangu gary wayne DavidRose Marilyn Deborah christenunie rms Sharon gare Mary frankfurt Samuel BreonnaTaylor Mark walter rebecca RaymondHill helendixon Madison Juan lisa cheryl janice ChristopherTrout jeffreyepstein Christian gerald Timothy roger edward bradley Gerald PiersMorgan patrickrachel framalang Kimberly steve Gabriel Marie EmmaFerris PeterHoffmann PaulBaran louis kathleen Arthur Gloria terry royals freejeremy bernardhickey Richard jonathan Harold shame Roy samantha DavidSeymour Carl chalice Eric AndreiKazimirov RebeccaHarvey relationships visuallyimpaired nicole Andrea Judith Terry Stephanie Johnny Angela Noah Kathryn RichardBoeth Ronald AskVanta Michelle Theresa gabrielmarie Samantha Judy michael charles GeorgeGerbner Tyler philipmorris amaryllis DouglasPFry kayla catherinealexander Martha debra JohnMichaelGreer stevewozniak joyce
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
activitypub
- followerpower FederatedSocialMedia mastodevs kazarma activitypub activertypub FediTips tootfic askthefedi fedivision pleroma losttoot Rss2Fedi PeerTube CreativeToots devices gofed getfedihired collaborate pixelfedlabs hometown homelab RedactionWeb fediblock fediverso lazyfedi happyfedi2u federation Invite2Fedi instances fedilab bandsofmastodon Wallabag blocks pixiv mastotips TheFediverseChallenge sammelabschiebung toot fedilabfeature mastodev fediversetv pixel Ktistec mastodontips catsofthefediverse misskeydev mastotip pixel3a wallaby MastoDev friendica mastodontip talesfromthefediverse mastofficina fleamarket ap_c2s hiveway bands mastodonart mast Moneylab Mosstodon Adblocker fedionly DeveloperExperience askthefediverse misskey collaboraoffice activitypub_conf plsboost BlackFedi joinmastodon AskPixelfed siskin socialhub followers fediart blocking fedifreebies Metatext FediBlock SocialMediaReimagined fediverse13 mondkapjesplicht Pixelfed contentwarnings pixelfed labournettv fediverseplaysjackbox mapeocolaborativo fedihive greeninstances fedidb block FediMemories mastectomy Feditip devs fablab fediverseparty collabathon Dev Fediseminar onlyfedi admin socialcg teamtoot masterton fedbox FediMeta sponsorblock SocialNetworkingReimagined tusky retoot contentwarning peertubers imagedescription joinpeertube anastasia feditips tootcat dnsssecmastery2e fedizens alimonda Mastodon following epicyon afediversechat andstatus peertubeadmin leylableibt fediversefleamarket mastomagic YearOfTheFediverse dearMastomind thatsthetoot mastodob fediadmin pleaseboost mastodonhost mond pixeldev pixelfont timeline socialmedia tips wedistribute fedivisionCollab fosstodon instanceblock softwaredevelopment freetoot mastodonmonday fedihelp fediWhen fedicat asta collaborative isolategab greenmastodon FediverseFixesThis fedireads pixeldroid networkTimeline PeertubeMastodonHost boost AskFediverse Bookwyrm federated socialhome greenfediverse WriteFreely fédiverse microblocks collabora fedivers MastodonMondays fediverse imagedescriptions mastobikes gbadev lemmy Fedilab bunsenlabs mastoadmin smithereen hackerstown uadblock c2s FediverseFutures latenighttoots mastodon pcmasterrace developingcountries boostswelcome PixelfedDev fedi fediversefriday mondkapje fediplay activity widevine socialcoop peertube fieldlabs mastomind lab fediversepower BlackMastadon fedeproxy boosten tootorial boostwelcome lazyfediverse mastoaiuto mobilizon Fediverse13 lazy gemifedi activityPubRocks
+ followerpower FederatedSocialMedia mastodevs kazarma activitypub activertypub FediTips tootfic askthefedi fedivision pleroma losttoot Rss2Fedi PeerTube CreativeToots devices gofed getfedihired collaborate pixelfedlabs hometown homelab RedactionWeb fediblock fediverso lazyfedi happyfedi2u federation Invite2Fedi instances fedilab bandsofmastodon Wallabag blocks pixiv mastotips TheFediverseChallenge sammelabschiebung toot fedilabfeature mastodev fediversetv pixel Ktistec mastodontips catsofthefediverse misskeydev mastotip pixel3a wallaby MastoDev friendica mastodontip talesfromthefediverse mastofficina fleamarket ap_c2s hiveway bands mastodonart mast Moneylab Mosstodon Adblocker fedionly DeveloperExperience askthefediverse misskey collaboraoffice activitypub_conf plsboost BlackFedi joinmastodon AskPixelfed siskin socialhub followers fediart blocking fedifreebies Metatext FediBlock SocialMediaReimagined fediverse13 mondkapjesplicht Pixelfed contentwarnings pixelfed labournettv fediverseplaysjackbox mapeocolaborativo fedihive greeninstances fedidb block FediMemories mastectomy Feditip devs fablab fediverseparty collabathon Dev Fediseminar onlyfedi admin socialcg teamtoot masterton fedbox FediMeta sponsorblock SocialNetworkingReimagined tusky retoot contentwarning peertubers imagedescription joinpeertube anastasia feditips tootcat dnsssecmastery2e fedizens alimonda Mastodon following epicyon afediversechat andstatus peertubeadmin leylableibt fediversefleamarket mastomagic YearOfTheFediverse dearMastomind thatsthetoot mastodob fediadmin pleaseboost mastodonhost mond pixeldev pixelfont timeline socialmedia tips wedistribute fedivisionCollab fosstodon instanceblock softwaredevelopment freetoot mastodonmonday fedihelp fediWhen fedicat asta collaborative isolategab greenmastodon FediverseFixesThis fedireads pixeldroid networkTimeline PeertubeMastodonHost boost AskFediverse Bookwyrm federated socialhome greenfediverse WriteFreely fédiverse microblocks collabora fedivers MastodonMondays fediverse imagedescriptions mastobikes gbadev lemmy Fedilab bunsenlabs mastoadmin smithereen hackerstown uadblock c2s FediverseFutures latenighttoots mastodon pcmasterrace developingcountries boostswelcome PixelfedDev fedi fediversefriday mondkapje fediplay activity widevine socialcoop peertube fieldlabs mastomind lab fediversepower BlackMastadon fedeproxy boosten tootorial boostwelcome lazyfediverse mastoaiuto mobilizon Fediverse13 lazy gemifedi activityPubRocks fediversehistory fedibird proxy activitystreams fediversetown pixelPrinciples zap spritely
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
linux
- pubnix fishshell linuxboot compiz osdev musescore commandline opensuse share linuxisnotanos elementaryos cli buster viernesdeescritorio voidlinux shell nu cliff olinuxino deb composite beschbleibt kde FragAttacks Debian11 reprobuilds pureos nospoilers kdepim thisweekinlinux slackware search bsd tap openwrt falling runbsd distros stapler viernes tmux nixos alpine nix DebianBullseye jobsearch rm xfce ubuntubuzz gnutools vaguejoke ack shareyourdesktop shellagm personal wireguard posix lightweight whonix hardenedbsd Guix linuxaudio mate haikuos usb initramfs nushell LinuxTablets nixpkgs wordsearch landback osi alpines computertruhe nonmateria torvalds gtk linuxmint DebianAcademy debian chroot trisquel studio gnome distrowatch oldcomputerchallenge linuxposting fedoraonpinephone trackers console showyourdesktop FuckDeMonarchie researchassistants anarchie windowmanager desktop GuixSystem arch chaoscomputerclub personalities platform ubuntu personalwiki jodee snowfall gnulinux patriarchat aur tuxjam justlinuxthings xubuntu thesuicidesquad kdeframeworks5 stackoverflow unix fedora openbsd centos nos fittrackee tuxedocomputers tracker openmandriva backwaren gentoo buildroot aurora researcher archive icarosdesktop BlackLives liveusb dee SearchFu personalarchive usergroup StockOS systemd linuxgaming Debian distro 1492LandBackLane Racklet theartofcomputerprogramming icecat tape puppylinux destinationlinux LinuxSpotted lovelinux thestudio suicide aros show Squarch monstrosities computer gtk3 blackout deepBlah escritoriognulinux acepride materials qubesos i3wm clipstudiopaint dadjokes kubuntu epr artixlinux JuiceFS reproducible kdecommunity haiku alpinelinux linuxisnotaplatform clip fall linux EMMS planetdebian minicomputer altap raspbian netbsd DanctNIX termux btrfs reproduciblebuilds showTheRainbow gravitationalwaves joke artix gtk4 esc linuxexpress archlinuxarm bash dd exposingtheinvisible archlinux hare ubuntucore linuxconfau newinbullseye researchers AuratAzadiMarch gnomebuilder void GNUlinux rhel debianinstaller debianindia linuxisajoke tux devuan debían suse zsh linuxconsole scoobySnacks bullseye
+ pubnix fishshell linuxboot compiz osdev musescore commandline opensuse share linuxisnotanos elementaryos cli buster viernesdeescritorio voidlinux shell nu cliff olinuxino deb composite beschbleibt kde FragAttacks Debian11 reprobuilds pureos nospoilers kdepim thisweekinlinux slackware search bsd tap openwrt falling runbsd distros stapler viernes tmux nixos alpine nix DebianBullseye jobsearch rm xfce ubuntubuzz gnutools vaguejoke ack shareyourdesktop shellagm personal wireguard posix lightweight whonix hardenedbsd Guix linuxaudio mate haikuos usb initramfs nushell LinuxTablets nixpkgs wordsearch landback osi alpines computertruhe nonmateria torvalds gtk linuxmint DebianAcademy debian chroot trisquel studio gnome distrowatch oldcomputerchallenge linuxposting fedoraonpinephone trackers console showyourdesktop FuckDeMonarchie researchassistants anarchie windowmanager desktop GuixSystem arch chaoscomputerclub personalities platform ubuntu personalwiki jodee snowfall gnulinux patriarchat aur tuxjam justlinuxthings xubuntu thesuicidesquad kdeframeworks5 stackoverflow unix fedora openbsd centos nos fittrackee tuxedocomputers tracker openmandriva backwaren gentoo buildroot aurora researcher archive icarosdesktop BlackLives liveusb dee SearchFu personalarchive usergroup StockOS systemd linuxgaming Debian distro 1492LandBackLane Racklet theartofcomputerprogramming icecat tape puppylinux destinationlinux LinuxSpotted lovelinux thestudio suicide aros show Squarch monstrosities computer gtk3 blackout deepBlah escritoriognulinux acepride materials qubesos i3wm clipstudiopaint dadjokes kubuntu epr artixlinux JuiceFS reproducible kdecommunity haiku alpinelinux linuxisnotaplatform clip fall linux EMMS planetdebian minicomputer altap raspbian netbsd DanctNIX termux btrfs reproduciblebuilds showTheRainbow gravitationalwaves joke artix gtk4 esc linuxexpress archlinuxarm bash dd exposingtheinvisible archlinux hare ubuntucore linuxconfau newinbullseye researchers AuratAzadiMarch gnomebuilder void GNUlinux rhel debianinstaller debianindia linuxisajoke tux devuan debían suse zsh linuxconsole scoobySnacks bullseye gnomemaps fedisearch SharedEarningsAgreement archives elementary
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
programming
- Easer psychotherapie DigitalInfrastructure cpp digitalpreservation programming css maui rubyonrails objects Python system digitaldivide digitalisierung FrancisBacon2020 dracut gitea orgmode mixers webdev proofing developerexperience seguridaddigital gui digital release ada schutzstreifen pypi crust codeforge workaround proofofwork zorg node websocket proofofstake ecosystem rustlang systemwandel DigitalTech python2 ocaml NapierBarracks system76 program ngiforum21 DigitalSouveräneSchule request_reaction sqlite guile capitolhillautonomouszone transcript TransZorgNu nim warsawhackerspace uptronics algorithmicharm hypocritcal profiles digitalsketch DeutschlandDigitalSicherBSI typescript forums vscode aapihm gitsyncmurder musicforhackers publiccode ocr computerscience hackers guidelines vieprivée Digitalzwangmelder laravel vala adventofcode cgit solidarność DigitalPayments beginnersguide CommonJS webdev101 scripting coding warn mauikit digitalesouveränität DevelopmentBlog anime ohShitGit digitalzwang meld git org QR_code proof sourcehut ui nocode solid nodejs systemchange trevornoah zinccoop tailwindcss terminalporn Wassersouveränität guix libertàdigitali js_of_ocaml raku fedidev c script freenode-services sourcecode audiodescription publiekecode framaforms WendyLPatrick DigitalAutonomy grep django gmic zim sackthelot amada gitportal Acode gitlab crusty decoder bulldada readability parrot relevance_P1Y mnt digitalartwork Verkada react dogfooding webdevelopment kingparrot Leiharbeit programmer trunk java haskell OpenSourceHardware CodedBias codelyoko workstation guixhome Tarifvertrag capitolhill Auto desperatehousehackers esm penguin unicode development gittutors ursulakleguin programminglanguage gerrit db frgmntscnr Fagradalsfjall dev github freecodecamp openrc tuskydev threema recoverourdigitalspace html5 algorithms PythonJob lisp digitaldefenders codeberg souveränität forge ursulaleguin pleaseshare rustprogramming EspacioDigital HirsuteHippo resnetting frontenddevelopment animatedgif fourtwenty rails rakudev adaptation programme developers bug fortran libraries drivers animation printingsystems freecode forgefed javascript fragment cpm code elisp JardínOpenSource commands patterns eq ECMAScriptModules html codeofconduct vintagecomputers ConstructiveAmbiguity rakulang portal terminal c99 SemillasOpenSource rust programminghumor lowcode request AreWeTheBorg spiritbomb r FOSSlight bugbounty dramasystem go forges digitalaudioworkstation esbuild federadas commonlisp golang clojurescript nodemcu vintage ruby releaseday rustc contractpatch rubylang deceptionpatterns mugorg debugging makejavascriptoptional nodefwd obsolescence_programmée computers developer darkpatterns racket sourceforge forum ksh digitalprivacy minimumwage bugreport mercurial aapi adafruit openappecosystem python fontforge webdeveloper indiedev ocrodjvu sh digitalGardens api assembler kabelfernsehen OpenSource Scheibenwischer
+ Easer psychotherapie DigitalInfrastructure cpp digitalpreservation programming css maui rubyonrails objects Python system digitaldivide digitalisierung FrancisBacon2020 dracut gitea orgmode mixers webdev proofing developerexperience seguridaddigital gui digital release ada schutzstreifen pypi crust codeforge workaround proofofwork zorg node websocket proofofstake ecosystem rustlang systemwandel DigitalTech python2 ocaml NapierBarracks system76 program ngiforum21 DigitalSouveräneSchule request_reaction sqlite guile capitolhillautonomouszone transcript TransZorgNu nim warsawhackerspace uptronics algorithmicharm hypocritcal profiles digitalsketch DeutschlandDigitalSicherBSI typescript forums vscode aapihm gitsyncmurder musicforhackers publiccode ocr computerscience hackers guidelines vieprivée Digitalzwangmelder laravel vala adventofcode cgit solidarność DigitalPayments beginnersguide CommonJS webdev101 scripting coding warn mauikit digitalesouveränität DevelopmentBlog anime ohShitGit digitalzwang meld git org QR_code proof sourcehut ui nocode solid nodejs systemchange trevornoah zinccoop tailwindcss terminalporn Wassersouveränität guix libertàdigitali js_of_ocaml raku fedidev c script freenode-services sourcecode audiodescription publiekecode framaforms WendyLPatrick DigitalAutonomy grep django gmic zim sackthelot amada gitportal Acode gitlab crusty decoder bulldada readability parrot relevance_P1Y mnt digitalartwork Verkada react dogfooding webdevelopment kingparrot Leiharbeit programmer trunk java haskell OpenSourceHardware CodedBias codelyoko workstation guixhome Tarifvertrag capitolhill Auto desperatehousehackers esm penguin unicode development gittutors ursulakleguin programminglanguage gerrit db frgmntscnr Fagradalsfjall dev github freecodecamp openrc tuskydev threema recoverourdigitalspace html5 algorithms PythonJob lisp digitaldefenders codeberg souveränität forge ursulaleguin pleaseshare rustprogramming EspacioDigital HirsuteHippo resnetting frontenddevelopment animatedgif fourtwenty rails rakudev adaptation programme developers bug fortran libraries drivers animation printingsystems freecode forgefed javascript fragment cpm code elisp JardínOpenSource commands patterns eq ECMAScriptModules html codeofconduct vintagecomputers ConstructiveAmbiguity rakulang portal terminal c99 SemillasOpenSource rust programminghumor lowcode request AreWeTheBorg spiritbomb r FOSSlight bugbounty dramasystem go forges digitalaudioworkstation esbuild federadas commonlisp golang clojurescript nodemcu vintage ruby releaseday rustc contractpatch rubylang deceptionpatterns mugorg debugging makejavascriptoptional nodefwd obsolescence_programmée computers developer darkpatterns racket sourceforge forum ksh digitalprivacy minimumwage bugreport mercurial aapi adafruit openappecosystem python fontforge webdeveloper indiedev ocrodjvu sh digitalGardens api assembler kabelfernsehen OpenSource Scheibenwischer digitalindia postgresql emscripten digitalsobriety featurerequest Payment
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
legal
- NoALaReformaTributaria justafewlines eek scanlines kurmancî rma informatik formatie2021 hfgkarlsruhe doj amro karlsruhe dmc remotelearning tamron formatie SpreekJeUitBekenKleur newnormal line OfflineNavigation disinformation kurmanji OnlineHarms GameSphere squeekboard mermaid stopline3 DNSmugOfTheWeek permagold OnlineHarmsBill laipower gdpr intros Anticritique energyflow peekier MovieGeek OnlineMeetings scan informationsfreiheit mojeek digitalservicesact line3 disinfo mainline freiheit darmanin airline OfflineHarms permafrost geekproblem dmca
+ NoALaReformaTributaria justafewlines eek scanlines kurmancî rma informatik formatie2021 hfgkarlsruhe doj amro karlsruhe dmc remotelearning tamron formatie SpreekJeUitBekenKleur newnormal line OfflineNavigation disinformation kurmanji OnlineHarms GameSphere squeekboard mermaid stopline3 DNSmugOfTheWeek permagold OnlineHarmsBill laipower gdpr intros Anticritique energyflow peekier MovieGeek OnlineMeetings scan informationsfreiheit mojeek digitalservicesact line3 disinfo mainline freiheit darmanin airline OfflineHarms permafrost geekproblem dmca Tuscany
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
nature
- hiking camping RedNeckedWallaby reforestation hillwalking wat hambach nsu20 marsupial lightning StormBella zensurheberrecht insect morning lavawervelwind seashell delightful plankton otterbox trees sky_of_my_window lichen MicroOrganisms badger nsu2 ProForestation nonsupremacy light gecko birds nature embargo_watch volcano teamcapy butterflies Nature snowden actiblizzwalkout frogs rainforest snow sunrise fossils hambacherforest forestfinance lighthouse hitchhiking leopardgecko moutains coldwater rocks inaturalist revuestarlight clamfacts sunset naturereserve forest LandRestoration australianwildlife forests capybara rgblighting enlightened waterfall sundaymorning forestation enlightenment natur lightening finance walking watches deforestation desert lava natural WoodWideWeb birdsarentreal lichensubscribe morningwalk lighttheme nsu retraction_watch SpringRockShed insects wildlife GreatInsults snowdrift afforestation northernlights RainforestAlliance ProtégeonsLaNature amphibians Bear walk desertification otter
+ hiking camping RedNeckedWallaby reforestation hillwalking wat hambach nsu20 marsupial lightning StormBella zensurheberrecht insect morning lavawervelwind seashell delightful plankton otterbox trees sky_of_my_window lichen MicroOrganisms badger nsu2 ProForestation nonsupremacy light gecko birds nature embargo_watch volcano teamcapy butterflies Nature snowden actiblizzwalkout frogs rainforest snow sunrise fossils hambacherforest forestfinance lighthouse hitchhiking leopardgecko moutains coldwater rocks inaturalist revuestarlight clamfacts sunset naturereserve forest LandRestoration australianwildlife forests capybara rgblighting enlightened waterfall sundaymorning forestation enlightenment natur lightening finance walking watches deforestation desert lava natural WoodWideWeb birdsarentreal lichensubscribe morningwalk lighttheme nsu retraction_watch SpringRockShed insects wildlife GreatInsults snowdrift afforestation northernlights RainforestAlliance ProtégeonsLaNature amphibians Bear walk desertification otter toadstool EyesOnNature alligator
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
writing
- blog framablog interactive amwriting authors writingprompt poem lime cutupmethod story pdf linkblog blogPages swap shortstory prompts magazine smallstories prompt blogging smallpoems sciencefiction responsetootherblogs writing proverbs quotes blogs teleprompters noblogo otf logo playwright hedgedoc FediWriters interactivestorytelling westernjournal AuthorsofSocialCoop Videopoetry quote olimex QuickSummary letterwriting icanhazpdf microblog bulletjournal storytelling goodread goodreads journalist creativewriting horror wordplay writers limerick journals artjournaling zineswap zines shortstories journalists journal writingcommunity poetry 20thcenturypoetry logos amwritingfiction
+ blog framablog interactive amwriting authors writingprompt poem lime cutupmethod story pdf linkblog blogPages swap shortstory prompts magazine smallstories prompt blogging smallpoems sciencefiction responsetootherblogs writing proverbs quotes blogs teleprompters noblogo otf logo playwright hedgedoc FediWriters interactivestorytelling westernjournal AuthorsofSocialCoop Videopoetry quote olimex QuickSummary letterwriting icanhazpdf microblog bulletjournal storytelling goodread goodreads journalist creativewriting horror wordplay writers limerick journals artjournaling zineswap zines shortstories journalists journal writingcommunity poetry 20thcenturypoetry logos amwritingfiction stories wether whether microblogging shorts
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
countries
- roma thai romania korean burma lithuania solomon chile europeanparliament Instanz boycottisrael fiji tajikistan benin paraguay eeuu icelandtrip senegal ukraine italy brunei nicaragua guyana Pflanzenbestimmung grönland euphoria zambia PalestineStrike iceland europeancouncil morocco netherlands swaziland EuropeanUnion bosnian suriname welcome2america winningatlife elsalvador russia freeburma samoa StaatstrojanerParteiDeutschlands romanian asl european czech belarus hayabusa2 bw kyrgyzstan english uk translation sanmarino catalonia panama africa west indians unitedkingdom japan Netherlands buyused venezuela gambia freeNukem kuwait barbados papua greece switzerland brasilien uae mau england FuckIsrael nigeria usa angola honduras djibouti laos sierraleone nonprofit investigation artemis britain cambodia translators ych vietnam esperanto neofeud zealios seychelles marshall kazakhstan estonia investigate tonga stlucia burundi bangladesh egypt nachhaltigkeit japanese mali congo us IcelandicVerminControl jordan MusiciansForPalestine americangods digitaleurope speedrun grenada israel lowestoft psychic algeria ghana bosnia translations russian LateAmericanSentences eritrea bhutan armenian hama hungary Störungsverbot saudi slovenia tig czechosvlovakia bahamas america libadwaita australia kiribati togo DeathToAmerica koreanorth poland Überbevölkerung malawi AlwaysBetterTogether capeverde armenia american hautrauswasgeht bahrain mozambique WichtigerHinweis abcbelarus japaneseglare americanpinemarten beleuchtung southsudan adminlife citylife europehoax Martesamericana syria german micronesia maldives iran indigenous sweden bijîberxwedanarojava ethiopia sid cuba liberia canada burkina indian Südwestgrönland somalia Chile whatshappeningintigray scotland Enlgand russiaToday vaticancity easttimor austria EuropeanUnionNews turkey yemen Bolivia denmark USBased domesticabuse austrianworldsummit madagascar finland Wales Iran philippines ivorycoast haiti ecuador Portugal azerbaijan gasuk spain albania massachusetts afghanistan europe mauritania dominica ökonomisierung thailand belize westpapuauprising nerdlife macedonia montenegro BelarusProtests ChileDesperto thenetherlands qatar mongolia costarica boatingeurope birdsofkenya Australia boat latvia uzbekistan fatigue kabelaufklärung ireland iraq malaysia mexico investigations mauritius dezentralisierung oman chad nz de georgia zimbabwe france serbia lesotho romani halflife oddmuse tunisia argentina czechia cameroon namibia sudan indonesia lifeboat colombia worldwildlifeday kryptowährung tuvalu britainology merica beckychambers turkmenistan tanzania germany trojan neuhier norway comoros auteursrecht guatemala Thailand kosovo eastgermany andorra wales indiastrikes vanlife Palestine servus pakistan belgium china 3615malife antigua life europeanvalues koreasouth newzealand visiticeland einzelfall rwanda luxembourg libya indywales italyisntreal nauru moldova bad spanish eastindiacompany northernireland stigmergic palau taiwan kenya trinidad eu botswana Lebensmittelzusatzstoff CuriosidadesVariadas jamaica vanuatu cyprus aminus3 israele malta Icelandic psychedelia niger s3 westpapua busse unitedstates myanmar saintvincent guinea nepal peru uganda uruguay india pacificnorthwest lebanon neurodiversity southafrica writer arte croatia europeanunion writerslife bolivia chinese dominican europeancommission srilanka bulgaria etherpad slovakia speedrunning gabon psychedelicart ether palestine stkitts liechtenstein saveabkindonesia neofeudalism surinam brazil shutdowncanada
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
music
- LibreMusicChallenge musicprodution KobiRock iea travessiapelavida LaurieAnderson ics punk punkname ourbeats gas vollgasindiekrise indieweb musician cypherpunk rutasenemigas synthesizer daftpunk bootstrappable kenloach indiemusic collapseos meatpunks LibreGraphicsMeetup cipherpunk 20thcenturyjazz acousticguitar synthpop psychedelicrock steamlinux playingnow streetpunk loader hydrapaper bikepunks bandcamp mymusic pop countryrock musicians jamendo ipod skinheadmusic jam rap shoegaze mp3 nettlepunk steam indie steganography PegasusSpyware steampunk ldjam48 indieauthor composing folkrock perlligraphy nazipunksfuckoff Music strap EnvoieStopHashtagAu81212 anarchopunk eurovisionsongcontest biography musicmaking psychedelic thecure posthardcore vaporwave IndustrialMusicForIndustrialPeople Mixtip dubstep synthwave bootstrap princeday oi graphisme rave freemusic nowplaying hiphop hardcore frappuccino Musicsoft experimentalmusic nazi folk cp TravesíaPorLaVida spotify fedimusic ml bootstrapping webscraping elisamusicplayer funkloch musicbrainz eurovision lasvegas catsWithMusicalTalent PegasusSnoopingScandal eos90D soundcloud psicodelia frankiegoestohollywood gastropod whenyoulistentocoildoyouthinkofmusic trial soundsynthesis PigTrap bassguitar collapse 20thcenturymusic powerpop vinyl rock ccmusic typographie dj newwave dorkwave producing experimental celticmetal prince musicproduction chiptune scraping loa Schleprock thrash bluestacks lastfm uploadfilters tekno ripprince Eurvision maunaloa technocracy asus 1 funkwhale 20thcenturyrock eos wp playlist retrosynth NowPlaying contest libremusicproduction psychrock MusicAdvent poppy coinkydink appropriatetechnology toledo samensterk indiepop rockalternativo MusicTouring indierock pmbootstrap midi arianagrande indiecember synth guitar blues musiciens listeningtonow abandonedplaces music folkpunk np bass techno gmtkjam musicmonday jazz production graphics dieanstalt perl darkwave mastomusic band TheGrunge metal chipmusic graphviz tigase polychromatic funk mindjammer popos magnatune fediversemusic pegasus PegasusProject grunge postpunk punkrock indieauth cyberpunkmusic raveculture cleantechnologies ldjam ftp BandcampFriday elisa mixtape garagerock MusicsoftDownloader camanachd
+ LibreMusicChallenge musicprodution KobiRock iea travessiapelavida LaurieAnderson ics punk punkname ourbeats gas vollgasindiekrise indieweb musician cypherpunk rutasenemigas synthesizer daftpunk bootstrappable kenloach indiemusic collapseos meatpunks LibreGraphicsMeetup cipherpunk 20thcenturyjazz acousticguitar synthpop psychedelicrock steamlinux playingnow streetpunk loader hydrapaper bikepunks bandcamp mymusic pop countryrock musicians jamendo ipod skinheadmusic jam rap shoegaze mp3 nettlepunk steam indie steganography PegasusSpyware steampunk ldjam48 indieauthor composing folkrock perlligraphy nazipunksfuckoff Music strap EnvoieStopHashtagAu81212 anarchopunk eurovisionsongcontest biography musicmaking psychedelic thecure posthardcore vaporwave IndustrialMusicForIndustrialPeople Mixtip dubstep synthwave bootstrap princeday oi graphisme rave freemusic nowplaying hiphop hardcore frappuccino Musicsoft experimentalmusic nazi folk cp TravesíaPorLaVida spotify fedimusic ml bootstrapping webscraping elisamusicplayer funkloch musicbrainz eurovision lasvegas catsWithMusicalTalent PegasusSnoopingScandal eos90D soundcloud psicodelia frankiegoestohollywood gastropod whenyoulistentocoildoyouthinkofmusic trial soundsynthesis PigTrap bassguitar collapse 20thcenturymusic powerpop vinyl rock ccmusic typographie dj newwave dorkwave producing experimental celticmetal prince musicproduction chiptune scraping loa Schleprock thrash bluestacks lastfm uploadfilters tekno ripprince Eurvision maunaloa technocracy asus 1 funkwhale 20thcenturyrock eos wp playlist retrosynth NowPlaying contest libremusicproduction psychrock MusicAdvent poppy coinkydink appropriatetechnology toledo samensterk indiepop rockalternativo MusicTouring indierock pmbootstrap midi arianagrande indiecember synth guitar blues musiciens listeningtonow abandonedplaces music folkpunk np bass techno gmtkjam musicmonday jazz production graphics dieanstalt perl darkwave mastomusic band TheGrunge metal chipmusic graphviz tigase polychromatic funk mindjammer popos magnatune fediversemusic pegasus PegasusProject grunge postpunk punkrock indieauth cyberpunkmusic raveculture cleantechnologies ldjam ftp BandcampFriday elisa mixtape garagerock MusicsoftDownloader camanachd bassed technologie
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
gardening
- seedstarting BlagueDeCodeur sporespondence blockade inde mastogarden kinder communitygardening som deno composting soil sehenswert cabbage bundeswehr opensourceseeds onions lettuce blossoms gardenersofmastodon datenschleuder florespondence garten rinder succulent mulberry weekendGardeningThoughts cherryblossoms garden thyme flower horticulture DailyFlowers Schlachthofblockade cherryblossom agriculture acu vegetable plant bricolage financialindependence plasticflowersneverdie kinderbijslag permaculture awesome teracube hens papuamerdeka Auflagen wildflowers lag independenceday CompanionPlanting vlag gardens independence flowers seed kale seedvault plants thegardenpath devilslettuce vegetables thegarden fahrräder gardenersworld recyclage golden beekeeping toeslagenaffaire seeds Opensourcegarden toeslagenschandaal vegetablegarden
+ seedstarting BlagueDeCodeur sporespondence blockade inde mastogarden kinder communitygardening som deno composting soil sehenswert cabbage bundeswehr opensourceseeds onions lettuce blossoms gardenersofmastodon datenschleuder florespondence garten rinder succulent mulberry weekendGardeningThoughts cherryblossoms garden thyme flower horticulture DailyFlowers Schlachthofblockade cherryblossom agriculture acu vegetable plant bricolage financialindependence plasticflowersneverdie kinderbijslag permaculture awesome teracube hens papuamerdeka Auflagen wildflowers lag independenceday CompanionPlanting vlag gardens independence flowers seed kale seedvault plants thegardenpath devilslettuce vegetables thegarden fahrräder gardenersworld recyclage golden beekeeping toeslagenaffaire seeds Opensourcegarden toeslagenschandaal vegetablegarden seedsaving
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
privacy
- privacyplease appleprivacyletter state whatip auditableprivacy appleprivacy PrivacyBook SearchHistory privacyaware dataprivacyday profiling what3words surveillancestate Privacy datenschutz privacypolicy WhatsApp privacyrights privacytoolsio privacyshield makeprivacystick privacyweek surveillancetech onlineprivacy developertools WhatMakesMeReallyAngry privacyredirect Liberanet LiberanetChat drugpolicy privacymatters policy privacyMatters whatsappprivacypolicy dataprivacy privacywashing fight4privacy privacy privacyinternational whowhatwere hat NoToWhatsApp DataPrivacyDay2020 investinprivacy PrivacyFlaw statePropaganda nl privacytools WhatsappPrivacy tool
+ privacyplease appleprivacyletter state whatip auditableprivacy appleprivacy PrivacyBook SearchHistory privacyaware dataprivacyday profiling what3words surveillancestate Privacy datenschutz privacypolicy WhatsApp privacyrights privacytoolsio privacyshield makeprivacystick privacyweek surveillancetech onlineprivacy developertools WhatMakesMeReallyAngry privacyredirect Liberanet LiberanetChat drugpolicy privacymatters policy privacyMatters whatsappprivacypolicy dataprivacy privacywashing fight4privacy privacy privacyinternational whowhatwere hat NoToWhatsApp DataPrivacyDay2020 investinprivacy PrivacyFlaw statePropaganda nl privacytools WhatsappPrivacy tool policymaking
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
media
- tradicional InfiniTime livestreaming ip digitalmedia mustwatch sustainable videobearbeitung transparency polarbears mediathek mianstreaming stream videoconferencias trad AtlanticGulfstream maistreaming ime sustainabilty mixxx shortfilm selfsustainable amstrad kawaiipunkstreams mainstream films streaming weAreAllCrazy video streamdeck puns maiabeyrouti videoconference shortfilms mix MediaEU mixed diymedia Fairtrade drmfree film streams massmedia stummfilm submedia theatlantic traditionalmedia videos Internetradio mediawatch mainstreamining newsmedia audiovideo videosynthesis filmnoir wikimedia railroad mixedmedia railroads heat documentary streamers artstream vi nationalgeographic folktraditions gstreamer tootstream taina ai mediawiki slowtv bear realmedia media independentmedia SiberianTimes theintercept
+ tradicional InfiniTime livestreaming ip digitalmedia mustwatch sustainable videobearbeitung transparency polarbears mediathek mianstreaming stream videoconferencias trad AtlanticGulfstream maistreaming ime sustainabilty mixxx shortfilm selfsustainable amstrad kawaiipunkstreams mainstream films streaming weAreAllCrazy video streamdeck puns maiabeyrouti videoconference shortfilms mix MediaEU mixed diymedia Fairtrade drmfree film streams massmedia stummfilm submedia theatlantic traditionalmedia videos Internetradio mediawatch mainstreamining newsmedia audiovideo videosynthesis filmnoir wikimedia railroad mixedmedia railroads heat documentary streamers artstream vi nationalgeographic folktraditions gstreamer tootstream taina mediawiki slowtv bear realmedia media independentmedia SiberianTimes theintercept novaramedia filmpreservation
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
ai
+ ai macos machinelearning openai EthicsInAI smartcity
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
health
- eventsourcing merchandise FreedomIsTheOnlyTreatment gnuhealth water 4 medical CoronaApp bundesregierung runningdownthewalls watersnood EfeLevent autism burnout Underunderstood cannabis hand event healthinsurance medicine anxiety freshwater mh inflammation run eternalpuberty NHSDataGrab treatment EmotionalFirstAid safeabortion4all maryjane organisierung autistic BlockBrunsbüttel running neurodivergent health motion crunchbang actuallyautistic meds PatientSafety marijuana suicideprevention mentalhealth postmortem H5N8 healthy DarrenChandler autismmeme einzelhandel drugs atm neurodiverse asperger cigarettes insurance hearingimpairment selfcare autismus
+ eventsourcing merchandise FreedomIsTheOnlyTreatment gnuhealth water 4 medical CoronaApp bundesregierung runningdownthewalls watersnood EfeLevent autism burnout Underunderstood cannabis hand event healthinsurance medicine anxiety freshwater mh inflammation run eternalpuberty NHSDataGrab treatment EmotionalFirstAid safeabortion4all maryjane organisierung autistic BlockBrunsbüttel running neurodivergent health motion crunchbang actuallyautistic meds PatientSafety marijuana suicideprevention mentalhealth postmortem H5N8 healthy DarrenChandler autismmeme einzelhandel drugs atm neurodiverse asperger cigarettes insurance hearingimpairment selfcare autismus medicineedison
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
hardware
- plugandplay bluetooth printnightmare singleboardcomputer purism dating schematics opennic tektronix zomertijd librehardware BoBurnham restauration rmw riscv solarpower carbonFootprintSham mietendeckel PersonalComputer cyberdeck PineCUBE firmware tex keyboards debuerreotype electron ChromebookDuet AbolishFrontex webcam bond hibernation PneumaticLoudspeakers schreibmaschine imac Nottingham schwarmwissen elitesoldat handheld screenless megapixels BibliothekDerFreien KeepTheDiskSpinning homebrewcomputing FarmersTractorRally pinebook farming modem lowtech biblatex allwinner daten home pimeroni 68 lebensmittelsicherheit industrial hambibleibt analogcomputing homer TrueDelta keyboard screenprinting robotics Pinecil mutantC raspberrypi3 pocketchip oshw misterfpga noisebridge disapora T440p ArmWorkstation datensicherheit latexrun hardwarehacking mer picodisplay laptops electronics scuttlebutt ham teamdatenschutz charm SectorDisk wolnabiblioteka preprint permacomputing uart panasonic pcb almere armbian performance kopimi printmaker deck making hambi powerpc solar ssd acoustics ibmcompatible webcams modular larp tweedekamer cybredeck latex 3dprinted MacBook emmc ipadproart computing laptop solarpunk isa recycling modularsynth apparmor repairability macbook theatrelighting pc lenovo updates fairelectronics industrialmusic librem carbonsequestration electronica sed TokyoCameraClub MacBookProService pocket box86 JingPad righttorepair mac trackball fuse date solarpunkactionweek ibm 3dprinting electro carbon MechcanicalKeyboards netbook hardware m68k pisa retrohardware pinetab sicherheit openhardware raspberrypi irobot datenautobahn webtoprint 3dprinter barcode lüneburg Quartz64 PlanetComputer jtag ebu merseyside itsicherheit CompressedAirAmplification pinetime screens pinebookpro lebensmittel 3d batteries PinebookPro 3dprint pim Handprint modemmanager securescuttlebutt keyboardio mechanicalkeyboard electronicmusic solarpunks carbondioxide robot arm lowerdecks sonic ipad FireAlarms PinePower paperComputer amd openpower poweredSpeaker devopa a64 eeepc bahn F9600 rpi4 thinkpad RaspberryPiPico iot dat BeagleV arm64 merveilles repairable sbc circuitbending raspberrypi4 print displayport akihabara analog electronic FrameworkLaptop
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
food
- battery vitamind cake veganism teamviewer FoodHardship vanilla pankow margarine zwartepiet panthera dessert foils salsa caviar utopie brot theexpanse BellaSpielt cookery pietons Ôtepoti panther food cakecutting skillet teamgodzilla openfoodnetwork spiel Vegan liquor SoilSovereignty milk bolognese recipe foodporn yeast drinking VendrediPeanutsNouka plate waffle pansexual biscuit glaze omelette veganismo morel filet pastry wine woke Caribbeans hamburger juice unauthorizedbread Amazfish Avocados management sourdough gedankenspiel cagefree words MauriceSchuhmann nuts gras toast broth batter foodie breadposting spiele zerowaste haggis ketchup carrots go-nuts damnfinecoffee divoc seasoning mayo nowords MastoEats soup arpanet SteamDeck pan voc imateapot Anglefish mayoverse potatoes mayonnaise vegan dish avocado spice keto bakery butterfly cooking teamhuman SailfishOS Trypanophobia AgentProvocatuer yogurt rok thecandycrystalrainbowcodex crumble PropaneSalute cider caffeine Kinipan butter mastokitchen triceratops cook rain pottery kurdish creepypasta wastemanagement kitchencounter mastocook cobbler steak pizza vocaloid crystal soda fedikitchen coffeebreak aroma oil Miroil kochbrothers flour foodsovereignty cream nutella pie cut cuisine potse meatismurder freerange tartar kropotkin tea marinade cakes mushroom thekitchen govegan entree lfi dominospizza bread salad beans mush fresh syrup fermentation teamsky mushrooms cookie cookiebanners olivetti wordstoliveby curd soysauce lowcarb pudding plantbased tema beer organicfood peterkropotkin fish grasslands panoptykon spanisch honeypot foodnotbombs foodwaste organic wholeGrain wheat pot TeamFerment timewaster Wypierdalaj sauerkraut stew weltspiegel chocolate paste soynuevo wok rainbow recipes kitchengarden expanse olive burger mrpotatohead candy lifesnacks Steam kitchen coffee foodshortage bagel batterylife OpTinfoil teams taste SpieleWinter2020 meat johannisbeeren noodle raclette caramel rice eggs grill davewiner DavePollard poutine demoteam lard croissant pasta vegane strawberry toomuchcaffeine morelmushroom foods coffeeaddict WaterDrinkers cheese oregano drink muffin bikekitchen krop LowRefresh kyotocandy foie onepiece sauce foodanddrink soy foodpics growyourfood vore mushtodon wholewheat pandemie cocoa sandwich bigoil mousse waste chili redfish
+ battery vitamind cake veganism teamviewer FoodHardship vanilla pankow margarine zwartepiet panthera dessert foils salsa caviar utopie brot theexpanse BellaSpielt cookery pietons Ôtepoti panther food cakecutting skillet teamgodzilla openfoodnetwork spiel Vegan liquor SoilSovereignty milk bolognese recipe foodporn yeast drinking VendrediPeanutsNouka plate waffle pansexual biscuit glaze omelette veganismo morel filet pastry wine woke Caribbeans hamburger juice unauthorizedbread Amazfish Avocados management sourdough gedankenspiel cagefree words MauriceSchuhmann nuts gras toast broth batter foodie breadposting spiele zerowaste haggis ketchup carrots go-nuts damnfinecoffee divoc seasoning mayo nowords MastoEats soup arpanet SteamDeck pan voc imateapot Anglefish mayoverse potatoes mayonnaise vegan dish avocado spice keto bakery butterfly cooking teamhuman SailfishOS Trypanophobia AgentProvocatuer yogurt rok thecandycrystalrainbowcodex crumble PropaneSalute cider caffeine Kinipan butter mastokitchen triceratops cook rain pottery kurdish creepypasta wastemanagement kitchencounter mastocook cobbler steak pizza vocaloid crystal soda fedikitchen coffeebreak aroma oil Miroil kochbrothers flour foodsovereignty cream nutella pie cut cuisine potse meatismurder freerange tartar kropotkin tea marinade cakes mushroom thekitchen govegan entree lfi dominospizza bread salad beans mush fresh syrup fermentation teamsky mushrooms cookie cookiebanners olivetti wordstoliveby curd soysauce lowcarb pudding plantbased tema beer organicfood peterkropotkin fish grasslands panoptykon spanisch honeypot foodnotbombs foodwaste organic wholeGrain wheat pot TeamFerment timewaster Wypierdalaj sauerkraut stew weltspiegel chocolate paste soynuevo wok rainbow recipes kitchengarden expanse olive burger mrpotatohead candy lifesnacks Steam kitchen coffee foodshortage bagel batterylife OpTinfoil teams taste SpieleWinter2020 meat johannisbeeren noodle raclette caramel rice eggs grill davewiner DavePollard poutine demoteam lard croissant pasta vegane strawberry toomuchcaffeine morelmushroom foods coffeeaddict WaterDrinkers cheese oregano drink muffin bikekitchen krop LowRefresh kyotocandy foie onepiece sauce foodanddrink soy foodpics growyourfood vore mushtodon wholewheat pandemie cocoa sandwich bigoil mousse waste chili redfish warmcookiesoftherevolution
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
security
- zuluCrypt signalboost encrypt letsencrypt messengers autos BrowserHistory FlexibilizaciónResponsable puntarellaparty autoritäreretatismus BlacksInCyber omemo autotomy saveanonymity alg onionrouting Installationsanleitung dataleak messenger foodinsecurity password keepassxc partyline cryptography party cybersecuritynews pipewire Installation cryptolalaland solarwinds bitwarden communityalgorithmictrust infosec gchq GemeinsamGegenDieTierindustrie mitm wireless castor repairing IHaveSomethingToHide fotografie passwords gif IronySec cryptowars anonym encryptioncan supplychainattacks UseAMaskUseTor anonymous cyberattack editors security tor comb e2e supplychain bruceschneier gigafactory vpn BlacksInCybersecurity ransomware wireapp toreador itsec dnssecmastery2e openssh factorio Reactorweg openssl backdoored spyware dorfleaks torx encryptionsts e2ee sequoia backdoor cryptotokens NSAmeansNationalScammingAgency stork conscientiousobjectors ed25519 torproject cryptomeanscryptography encryption 0day informationsecurity ssh misshaialert cybersec restore FileSecurity FormFactors crypto theObservatory autokorrektur giftofencryption CyberSecurity foodsecurity kansascity auto signalapp firejail anonymity endtoendcrypto automattic fotografía onionshare onion encryptionist kontor autofahrer infosecbikini autocrypt malware switchtosignal 0days cloudsecurity corydoctorow RestoreOurEarth radiorepair algérie WebAuthn hexeditor nsogroup automotive distortions cryptographyisoverparty opsec InfoSec keepass encryptionists TastySecurity cryptobros securitybyobscurity torsocks toronto nsa autorenleben schneier protonvpn trustissues InsecurityByObscurity yubikey nitrokey encrypted 1password openpgp pgpainless tatort ghibli afraleaks castor9 deletesignal prismbreak gpgtools autodidactic gpg automation fotopiastory equatorial sequoiapgp cybersecurity Tor CryptoWars signal noscript redaktor vector trust backdoors Torge Torfverbrennung sasl emailsecurity cryptoparty pentest wire historia AllmendeKontor itsecurity websecurity foto pgp RobinHoodStore cryptomator signalmessenger openvpn CyberAttack datasecurity autorotate regulators anleitung leak drugstore encryptiost libresignal doctors securitynow storage tracking
+ zuluCrypt signalboost encrypt letsencrypt messengers autos BrowserHistory FlexibilizaciónResponsable puntarellaparty autoritäreretatismus BlacksInCyber omemo autotomy saveanonymity alg onionrouting Installationsanleitung dataleak messenger foodinsecurity password keepassxc partyline cryptography party cybersecuritynews pipewire Installation cryptolalaland solarwinds bitwarden communityalgorithmictrust infosec gchq GemeinsamGegenDieTierindustrie mitm castor repairing IHaveSomethingToHide fotografie passwords gif IronySec cryptowars anonym encryptioncan supplychainattacks UseAMaskUseTor anonymous cyberattack editors security tor comb e2e supplychain bruceschneier gigafactory vpn BlacksInCybersecurity ransomware wireapp toreador itsec dnssecmastery2e openssh factorio Reactorweg openssl backdoored spyware dorfleaks torx encryptionsts e2ee sequoia backdoor cryptotokens NSAmeansNationalScammingAgency stork conscientiousobjectors ed25519 torproject cryptomeanscryptography encryption 0day informationsecurity ssh misshaialert cybersec restore FileSecurity FormFactors crypto theObservatory autokorrektur giftofencryption CyberSecurity foodsecurity kansascity auto signalapp firejail anonymity endtoendcrypto automattic fotografía onionshare onion encryptionist kontor autofahrer infosecbikini autocrypt malware switchtosignal 0days cloudsecurity corydoctorow RestoreOurEarth radiorepair algérie WebAuthn hexeditor nsogroup automotive distortions cryptographyisoverparty opsec InfoSec keepass encryptionists TastySecurity cryptobros securitybyobscurity torsocks toronto nsa autorenleben schneier protonvpn trustissues InsecurityByObscurity yubikey nitrokey encrypted 1password openpgp pgpainless tatort ghibli afraleaks castor9 deletesignal prismbreak gpgtools autodidactic gpg automation fotopiastory equatorial sequoiapgp cybersecurity Tor CryptoWars signal noscript redaktor vector trust backdoors Torge Torfverbrennung sasl emailsecurity cryptoparty pentest wire historia AllmendeKontor itsecurity websecurity foto pgp RobinHoodStore cryptomator signalmessenger openvpn CyberAttack datasecurity autorotate regulators anleitung leak drugstore encryptiost libresignal doctors securitynow storage tracking theonion odin dnssec autonomy algorithm adtracking threatpost
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
science
- engineering math politicalgeography epidemiology stemfie OpenScienceUN TranslateScience electrochemistry ethnology womeninstem archeology botany STEM biodiversity ocean stemgeenFVD linguistic anthro supercollider nextgeneration zoology linguistics climatology oceans SolarSystems reasoning awk dna geography physics intergenerational archaeologist generalstreik geology ClinicalPsychology generationidentitaire economicanthropology Science SystemicRacism OpenScience corrosion research stemwijzer systemsmap bioengineering GotScience sistemainoperativo stemgeenPVV knowledge stemgeenVVD botanical dream dawkins ineigenersache psychogeography stemgeenVVS holo graphTheory deepdreamgenerator AnnualStatisticalReview trilateralresearch meterology botanicalart JA21 regenerative ScienceDenial biotech stemgeenJA21 regeneration psychology dreamtime pataphysics particles biology bughunting researching_research hunt pacificocean generation gene fossilhunting arthunt badscience mathematics chemistry muon processengineering paleontology oceanography stem anthropocene particlephysics nextgenerationinternet biomedical mechanicalengineering anthropology
+ engineering math politicalgeography epidemiology stemfie OpenScienceUN TranslateScience electrochemistry ethnology womeninstem archeology botany STEM biodiversity ocean stemgeenFVD linguistic anthro supercollider nextgeneration zoology linguistics climatology oceans SolarSystems reasoning awk dna geography physics intergenerational archaeologist generalstreik geology ClinicalPsychology generationidentitaire economicanthropology Science SystemicRacism OpenScience corrosion research stemwijzer systemsmap bioengineering GotScience sistemainoperativo stemgeenPVV knowledge stemgeenVVD botanical dream dawkins ineigenersache psychogeography stemgeenVVS holo graphTheory deepdreamgenerator AnnualStatisticalReview trilateralresearch meterology botanicalart JA21 regenerative ScienceDenial biotech stemgeenJA21 regeneration psychology dreamtime pataphysics particles biology bughunting researching_research hunt pacificocean generation gene fossilhunting arthunt badscience mathematics chemistry muon processengineering paleontology oceanography stem anthropocene particlephysics nextgenerationinternet biomedical mechanicalengineering anthropology helloSystem WoozlesEpistemicParadox epistemicSystems
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
photos
smartphonephotography nikon 90mm photography fujifilm rewild photogrammetry wildlifephotography wild affinityphoto photocló photo photographe photogrpahy photographer tokyocameraclub nikond90 photos macrophotography photoshop photographie photovoltaik seancephoto camera crops photomanager macropod uwphoto wildbiene macronie photographers cameras fossphotography phototherapie phonephotography myphoto rewilding naturephotography microplastics fediphoto picture wildfood macro intothewild streetphotography FujinonXF90mm wildcat photoreference crop phototherapy pictures
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
history
musichistory heirloom monarchs holocaust history arthistory makeinghistory History anarchisthistory indigenoushistorymonth CarHistory gaminghistory womenshistorymonth NetworkingHistory blackhistory otd monarch computerhistory HistoryOfArt
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
software
- beta borgbackup forms app FLOSS freeUP1 freedombox windows edit nginx transclusion krebsrisiken proprietarysoftware cooperativetechnology freepalestin calibre misophonia fosshost postscript nota AAPIHeritageMonth freenet freebsd kc font Framasoft tts E40 Flisol2021 invidious drm freedos softwarelibero alternativesto Raychat publicdomain ilovefreesoftware hydra readers StoryMapJS kubernetes openvms luca nodrm copyleft fossmendations happyauthor freedoom librespeed jami betatesting NottsTV libregraphics genossenschaft FuckOffZoom quicksy thunder whiteboard free docker softwarelibre opensourcehardware uxdesign interoperability impression3d freesoftware gimp krebs backups foss matrix fonts dinosaur mossad unfa weechat clapper designjustice thefreethoughtproject filesystems nextcloud translate wechat notmatrix gnupg lucaApp chats duplicati HappyLight opensourcesoftware permissionless compression ArchLinux openscad freeganizm uidesign softwaredeveloper neochat TabOrder searx ikiwiki prosody Linux FreeSoftware userresearch FlisolLibre2021 DisCOElements Audio rocketchat thanksfreesw libres webapps immers outreachy synapse API freelibre lyft freekirtaner nitter monitoring misogyny virtualbox ngi4eu discord reverseengineering whisperfish ee opensourcedesign vaporware opensource diaspora yunohost oss librelounge AudioCreation chickadee appstore dégooglisons littlebigdetails cabal conferencing cadmium libreboot blueridgeabc musiquelibre mycroft smokefree devops kdeapplications owncast lovewins phabricator emacs freiesoftware FLOSSvol moss fluffychat dinoim impress writefreely videoconferencing bigbluebutton tile_map_editor email moa ngi esri chatapps HappyNewYear Eiskappe fossilfriday umatrix floss plugins softwaresuite frecklesallovertheshow graphic libresoftware softwareengineering mosstodon expandocat deltachat application uifail FOSS peatfree lucaapp GNOMECircle rockpro64 bittorrent palestinewillbefree penpot vlc zoom southasia tiling session diaspora0800 FriendofGNOME Senfstoffknappheit usability winamp opendesign obnam snap appim ProprietarySoftwareProblems pandoc Happy4thJuly freemumia write artificialintelligence blackcappedchickadee cryptpad software libretranslate OwnStream upstream maplibre slack Hummingbard userfreedom hydrated emacslisp Element freeware DismantleFossilCompanies safenetwork asia jit SoftwareLibre zrythm gnu CTZN silicongraphics mumble strugglesessions grsync freecad telegram containers tails freeschool chatons blockchain windows11 irssi HabKeinWhatsapp information mcclim jitsimeet dedrm iso mutt librelingo freetibet WeAreAlmaLinux tilingwm sri design gameoftrees GnuLinuxAudio freegan freeriding freetool backup trueLinuxPhone ngio rotonde freetube jumpdrive GNU speechrecognition eurovison skydroid thunderbird it sound alternativeto screenreader parler bison apps chat licensing fossasia inclusivedesign ethicalsoftware defectivebydesign berne metager digitalsustainability screenreaders ZeroCool LINMOBapps freedombone uber obsproject arti librecast softwareheritage pittsburgh profanity delta Tankklappe doomemacs imageeditor ffmpeg fossandcrafts GNOME40 telesoftware proprietary love notabug reboot opensourcegardens musique switchingsoftware hydrangeas OSM freesw agpl distribute magnifyingglass GNOME freeganizmniewybacza drive botlove duolingo freesoftare AlmaLinux GreenandBlackCross strafmaatschappij freetillie distributedledger mattermost principiadiscordia blue LinuxPhones filesystem rocket ghostscript win10 Zoom tibet ComputerFolklore fossaudio elemental SocialCreditScores flisoltenerife libreops appsec element platforms inclusive uxn librelabucm engineer softwareNotAsAService ptp chatty Matrix lucafail fontawesome informationwantstobefree softwareGripe nativeApp MatrixEffect culturalibre jitsi taintedlove flisol engineers dinosaurier wordpress SwitchToJami mongodb ux rsync libreoffice chatbot crossstitch webdesign Encrochat dino RainbOSM plugin xwiki tecc openoffice container discordia softwaredesign redeslibres ledger sounddesign chatcontrol alternatives glimpse libregraphicsmeeting
+ beta borgbackup forms app FLOSS freeUP1 freedombox windows edit nginx transclusion krebsrisiken proprietarysoftware cooperativetechnology freepalestin calibre misophonia fosshost postscript nota AAPIHeritageMonth freenet freebsd kc font Framasoft tts E40 Flisol2021 invidious drm freedos softwarelibero alternativesto Raychat publicdomain ilovefreesoftware hydra readers StoryMapJS kubernetes openvms luca nodrm copyleft fossmendations happyauthor freedoom librespeed jami betatesting NottsTV libregraphics genossenschaft FuckOffZoom quicksy thunder whiteboard free docker softwarelibre opensourcehardware uxdesign interoperability impression3d freesoftware gimp krebs backups foss matrix fonts dinosaur mossad unfa weechat clapper designjustice thefreethoughtproject filesystems nextcloud translate wechat notmatrix gnupg lucaApp chats duplicati HappyLight opensourcesoftware permissionless compression ArchLinux openscad freeganizm uidesign softwaredeveloper neochat TabOrder searx ikiwiki prosody Linux FreeSoftware userresearch FlisolLibre2021 DisCOElements Audio rocketchat thanksfreesw libres webapps immers outreachy synapse API freelibre lyft freekirtaner nitter monitoring misogyny virtualbox ngi4eu discord reverseengineering whisperfish ee opensourcedesign vaporware opensource diaspora yunohost oss librelounge AudioCreation chickadee appstore dégooglisons littlebigdetails cabal conferencing cadmium libreboot blueridgeabc musiquelibre mycroft smokefree devops kdeapplications owncast lovewins phabricator emacs freiesoftware FLOSSvol moss fluffychat dinoim impress writefreely videoconferencing bigbluebutton tile_map_editor email moa ngi esri chatapps HappyNewYear Eiskappe fossilfriday umatrix floss plugins softwaresuite frecklesallovertheshow graphic libresoftware softwareengineering mosstodon expandocat deltachat application uifail FOSS peatfree lucaapp GNOMECircle rockpro64 bittorrent palestinewillbefree penpot vlc zoom southasia tiling session diaspora0800 FriendofGNOME Senfstoffknappheit usability winamp opendesign obnam snap appim ProprietarySoftwareProblems pandoc Happy4thJuly freemumia write artificialintelligence blackcappedchickadee cryptpad software libretranslate OwnStream upstream maplibre slack Hummingbard userfreedom hydrated emacslisp Element freeware DismantleFossilCompanies safenetwork asia jit SoftwareLibre zrythm gnu CTZN silicongraphics mumble strugglesessions grsync freecad telegram containers tails freeschool chatons blockchain windows11 irssi HabKeinWhatsapp information mcclim jitsimeet dedrm iso mutt librelingo freetibet WeAreAlmaLinux tilingwm sri design gameoftrees GnuLinuxAudio freegan freeriding freetool backup trueLinuxPhone ngio rotonde freetube jumpdrive GNU speechrecognition eurovison skydroid thunderbird it sound alternativeto screenreader parler bison apps chat licensing fossasia inclusivedesign ethicalsoftware defectivebydesign berne metager digitalsustainability screenreaders ZeroCool LINMOBapps freedombone uber obsproject arti librecast softwareheritage pittsburgh profanity delta Tankklappe doomemacs imageeditor ffmpeg fossandcrafts GNOME40 telesoftware proprietary love notabug reboot opensourcegardens musique switchingsoftware hydrangeas OSM freesw agpl distribute magnifyingglass GNOME freeganizmniewybacza drive botlove duolingo freesoftare AlmaLinux GreenandBlackCross strafmaatschappij freetillie distributedledger mattermost principiadiscordia blue LinuxPhones filesystem rocket ghostscript win10 Zoom tibet ComputerFolklore fossaudio elemental SocialCreditScores flisoltenerife libreops appsec element platforms inclusive uxn librelabucm engineer softwareNotAsAService ptp chatty Matrix lucafail fontawesome informationwantstobefree softwareGripe nativeApp MatrixEffect culturalibre jitsi taintedlove flisol engineers dinosaurier wordpress SwitchToJami mongodb ux rsync libreoffice chatbot crossstitch webdesign Encrochat dino RainbOSM plugin xwiki tecc openoffice container discordia softwaredesign redeslibres ledger sounddesign chatcontrol alternatives glimpse libregraphicsmeeting libre mycroftai kiwi multics sustainablewebdesign spacemacs meditation composeui
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
conferences
- FOSDEM2021 stackconf fossnorth debconf debconf21 FOSDEM talk fossdem FreedomBoxSummit apconf2020 schmoocon Aktionscamp realtalk persco penguicon2021 letstalkaboutyes summit confidenceTricks agm libreplanet SeaGL2021 confindustria confluence minidebconf edw2021 maintainerssummit rc3worldleaks rightscon StopStalkerAds SeaGL penguicon emacsconf MCH2021 conferencecalls flossconference LGM2021 conferences LibrePlanet defcon emfcamp flossevent askpinetalk bc conf talks defcon201 rC3 rC3World FOSDEM21 conference mozfest flossconf bootcamp apconf ccc persconferentie GeekBeaconFest rC3one GenCon smalltalk camp g7 C3 config penguicon2022 confy
+ FOSDEM2021 stackconf fossnorth debconf debconf21 FOSDEM talk fossdem FreedomBoxSummit apconf2020 schmoocon Aktionscamp realtalk persco penguicon2021 letstalkaboutyes summit confidenceTricks agm libreplanet SeaGL2021 confindustria confluence minidebconf edw2021 maintainerssummit rc3worldleaks rightscon StopStalkerAds SeaGL penguicon emacsconf MCH2021 conferencecalls flossconference LGM2021 conferences LibrePlanet defcon emfcamp flossevent askpinetalk bc conf talks defcon201 rC3 rC3World FOSDEM21 conference mozfest flossconf bootcamp apconf ccc persconferentie GeekBeaconFest rC3one GenCon smalltalk camp g7 C3 config penguicon2022 confy debconf20 confession
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
farming
johndeere deer
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
facts
lifehacking funfact lifehack
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
indymedia
- fpga hs2 dotcons visionontv geek tredtionalmedia indiemedia degeek globleIMC indymediaback pga mainstreaming indymedia closed stupid foo encryptionsist hs2IMC indymediaIMC network networkmonitoring Blackfoot roadsIMC stupidindivialisam roadstonowhere networkeffect lifecult closedweb avgeek monitor dotconsall omn tv roadstonowhereIMC kiss UKIMC fluffy 4opens openmedianetwork
+ fpga hs2 dotcons visionontv geek tredtionalmedia indiemedia degeek globleIMC indymediaback pga mainstreaming indymedia closed stupid foo encryptionsist hs2IMC indymediaIMC network networkmonitoring Blackfoot roadsIMC stupidindivialisam roadstonowhere networkeffect lifecult closedweb avgeek monitor dotconsall omn tv roadstonowhereIMC kiss UKIMC fluffy 4opens openmedianetwork stupidiindividualism
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
cycling
bicycle bicyles cycle bic cycling bicycleday DataRecycling arabic bike motorbike reusereducerecycle bikeing cyclingtour thingsonbikes openbikesensor bikeways Snowbike cyclist
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
gender
- black blackcompany transparantie transistors transparenz broketrans transition internationalwomensday2021 transwomen transformativejustice womenwhocode transfobie buildless WomenInHistory sf transmission transgender cashless RaquelvanHaver caféLatte transdayofresistance mens vieillesse womensart blacktranslivesmatter female nonbinary womensday vantascape van blacktransmagic less nb trans patriarchy nonbinarycommunity transpositivity LucyLawless transdayofvisibility lgbtqia transphobia transmitter women menschheit lgbt bodypositive nonbinarypeoplesday transzorg womenrock estradiol lgbtq transaid queerartist KCHomelessUnion transgenders girlboss pointlesslygendered queer transdayofvisbility nonbinaryday genderQuiz gender genderqueerpositivity NonBinaryPositivity dagvandearbeid woman transrights transdayofrevenge
+ black blackcompany transparantie transistors transparenz broketrans transition internationalwomensday2021 transwomen transformativejustice womenwhocode transfobie buildless WomenInHistory sf transmission transgender cashless RaquelvanHaver caféLatte transdayofresistance mens vieillesse womensart blacktranslivesmatter female nonbinary womensday vantascape van blacktransmagic less nb trans patriarchy nonbinarycommunity transpositivity LucyLawless transdayofvisibility lgbtqia transphobia transmitter women menschheit lgbt bodypositive nonbinarypeoplesday transzorg womenrock estradiol lgbtq transaid queerartist KCHomelessUnion transgenders girlboss pointlesslygendered queer transdayofvisbility nonbinaryday genderQuiz gender genderqueerpositivity NonBinaryPositivity dagvandearbeid woman transrights transdayofrevenge transmetropolitan
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
phones
- mobileapp cellular fairphone3 téléphone libre nemomobile fairtec linuxfr conocimientolibre manjaro Jingos plasmaDev TourBrandenburg21 rand 5g mobian LinuxPhoneApps lg pine Brandkopf alarmphone androidemulator fdroid plasmamobile shotonpinephone fairuse android smartphonepic nophone ubportsqanda linuxmobile sailfish phones fennecfdroid Mobian osmf AlpineConf automobile smartphone plasma5 ios selinux mobileGNU PinePhoneOrderDay exxon sms4you mob bp microphone linuxconnexion smart smartphones iOS14 pinemarten linuxphones openmoko mobilecoin mobilelinux freeyourandroid fair QWERTYphones exxonmobil sailfishos siskinim epic monal android10 osmocom Smartphones WakeMobile androids lineageos molly angelfish androiddev Briar manjarolinux quasseldroid wirtschaft plasma mobilephones phosh BriarProject Fairphone librem5 ubportsinstaller linuxphone shotonlibrem5 pinephone Teracube PinePhone pinedio mobile pinephones manjaroarm sms pine64 automobiles fairphone ubuntutouch linphone Android osmirl ubports gnomeonmobile immobilienwirtschaft Bramble osmand vodafone gnomemobile linuxonmobile iphones postmarketos iOS microg brandenburg librecellular GetSession grapheneos sail recycletechjunkuselinux phone cm mobileKüfA lineage josm iphone linuxappsummit Xperia10mark2 newprofilepic
+ mobileapp cellular fairphone3 téléphone nemomobile fairtec linuxfr conocimientolibre manjaro Jingos plasmaDev TourBrandenburg21 rand 5g mobian LinuxPhoneApps lg pine Brandkopf alarmphone androidemulator fdroid plasmamobile shotonpinephone fairuse android smartphonepic nophone ubportsqanda linuxmobile sailfish phones fennecfdroid Mobian osmf AlpineConf automobile smartphone plasma5 ios selinux mobileGNU PinePhoneOrderDay exxon sms4you mob bp microphone linuxconnexion smart smartphones iOS14 pinemarten linuxphones openmoko mobilecoin mobilelinux freeyourandroid fair QWERTYphones exxonmobil sailfishos siskinim epic monal android10 osmocom Smartphones WakeMobile androids lineageos molly angelfish androiddev Briar manjarolinux quasseldroid wirtschaft plasma mobilephones phosh BriarProject Fairphone librem5 ubportsinstaller linuxphone shotonlibrem5 pinephone Teracube PinePhone pinedio mobile pinephones manjaroarm sms pine64 automobiles fairphone ubuntutouch linphone Android osmirl ubports gnomeonmobile immobilienwirtschaft Bramble osmand vodafone gnomemobile linuxonmobile iphones postmarketos iOS microg brandenburg librecellular GetSession grapheneos sail recycletechjunkuselinux phone cm mobileKüfA lineage josm iphone linuxappsummit Xperia10mark2 newprofilepic tmobile headphones dumbphones telephones spyphone
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
activism
- UniteInResistance rightwing rights activism protestor dutysolicitor roots WeDemandTransparency CallToAction annonce rojava PrisonLivesMatter clearchannel nog20 Lobauautobahn farright eni tyrannyofconvenience grassroot nonviolentcommunication FreeLibreOpen protesters g20 ShaleMustFall JusticeForRapheal rig augustriseup bekannt farmersprotest animalrights protests resistance cyborgrights riseup resistg7 DontShootTheMessenger demo PrisonSolidarity linnemann sflc uprootthesystem DanniVive apt freeassange dangote reuse stopspyingonus keepiton Dannenroederforst FSFE20 ClimateJusticeMovement fsfe killthebill edri softwarefreedom indigenousrights activists unautremondeestpossible AntiCopyright Rojava ilovefs stopnacjonalizmowi ann activist wec HeroesResist edrigram xr SustainableUserFreedom bannerlord systemchangenotclimatechange undercurrents riseup4rojava righttoexist seachange directaction mannheim Doulingo politicalactivism diskriminierung wechange seattleprotests eff Gardening gamechanger root change openrightsgroup protest icantbreathe channelname JeffreySDukes planning FSF userrights LaptevSea actiondirecte kroymann climatechange protestsupport channel climatchange HS2 ngo MarcWittmann StandWithTillie Danni FrightfulFive fsf fsfi StopHS2 grassroots HS2Rebellion protestcamp resist openrights TalesFromTheExtinction FreeJournalistAssange announcements antireport ClimateJustice RodrigoNunes FreedomCamping BLM ExtinctionRebellion shellmustfall namechange changeisinyourhands wlroots weareallassange conservancy ngos UserFreedom sp bin JefferySaunders freepalestine CopsOffCampus GreatGreenWall LiliannePloumen freeassangenow savetheplanet freeradical directactiongetsthegoods hauptmann activismandlaw climatechangeadaptation Kolektiva Indigenousresistance BayouBridgePipeline XR freeolabini tellthetruth announcement isolateByoblu annieleonard
+ UniteInResistance rightwing rights activism protestor dutysolicitor roots WeDemandTransparency CallToAction annonce rojava PrisonLivesMatter clearchannel nog20 Lobauautobahn farright eni tyrannyofconvenience grassroot nonviolentcommunication FreeLibreOpen protesters g20 ShaleMustFall JusticeForRapheal rig augustriseup bekannt farmersprotest animalrights protests resistance cyborgrights riseup resistg7 DontShootTheMessenger demo PrisonSolidarity linnemann sflc uprootthesystem DanniVive apt freeassange dangote reuse stopspyingonus keepiton Dannenroederforst FSFE20 ClimateJusticeMovement fsfe killthebill edri softwarefreedom indigenousrights activists unautremondeestpossible AntiCopyright Rojava ilovefs stopnacjonalizmowi ann activist wec HeroesResist edrigram xr SustainableUserFreedom bannerlord systemchangenotclimatechange undercurrents riseup4rojava righttoexist seachange directaction mannheim Doulingo politicalactivism diskriminierung wechange seattleprotests eff Gardening gamechanger root change openrightsgroup protest icantbreathe channelname JeffreySDukes planning FSF userrights LaptevSea actiondirecte kroymann climatechange protestsupport channel climatchange HS2 ngo MarcWittmann StandWithTillie Danni FrightfulFive fsf fsfi StopHS2 grassroots HS2Rebellion protestcamp resist openrights TalesFromTheExtinction FreeJournalistAssange announcements antireport ClimateJustice RodrigoNunes FreedomCamping BLM ExtinctionRebellion shellmustfall namechange changeisinyourhands wlroots weareallassange conservancy ngos UserFreedom sp bin JefferySaunders freepalestine CopsOffCampus GreatGreenWall LiliannePloumen freeassangenow savetheplanet freeradical directactiongetsthegoods hauptmann activismandlaw climatechangeadaptation Kolektiva Indigenousresistance BayouBridgePipeline XR freeolabini tellthetruth announcement isolateByoblu annieleonard digitalhumanrights prototypedemos
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
accessibility
- you a11y accessibility captionyourimages hardofhearing
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
pandemic
- covid19 coronaPolicies gevaccineerd corona getvaccinated CovidImpacts psmeandmywholefamilycaughtcovidfromwork Coronavirus CoronaWarnApp facemasks vaccines wijvaccineren culturalrevolution pandemics vaccine vaccinesupply JournalistsSpeakUpForAssange Covid vaccinated coranavirus NoCovidMonopolies pandemic sayhername internationalproletarianrevolution Zbalermorna internationalcatday covidville ZeroCovid vaccini pandemia coronapps volkstheater COVID19india contacttracing VaccinePatents coronavaccinatie SùghAnEòrna tier4 coronapandemie covid pand SarsCoV2 volla volodine COVID19NL covidmask Moderna coronavirus masks viruses Moderna2 COVIDrelief coronapas virus contacttracingapps moderna coronadebat vaccin COVIDー19 Lockdown rna unvaccinated codid19 CripCOVID19 LongCovid COVID19 vaccination YesWeWork ContactTracing vol coronaviruses CoronaCrisis COVID coronamaatregelen debat international internationalsolidarity coronabeleid
+ covid19 coronaPolicies gevaccineerd corona getvaccinated CovidImpacts psmeandmywholefamilycaughtcovidfromwork Coronavirus CoronaWarnApp facemasks vaccines wijvaccineren culturalrevolution pandemics vaccine vaccinesupply JournalistsSpeakUpForAssange Covid vaccinated coranavirus NoCovidMonopolies pandemic sayhername internationalproletarianrevolution Zbalermorna internationalcatday covidville ZeroCovid vaccini pandemia coronapps volkstheater COVID19india contacttracing VaccinePatents coronavaccinatie SùghAnEòrna tier4 coronapandemie covid pand SarsCoV2 volla volodine COVID19NL covidmask Moderna coronavirus masks viruses Moderna2 COVIDrelief coronapas virus contacttracingapps moderna coronadebat vaccin COVIDー19 Lockdown rna unvaccinated codid19 CripCOVID19 LongCovid COVID19 vaccination YesWeWork ContactTracing vol coronaviruses CoronaCrisis COVID coronamaatregelen debat international internationalsolidarity coronabeleid ZeroCOVID
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
books
- readinggroup bookstore publicvoit bookbinding preview justhollythings secondhandbooks bookclub fake earthsea review ebooks docbook book notebook public amreading publishing republicday publichealth bookworm bookwyrm 5minsketch artbook republique bookreview reading sketching theLibrary audiobooks Gempub selfpublishing sketchbook wayfarers books peerreview bookreviews failbooks sketch ebook wikibooks booktodon epub cookbook bibliothèque AnarchoBookClub
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
crafts
- repair topic_imadethis hackerexchange exchange quilts textile upholstery hackgregator gatos gato hackspacers nrw shack 3dmodeling dust3d hackerspaces hacklab tryhackme sanding solvespace theglassroom sundiy craft wirtschafthacken papercrafts maker knitting hack workspace craftsmanship wood hacked Sipcraft calligraphy biohacking wip spacecrafts hacktheplanet jewelry diy textiles projects hackerweekend handicrafts Handicraft lovecraftcountry upcycling Minecraft woodworking 3dcad glass origami hackerexchange
+ repair topic_imadethis hackerexchange exchange quilts textile upholstery hackgregator gatos gato hackspacers nrw shack 3dmodeling dust3d hackerspaces hacklab tryhackme sanding solvespace sundiy craft wirtschafthacken papercrafts maker knitting hack workspace craftsmanship wood hacked Sipcraft calligraphy biohacking wip spacecrafts hacktheplanet jewelry diy textiles projects hackerweekend handicrafts Handicraft lovecraftcountry upcycling Minecraft woodworking 3dcad glass origami hackerexchange
-]] makers nrwe quilting crafting sparkwoodand21 hacker quilt crafts rwe weaving 3dmodel handtools tinkering project hacking woodwork ceramics handmade embroidery shacks teardown
+]] makers nrwe quilting crafting sparkwoodand21 hacker quilt crafts rwe weaving 3dmodel handtools tinkering project hacking woodwork ceramics handmade embroidery shacks teardown lockdownrepair lockdown level4lockdown lockdownRepairs
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
+
+-
+
exhibitions
+ theglassroom
+
+ Sun, 05 Sep 2021 11:03:44 UT
-
war
ru DonavynCoffey Myanmarmilitarycoup civilwar antiwar bomber coup weapon tank handforth landmine tankies military autonomousweapons army Etankstelle weaponsofmathdestruction conflict navy warplane fort guns Myanmarcoup weapons siege hbomberguy battle WMD wmd airforce forth
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
techbros
- bubbles bubble color redbubble securedrop einfachredeneben redditodicittadinanza coloredpencil redhat redwood hackernews weareredhat redmi red pencil reddit redon redis infrared VendrediNouka redshift optreden sec
+ bubbles bubble color redbubble securedrop einfachredeneben redditodicittadinanza coloredpencil redhat redwood hackernews weareredhat redmi red pencil reddit redon redis infrared VendrediNouka redshift optreden sec colorado
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
astronomy
telescope immersspace mercury guide pluto planets galaxy spaceport venus mars bloodmoon amateurastronomy uranus spacex nebula astronomy hubblespacetelescope neptune space jupiter rpc blackhole asteroid BackYardAstronomy moon thehitchhikersguidetothegalaxy observatory euspace asteroidos saturn milkyway telescopes spacelarpcafe
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
other
- ageassurance pentester bullshit klimaatbeleid justasleepypanda extinctionrebellion fail masseffect lastpass yolo nothingnew Lastpass extinction weareclosed happy efail bripe MasseyUniversity PassSanitaire solution dansenmetjanssen messageToSelf TagGegenAntimuslimischenRassismus quecksilber itscomplicated Erzvorkommen test isntreal gentests rzeźwołyńska massextinction misc tw rants manutentore frantzfanon shots assaultcube shitpost denachtvanjanssen biomassacentrale mining rising devilsadvocate ACA pinside xp impfpass cda rant Terrassen righttodisassemble rassismus MassoudBarzani koerden CovPass nahrungskette SomeUsefulAndRelvantHashtag LanguageHelpForMigrants nsfw dungeonsAndDragons biomass rassismustötet oversleep ass id Chiacoin futtermittel CubanProtests geo oerde m assassinfly migrantstruggles sleep PointlessGriping close decluttering OCUPACAOCARLOSMARIGHELLA
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
photography
peppercarrotmini NoShothgunParsers pea CanonSL2 landscapephotography landscapeart XSystem darktable photograph peppercarrot speakers hippeastrum landscape blackandwhite hot twinpeaks
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
month
maythe4thbewithyou yt ots april juneteenth PrideMonth2021 bots 1may july VeganMay march pridemonth chapril marchofrobots2021 october november august june blackherstorymonth december september augustusinc may feburary jejune PrideMonth january marchofrobots eternalseptember blackhistorymonth march4justice month robots maythe4th blacktheirstorymonth
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
news
- basicincome report news 56kNews Newsfeed krautreporter flash basic Wikileaks newsletter aljazeera nothingnews newsflash contemporaneous_reports newsroom EUNews Worldnews rt bbc foxnews journalismisnotacrime News bbcbasic goodnews flashcrash doubledownnews bbcnews reuters newschool theguardian fieldreport badReporting newsboat journalism SkyNews crash lobsters
+ basicincome report news 56kNews Newsfeed krautreporter flash basic Wikileaks newsletter aljazeera nothingnews newsflash contemporaneous_reports newsroom EUNews Worldnews rt bbc foxnews journalismisnotacrime News bbcbasic goodnews flashcrash doubledownnews bbcnews reuters newschool theguardian fieldreport badReporting newsboat journalism SkyNews crash lobsters newsmax
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
cats
Cat dailycatpic dxp MastoCats DailyCatVid Cats katze kotorico kot ketikoti qualitätskatze CatsOfMastodon Catshuis Leopard SpaceCatsFightFascism CatBellies catbellies LapCats qualitätskatzen katzen
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
podcasts
beautiful podcasting IntergalacticWasabiHour JenaFahrradies podcast rad radiopodcast postmarketOSpodcast TraditionCruelle podcasting20 tilderadio tildes podcasts tildeverse radverkehr smallisbeautiful fertilizers PineTalk radweg tilvids fahrrad tildetown qtile trillbilliespodcast
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
radio
cbradio worldradioday radiokookpunt hamr freieradios varia why radioamateur shoshanazuboff winlink tootlabradio pouetradio schenklradio dx macintosh radioactive ntsradio amateurradio radiohost radiokapital talkradio localization shortwave nwr vantaradio roadsafety ca radio healthcare listening hamradio FreeAllPoliticalPrisoners variabroadcasts card10 fastapi webradio freeradio radiobroadcasting radiosurvivor Poecileatricapillus apis radioshow local cellbroadcast radio3 noshame osh audycja hackerpublicradio kosher radioalhara Phosh audycjaradiowa california road nowlistening radiobroadcast radiostation mastoradio broadcasting radiodread amateurr radiolibre modelrailroad spazradio anonradio Capitaloceno kolektywneradio io
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
pets
- buny spinning catpics shepherd leiningen uninstallman ExposureNotifications ats germanshepherd catofmastodon nin TheRabbitHole staatstrojaner deskcat verification eurocrats QuestioningTechnology toocute cataloging cathedrals petpeeve Stelleninserat acidification reEducationCamp mastodogs rats puppets catbehaviour digidog dogecoin Stallman Coolcats petrats governing dogsofmastodon gentrification evening broadcats gattini bunyPosting benjennings kitten fostercats gamification woningnet WegenErdogan jürgenconings cats uninStallman kittens Uninstallman pet dog scotties Pruning woningnood acat catontour catsofmastodon leninismo podcatcher meow cute mastocat lenin catstodon dogs reimagining catsofparkdale mastocats W3CSpecification mastodog notpixiethecat londoninnercitykitties cat blackcat furry petitie JuliaKitten dogsofmaston JurgenConings training scottie catcontent UserDomestication
+ buny spinning catpics shepherd leiningen uninstallman ExposureNotifications ats germanshepherd catofmastodon nin TheRabbitHole staatstrojaner deskcat verification eurocrats QuestioningTechnology toocute cataloging cathedrals petpeeve Stelleninserat acidification reEducationCamp mastodogs rats puppets catbehaviour digidog dogecoin Stallman Coolcats petrats governing dogsofmastodon gentrification evening broadcats gattini bunyPosting benjennings kitten fostercats gamification woningnet WegenErdogan jürgenconings cats uninStallman kittens Uninstallman pet dog scotties Pruning woningnood acat catontour catsofmastodon leninismo podcatcher meow cute mastocat lenin catstodon dogs reimagining catsofparkdale mastocats W3CSpecification mastodog notpixiethecat londoninnercitykitties cat blackcat furry petitie JuliaKitten dogsofmaston JurgenConings training scottie catcontent UserDomestication vacation puppet
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
years
- newyearsresolutions resolutions Year2020 year 1yrago newyear happynewyear ox 5yrsago yearoftheox newyearseve
+ newyearsresolutions resolutions Year2020 year 1yrago newyear happynewyear ox 5yrsago yearoftheox newyearseve resolution
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
philosophy
postmeritocracy post minimalism maximalist Allposts nationalpost maximalism digitalminimalism postprocess philosophy erp stoic spiderposting postfordismo postmodernism minimalist
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
transport
deutschland luggage Gütertransporte publictransport busses activetransport transportation train transport trains deutsch deutscheumwelthilfe airway journey motorway aviation deutschebahn travel ev prorail airport rail
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
ethics
licenses digitalethics ethicaltech ethics ethicallicense ethicswashing ethical ethicsintech
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
commons
- ed openformat mentalillness opennmsgroup OpenAccessButton niemandistvergessen distraction open linkedopenactors openaccess reopening openocd openengiadina opennms ess badges opensocial commonscloud activisim openlibrary characters opensourcing innovation openpublishing verge InstantMessenger LessIsMore openrefine openworlds extraction openwashing publicinterest besserorganisieren exittocommunity openinnovation opennmt openbadges act accessable openfest2021 ManufacturaIndependente openspades Accessibility keinvergessen openrepos2021 openftw Bessa
+ ed openformat mentalillness opennmsgroup OpenAccessButton niemandistvergessen distraction open linkedopenactors openaccess reopening openocd openengiadina opennms ess badges opensocial commonscloud activisim openlibrary characters opensourcing innovation openpublishing verge InstantMessenger LessIsMore openrefine openworlds extraction openwashing publicinterest besserorganisieren exittocommunity openinnovation opennmt openbadges act accessable openfest2021 ManufacturaIndependente openspades Accessibility keinvergessen openrepos2021 openftw Bessa openarena
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
seasons
- mailspring officehours spring lupin thespinoff Dadvice autumn abolishice desummersummit licenziamenti namedropping office hooping sipping es fuckice winter EthicalLicenses ice luejenspringer hpintegrity pingpong santa summer iced LibreOffice summerschool onlyoffice pinball icedipping solstice unicef officework wintersolstice FederalOffice summerRolls pin mice
+ mailspring officehours spring lupin thespinoff Dadvice autumn abolishice desummersummit licenziamenti namedropping office hooping sipping es fuckice winter EthicalLicenses ice luejenspringer hpintegrity pingpong santa summer iced LibreOffice summerschool onlyoffice pinball icedipping solstice unicef officework wintersolstice FederalOffice summerRolls pin mice school
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
questions
- checking kayaking askmastodon flockingbird biking questions king euskadi asking mask lockpicking Hacking GlobalFrackingBan factchecking askfedi basketball smoking WorldAgainstFracking askafriend flask GlobalBanOnFracking TraditionalWoodworking question ska askmasto breaking scrap_booking maskengate criticalthinking askfediverse fucking totallyaskingforafriend ask daretoask askfosstodon
+ checking kayaking askmastodon flockingbird biking questions king euskadi asking mask lockpicking Hacking GlobalFrackingBan factchecking askfedi basketball smoking WorldAgainstFracking askafriend flask GlobalBanOnFracking TraditionalWoodworking question ska askmasto breaking scrap_booking maskengate criticalthinking askfediverse fucking totallyaskingforafriend ask daretoask askfosstodon lockin
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
fiction
ABoringDystopia interactivefiction cyberpunk VersGNRWstoppen thehobbit fiction microfiction stopCGL nonfiction DystopianCyberpunkFuture stoptmx top flashfiction cyberpunk2020 genrefiction
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
audio
feed audiophile liveaudio audioproduction feeds pulseaudio audi webaudio feedbackd audioprogramming mastoaudio audioengineering audience audiogames audiofeedback audio auditoriasocial
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
garbage
Anonymous cumbria documentation no QAnonAnonymous docu cardano documents cum u ChanCulture
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
birds
- RainbowBeeEater aves birb pigeon cawbird pigeonlover bird birdposting birdwatch birdsite birding birbposting birdwatching
+ RainbowBeeEater aves birb pigeon cawbird pigeonlover bird birdposting birdwatch birdsite birding birbposting birdwatching birdbutt
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
disability
ableism disabled ableismus
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
travel
tax travellers taxi airtravel
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
religion
atheist buddhist ama neopagan pagan catholic paganism genesis jesuit secularism SiddarthaGautama oorlogspropaganda
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
culture
etiquette
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
funding
- donate disabilitycrowdfund disabledcrowdfund erschöpfung funding now oled LuisaToledo alledoerferbleiben LeylaKhaled ethicalfunding mastercard netzfundstück didyouknow fundraiser BreakWalledGardens ki membership fundamentals nzSuperFund ngizero fun oer zeroknowledge edge led zerohedge DefundLine3 vkickstarter fungiverse alledörferbleiben fungus fundingmatters SmallPiecesLooselyCoupled hedgedog fungi EntangledLife desperate opencollective patreon FundOSS
+ donate disabilitycrowdfund disabledcrowdfund erschöpfung funding now oled LuisaToledo alledoerferbleiben LeylaKhaled ethicalfunding mastercard netzfundstück didyouknow fundraiser BreakWalledGardens ki membership fundamentals nzSuperFund ngizero fun oer zeroknowledge edge led zerohedge DefundLine3 vkickstarter fungiverse alledörferbleiben fungus fundingmatters SmallPiecesLooselyCoupled hedgedog fungi EntangledLife desperate opencollective patreon FundOSS nlnet
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
identity
genx boomer genz zoomer
- Tue, 10 Aug 2021 08:34:29 UT
-
--
-
ai
- macos machinelearning openai EthicsInAI
-
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
licenses
commongoods creativecommonsrocks voice violation agplv3 tootle commoning commonvoice CommunitySource place copyright commonspoly creative netcommons common gpl plugplugplug copyrightlaw commonplacebook license EthicalSource questioncopyright tragedyofthecommons cc0 creativecommons commongood cc creativetoot
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
political
copservation housekeeping gan ram progress slaughterhouse rog cops houseless brogue progresso joerogan theteahouse bibliogram house hydrogen straming theGreenhouse spycops teahouse progressivehouse techhouse clubhouse yayagram PDXdefendthehouseless pdxhouseless EnergyFlowDiagrams pr progress_note deephouse roguelike linguisticProgramming gancio
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
organisations
foundation scpfoundation scp
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
fashion
brasil fashionistas fashionesta bras fashionista fashion punkwear earrings socks patches feditats zebras
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
welfare
CreditReporting universalcredit welfare socialwelfare credit
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
scotland
lan atlanta glasgow highlands edinburgh loch
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
antisocial
stalking cyberstalking
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
comedy
laugh farce humour swisshumor satire irony standup funny humor punishment pun
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
obituaries
ueberwachung siberia tripadvisor rip JavaScriptSucks ratgeber obit ecmascript keyenberg raspberripi döppersberg cybergrooming Gudensberg überblick obituaries ber civilliberties rubber cyber
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
introductions
reintroductions newhere firsttoot recommends stt Introduction Introductions reintroduction introductons introduction intro introductions
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
geography
- theCartographer graph
+ theCartographer graph cartography
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
education
- SchoolForAfrica PhDstudent mitbewohnerin techlearningcollective oh languages student teaching tutorials education academics mit academia teach Lebensmittelfarbstoff elearning learning languagelearning tutorial mitkatzundkegel ec language deeplearning collect teacher cad mitteleuropa
+ SchoolForAfrica PhDstudent mitbewohnerin techlearningcollective oh languages student teaching tutorials education academics mit academia teach Lebensmittelfarbstoff elearning learning languagelearning tutorial mitkatzundkegel ec language deeplearning collect teacher cad mitteleuropa populareducation students
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
scifi
startrekdiscovery startrek discover SoftwareJob LegDichNieMitSchwarzenKatzenAn starwars ds9 discovery trek SchwarzeFrauen babylon NGIForward war babylon5
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
microcontroller
e kontrollieren microcontroller trolls Chatkontrolle troll arduinoide arduino
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
design
userfriendly friendly rf
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
help
mastohelp MutualAidRequest helpwanted lpf helpful MutualAidReques hilfe helpMeOutHere help
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
automotive
volkswagen
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
fantasy
discworld godzilla
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
entertainment
CircusInPlace legallyblonde watching theCinema Thundercat makingof entertainment me un nowwatching mandalorian themandalorian nt
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
election
Rainbowvote voted vote
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
moderation
fedblock
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
languages
lojban gaelic
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
environment
s crisisclimatica clim climatechaos climateadaptation
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
organization
conceptmap mindmapping mapping mindmap notetoself pi
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
industrial
powerplants
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
technology
AvatarResearch tools LowtechSolutions literatools
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
microcontrollers
esp32c3 microcontrollers esp8266 esp32
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
agriculture
farmers
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
organisation
InstitutionalMemory
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
skills
gardening baking
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
france
Macronavirus
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
memes
tired
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
sailing
theBoatyard
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
parenting
dadposting
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
jewelry
bracelet
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
architecture
concrete
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
-
licences
TVRights
- Tue, 10 Aug 2021 08:34:29 UT
+ Sun, 05 Sep 2021 11:03:44 UT
From 3261b41abf57b0c32eb64f3d41327bb430d4520f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 09:31:11 +0100
Subject: [PATCH 132/385] Check for duplicates
---
webapp_timeline.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/webapp_timeline.py b/webapp_timeline.py
index f375272ba..a4c158c77 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -900,10 +900,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
timelineStartTime, boxName, '12')
if currTlStr:
- itemCtr += 1
- tlStr += textModeSeparator + currTlStr
- if separatorStr:
- tlStr += separatorStr
+ if currTlStr not in tlStr:
+ itemCtr += 1
+ tlStr += textModeSeparator + currTlStr
+ if separatorStr:
+ tlStr += separatorStr
if boxName == 'tlmedia':
tlStr += '\n'
From 12c0c64d9615b0c6b5d8c639f1efe986b675aca8 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 09:48:58 +0100
Subject: [PATCH 133/385] Avoid duplicates
---
posts.py | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/posts.py b/posts.py
index df0839a21..25a6e8274 100644
--- a/posts.py
+++ b/posts.py
@@ -3458,6 +3458,7 @@ def _createBoxIndexed(recentPostsCache: {},
}
postsInBox = []
+ postUrlsInBox = []
indexFilename = \
acctDir(baseDir, timelineNickname, originalDomain) + \
@@ -3497,19 +3498,23 @@ def _createBoxIndexed(recentPostsCache: {},
postFilename.replace('\n', '').replace('\r', '')
postUrl = postUrl.replace('.json', '').strip()
+ if url in postUrlsInBox:
+ continue
+
# is the post cached in memory?
if recentPostsCache.get('index'):
if postUrl in recentPostsCache['index']:
if recentPostsCache['json'].get(postUrl):
url = recentPostsCache['json'][postUrl]
- if _addPostStringToTimeline(url,
- boxname, postsInBox,
- boxActor):
- totalPostsCount += 1
- postsAddedToTimeline += 1
- continue
- else:
- print('Post not added to timeline')
+ if _addPostStringToTimeline(url,
+ boxname, postsInBox,
+ boxActor):
+ totalPostsCount += 1
+ postsAddedToTimeline += 1
+ postUrlsInBox.append(url)
+ continue
+ else:
+ print('Post not added to timeline')
# read the post from file
fullPostFilename = \
@@ -3524,6 +3529,7 @@ def _createBoxIndexed(recentPostsCache: {},
postsInBox, boxActor):
postsAddedToTimeline += 1
totalPostsCount += 1
+ postUrlsInBox.append(url)
else:
print('WARN: Unable to add post ' + postUrl +
' nickname ' + nickname +
@@ -3539,6 +3545,7 @@ def _createBoxIndexed(recentPostsCache: {},
postsInBox, boxActor):
postsAddedToTimeline += 1
totalPostsCount += 1
+ postUrlsInBox.append(url)
else:
print('WARN: Unable to add features post ' +
postUrl + ' nickname ' + nickname +
From 3c2e575ea9e0574852ce3a19550bc04ee41877c4 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 09:50:44 +0100
Subject: [PATCH 134/385] Indentation
---
posts.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/posts.py b/posts.py
index 25a6e8274..bca31c6fd 100644
--- a/posts.py
+++ b/posts.py
@@ -3506,15 +3506,15 @@ def _createBoxIndexed(recentPostsCache: {},
if postUrl in recentPostsCache['index']:
if recentPostsCache['json'].get(postUrl):
url = recentPostsCache['json'][postUrl]
- if _addPostStringToTimeline(url,
- boxname, postsInBox,
- boxActor):
- totalPostsCount += 1
- postsAddedToTimeline += 1
- postUrlsInBox.append(url)
- continue
- else:
- print('Post not added to timeline')
+ if _addPostStringToTimeline(url,
+ boxname, postsInBox,
+ boxActor):
+ totalPostsCount += 1
+ postsAddedToTimeline += 1
+ postUrlsInBox.append(url)
+ continue
+ else:
+ print('Post not added to timeline')
# read the post from file
fullPostFilename = \
From 36fd03166b314dcf59d163a92648a2b496dc3f38 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 09:52:34 +0100
Subject: [PATCH 135/385] Url variable
---
posts.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/posts.py b/posts.py
index bca31c6fd..51d96c781 100644
--- a/posts.py
+++ b/posts.py
@@ -3498,7 +3498,7 @@ def _createBoxIndexed(recentPostsCache: {},
postFilename.replace('\n', '').replace('\r', '')
postUrl = postUrl.replace('.json', '').strip()
- if url in postUrlsInBox:
+ if postUrl in postUrlsInBox:
continue
# is the post cached in memory?
@@ -3511,7 +3511,7 @@ def _createBoxIndexed(recentPostsCache: {},
boxActor):
totalPostsCount += 1
postsAddedToTimeline += 1
- postUrlsInBox.append(url)
+ postUrlsInBox.append(postUrl)
continue
else:
print('Post not added to timeline')
@@ -3529,7 +3529,7 @@ def _createBoxIndexed(recentPostsCache: {},
postsInBox, boxActor):
postsAddedToTimeline += 1
totalPostsCount += 1
- postUrlsInBox.append(url)
+ postUrlsInBox.append(postUrl)
else:
print('WARN: Unable to add post ' + postUrl +
' nickname ' + nickname +
@@ -3545,7 +3545,7 @@ def _createBoxIndexed(recentPostsCache: {},
postsInBox, boxActor):
postsAddedToTimeline += 1
totalPostsCount += 1
- postUrlsInBox.append(url)
+ postUrlsInBox.append(postUrl)
else:
print('WARN: Unable to add features post ' +
postUrl + ' nickname ' + nickname +
From fd90bfd231a8fb7fa3351e34171a89e978e6acd7 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 09:57:40 +0100
Subject: [PATCH 136/385] Test no longer needed
---
webapp_timeline.py | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/webapp_timeline.py b/webapp_timeline.py
index a4c158c77..f375272ba 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -900,11 +900,10 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
timelineStartTime, boxName, '12')
if currTlStr:
- if currTlStr not in tlStr:
- itemCtr += 1
- tlStr += textModeSeparator + currTlStr
- if separatorStr:
- tlStr += separatorStr
+ itemCtr += 1
+ tlStr += textModeSeparator + currTlStr
+ if separatorStr:
+ tlStr += separatorStr
if boxName == 'tlmedia':
tlStr += '\n'
From eaf30ff15b3713a60353f29c7096a2b3893288cb Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Mon, 6 Sep 2021 10:00:10 +0100
Subject: [PATCH 137/385] Apparently is needed
---
webapp_timeline.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/webapp_timeline.py b/webapp_timeline.py
index f375272ba..a4c158c77 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -900,10 +900,11 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
timelineStartTime, boxName, '12')
if currTlStr:
- itemCtr += 1
- tlStr += textModeSeparator + currTlStr
- if separatorStr:
- tlStr += separatorStr
+ if currTlStr not in tlStr:
+ itemCtr += 1
+ tlStr += textModeSeparator + currTlStr
+ if separatorStr:
+ tlStr += separatorStr
if boxName == 'tlmedia':
tlStr += '\n'
From 717c2c9c5814b7c07ad99d5ec6ccee3ddbe9f416 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 17:29:59 +0100
Subject: [PATCH 138/385] Reduce default peertube sites to a minimal set
---
webapp_media.py | 24 ++----------------------
1 file changed, 2 insertions(+), 22 deletions(-)
diff --git a/webapp_media.py b/webapp_media.py
index bf666063b..b83b298d1 100644
--- a/webapp_media.py
+++ b/webapp_media.py
@@ -115,36 +115,16 @@ def _addEmbeddedVideoFromSites(translate: {}, content: str,
# peertube sites.
peerTubeSites = peertubeInstances
else:
- # A default selection of the current larger peertube sites,
- # mostly French and German language.
- # These have only been semi-vetted, and so should be under
- # continuous review.
+ # A default minimal set of peertube instances
# Also see https://peertube_isolation.frama.io/list/ for
# adversarial instances. Nothing in that list should be
# in the defaults below.
peerTubeSites = ('share.tube',
- 'tube.22decembre.eu',
- 'libre.video',
- 'peertube.linuxrocks.online',
- 'spacepub.space',
- 'tube.tchncs.de',
- 'video.irem.univ-paris-diderot.fr',
- 'peertube.openstreetmap.fr',
- 'video.antopie.org',
- 'scitech.video',
- 'video.ploud.fr',
- 'diode.zone',
'visionon.tv',
'peertube.fr',
- 'peertube.live',
'kolektiva.media',
- 'betamax.video',
'peertube.social',
- 'videos.lescommuns.org',
- 'video.tedomum.net',
- 'tilvids.com',
- 'exode.me',
- 'peertube.video')
+ 'videos.lescommuns.org')
for site in peerTubeSites:
site = site.strip()
if not site:
From a79d2b191dd8dd6f1ec0158c436fcbc937eeabb6 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 18:05:26 +0100
Subject: [PATCH 139/385] Extra http code
---
session.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/session.py b/session.py
index 8851ff2a7..f7cdfe899 100644
--- a/session.py
+++ b/session.py
@@ -108,6 +108,8 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
print('WARN: getJson Not Found url: ' + url)
+ elif result.status_code == 410:
+ print('WARN: getJson no longer available url: ' + url)
else:
print('WARN: getJson url: ' + url +
' failed with error code ' +
From a1901609bfd08284a965adf3cebec1bc18966a70 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 20:09:41 +0100
Subject: [PATCH 140/385] date format
---
newswire.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/newswire.py b/newswire.py
index 15ca99516..2aae4b087 100644
--- a/newswire.py
+++ b/newswire.py
@@ -192,9 +192,9 @@ def parseFeedDate(pubDate: str) -> str:
formats = ("%a, %d %b %Y %H:%M:%S %z",
"%a, %d %b %Y %H:%M:%S EST",
"%a, %d %b %Y %H:%M:%S UT",
+ "%a, %d %b %Y %H:%M:%S GMT",
"%Y-%m-%dT%H:%M:%SZ",
"%Y-%m-%dT%H:%M:%S%z")
-
publishedDate = None
for dateFormat in formats:
if ',' in pubDate and ',' not in dateFormat:
@@ -207,6 +207,8 @@ def parseFeedDate(pubDate: str) -> str:
continue
if 'EST' not in pubDate and 'EST' in dateFormat:
continue
+ if 'GMT' not in pubDate and 'GMT' in dateFormat:
+ continue
if 'EST' in pubDate and 'EST' not in dateFormat:
continue
if 'UT' not in pubDate and 'UT' in dateFormat:
From a8ac86b758442f57a87c17d0faf790a6603ffc98 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 20:33:27 +0100
Subject: [PATCH 141/385] Showing warning about date format
---
newswire.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/newswire.py b/newswire.py
index 2aae4b087..607f9cd2e 100644
--- a/newswire.py
+++ b/newswire.py
@@ -220,8 +220,6 @@ def parseFeedDate(pubDate: str) -> str:
publishedDate = \
datetime.strptime(pubDate, dateFormat)
except BaseException:
- print('WARN: unrecognized date format: ' +
- pubDate + ' ' + dateFormat)
continue
if publishedDate:
@@ -240,6 +238,8 @@ def parseFeedDate(pubDate: str) -> str:
pubDateStr = str(publishedDate)
if not pubDateStr.endswith('+00:00'):
pubDateStr += '+00:00'
+ else:
+ print('WARN: unrecognized date format: ' + pubDate)
return pubDateStr
From 010961d4d1bbf38100a1fd32809ece8da5c77f39 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 21:33:47 +0100
Subject: [PATCH 142/385] Accomodation types
---
ontology/accommodationTypes.json | 989 +++++++++++++++++++++++++++++++
tests.py | 6 +
2 files changed, 995 insertions(+)
create mode 100644 ontology/accommodationTypes.json
diff --git a/ontology/accommodationTypes.json b/ontology/accommodationTypes.json
new file mode 100644
index 000000000..e65b4599b
--- /dev/null
+++ b/ontology/accommodationTypes.json
@@ -0,0 +1,989 @@
+{
+ "@context": {
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+ "dfc-b": "http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#",
+ "dfc-p": "http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#",
+ "dfc-t": "http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#",
+ "dfc-u": "http://static.datafoodconsortium.org/data/units.rdf#",
+ "dfc-p:specialize": {
+ "@type": "@id"
+ }
+ },
+ "@graph": [
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#EntirePlace",
+ "rdfs:label": [
+ {
+ "@value": "Entire Place",
+ "@language": "en"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "ar"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "ku"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "es"
+ },
+ {
+ "@value": "Posto intero",
+ "@language": "it"
+ },
+ {
+ "@value": "Gesamter Ort",
+ "@language": "de"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "sw"
+ },
+ {
+ "@value": "Lugar completo",
+ "@language": "pt"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "oc"
+ },
+ {
+ "@value": "Все место",
+ "@language": "ru"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "cy"
+ },
+ {
+ "@value": "集合場所",
+ "@language": "ja"
+ },
+ {
+ "@value": "Áit Eintire",
+ "@language": "ga"
+ },
+ {
+ "@value": "जगह",
+ "@language": "hi"
+ },
+ {
+ "@value": "入口",
+ "@language": "zh"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "fr"
+ },
+ {
+ "@value": "Entire Place",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#EntirePlace",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#PrivateRoom",
+ "rdfs:label": [
+ {
+ "@value": "Private Room",
+ "@language": "en"
+ },
+ {
+ "@value": "الغرفة الخاصة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Private Room",
+ "@language": "ku"
+ },
+ {
+ "@value": "Habitación privada",
+ "@language": "es"
+ },
+ {
+ "@value": "Stanza privata",
+ "@language": "it"
+ },
+ {
+ "@value": "Privatzimmer",
+ "@language": "de"
+ },
+ {
+ "@value": "Private Room",
+ "@language": "sw"
+ },
+ {
+ "@value": "Quarto privado",
+ "@language": "pt"
+ },
+ {
+ "@value": "Private Room",
+ "@language": "oc"
+ },
+ {
+ "@value": "Частная комната",
+ "@language": "ru"
+ },
+ {
+ "@value": "Private Room",
+ "@language": "cy"
+ },
+ {
+ "@value": "プライベートルーム",
+ "@language": "ja"
+ },
+ {
+ "@value": "Seomra na nDaoine",
+ "@language": "ga"
+ },
+ {
+ "@value": "निजी कक्ष",
+ "@language": "hi"
+ },
+ {
+ "@value": "私人会议室",
+ "@language": "zh"
+ },
+ {
+ "@value": "Salle privée",
+ "@language": "fr"
+ },
+ {
+ "@value": "Private Room",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#PrivateRoom",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#HotelRoom",
+ "rdfs:label": [
+ {
+ "@value": "Hotel Room",
+ "@language": "en"
+ },
+ {
+ "@value": "فندق",
+ "@language": "ar"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "ku"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "es"
+ },
+ {
+ "@value": "Camera dell'hotel",
+ "@language": "it"
+ },
+ {
+ "@value": "Hotelzimmer",
+ "@language": "de"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "sw"
+ },
+ {
+ "@value": "Quarto de Hotel",
+ "@language": "pt"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "oc"
+ },
+ {
+ "@value": "Номер в отеле",
+ "@language": "ru"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "cy"
+ },
+ {
+ "@value": "ホテル ルーム",
+ "@language": "ja"
+ },
+ {
+ "@value": "Seomra Óstán",
+ "@language": "ga"
+ },
+ {
+ "@value": "होटल",
+ "@language": "hi"
+ },
+ {
+ "@value": "旅馆",
+ "@language": "zh"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "fr"
+ },
+ {
+ "@value": "Hotel Room",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#HotelRoom",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#SharedRoom",
+ "rdfs:label": [
+ {
+ "@value": "Shared Room",
+ "@language": "en"
+ },
+ {
+ "@value": "الغرفة المشتركة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Shared Room",
+ "@language": "ku"
+ },
+ {
+ "@value": "Habitación compartida",
+ "@language": "es"
+ },
+ {
+ "@value": "Camera condivisa",
+ "@language": "it"
+ },
+ {
+ "@value": "Zimmer",
+ "@language": "de"
+ },
+ {
+ "@value": "Shared Room",
+ "@language": "sw"
+ },
+ {
+ "@value": "Quarto compartilhado",
+ "@language": "pt"
+ },
+ {
+ "@value": "Shared Room",
+ "@language": "oc"
+ },
+ {
+ "@value": "Общая комната",
+ "@language": "ru"
+ },
+ {
+ "@value": "Shared Room",
+ "@language": "cy"
+ },
+ {
+ "@value": "シェアルーム",
+ "@language": "ja"
+ },
+ {
+ "@value": "Seomra Comhroinnte",
+ "@language": "ga"
+ },
+ {
+ "@value": "साझा कक्ष",
+ "@language": "hi"
+ },
+ {
+ "@value": "共有会议室",
+ "@language": "zh"
+ },
+ {
+ "@value": "Salle partagée",
+ "@language": "fr"
+ },
+ {
+ "@value": "Shared Room",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#SharedRoom",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Sofa",
+ "rdfs:label": [
+ {
+ "@value": "Sofa",
+ "@language": "en"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "ar"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "ku"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "es"
+ },
+ {
+ "@value": "Divano",
+ "@language": "it"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "de"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "sw"
+ },
+ {
+ "@value": "Sofá",
+ "@language": "pt"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "oc"
+ },
+ {
+ "@value": "Диван",
+ "@language": "ru"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "cy"
+ },
+ {
+ "@value": "ソファ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Toir agus Crainn",
+ "@language": "ga"
+ },
+ {
+ "@value": "सोफा",
+ "@language": "hi"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "zh"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "fr"
+ },
+ {
+ "@value": "Sofa",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Sofa",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Boat",
+ "rdfs:label": [
+ {
+ "@value": "Boat",
+ "@language": "en"
+ },
+ {
+ "@value": "Boat",
+ "@language": "ar"
+ },
+ {
+ "@value": "Boat",
+ "@language": "ku"
+ },
+ {
+ "@value": "El barco",
+ "@language": "es"
+ },
+ {
+ "@value": "Barca",
+ "@language": "it"
+ },
+ {
+ "@value": "Boote",
+ "@language": "de"
+ },
+ {
+ "@value": "Boat",
+ "@language": "sw"
+ },
+ {
+ "@value": "Barco",
+ "@language": "pt"
+ },
+ {
+ "@value": "Boat",
+ "@language": "oc"
+ },
+ {
+ "@value": "Лодка",
+ "@language": "ru"
+ },
+ {
+ "@value": "Boat",
+ "@language": "cy"
+ },
+ {
+ "@value": "ボート",
+ "@language": "ja"
+ },
+ {
+ "@value": "taiseachas aeir: fliuch",
+ "@language": "ga"
+ },
+ {
+ "@value": "नाव",
+ "@language": "hi"
+ },
+ {
+ "@value": "B. 博塔",
+ "@language": "zh"
+ },
+ {
+ "@value": "Boat",
+ "@language": "fr"
+ },
+ {
+ "@value": "Boat",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Boat",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Barge",
+ "rdfs:label": [
+ {
+ "@value": "Barge",
+ "@language": "en"
+ },
+ {
+ "@value": "Barge",
+ "@language": "ar"
+ },
+ {
+ "@value": "Barge",
+ "@language": "ku"
+ },
+ {
+ "@value": "Barge",
+ "@language": "es"
+ },
+ {
+ "@value": "Barge",
+ "@language": "it"
+ },
+ {
+ "@value": "Barrel",
+ "@language": "de"
+ },
+ {
+ "@value": "Barge",
+ "@language": "sw"
+ },
+ {
+ "@value": "Barco",
+ "@language": "pt"
+ },
+ {
+ "@value": "Barge",
+ "@language": "oc"
+ },
+ {
+ "@value": "Барж",
+ "@language": "ru"
+ },
+ {
+ "@value": "Barge",
+ "@language": "cy"
+ },
+ {
+ "@value": "バージ",
+ "@language": "ja"
+ },
+ {
+ "@value": "Toir agus Crainn",
+ "@language": "ga"
+ },
+ {
+ "@value": "बार्ज",
+ "@language": "hi"
+ },
+ {
+ "@value": "律师协会",
+ "@language": "zh"
+ },
+ {
+ "@value": "Barge",
+ "@language": "fr"
+ },
+ {
+ "@value": "Barge",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Boat",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Tent",
+ "rdfs:label": [
+ {
+ "@value": "Tent",
+ "@language": "en"
+ },
+ {
+ "@value": "الخيمة",
+ "@language": "ar"
+ },
+ {
+ "@value": "Tent",
+ "@language": "ku"
+ },
+ {
+ "@value": "Tent",
+ "@language": "es"
+ },
+ {
+ "@value": "Tenda",
+ "@language": "it"
+ },
+ {
+ "@value": "Zelt",
+ "@language": "de"
+ },
+ {
+ "@value": "Tent",
+ "@language": "sw"
+ },
+ {
+ "@value": "Tenda",
+ "@language": "pt"
+ },
+ {
+ "@value": "Tent",
+ "@language": "oc"
+ },
+ {
+ "@value": "Тент",
+ "@language": "ru"
+ },
+ {
+ "@value": "Tent",
+ "@language": "cy"
+ },
+ {
+ "@value": "テント",
+ "@language": "ja"
+ },
+ {
+ "@value": "Tent",
+ "@language": "ga"
+ },
+ {
+ "@value": "टेंट",
+ "@language": "hi"
+ },
+ {
+ "@value": "答辩",
+ "@language": "zh"
+ },
+ {
+ "@value": "Tent",
+ "@language": "fr"
+ },
+ {
+ "@value": "Tent",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Tent",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Caravan",
+ "rdfs:label": [
+ {
+ "@value": "Caravan",
+ "@language": "en"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "ar"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "ku"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "es"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "it"
+ },
+ {
+ "@value": "Wohnwagen",
+ "@language": "de"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "sw"
+ },
+ {
+ "@value": "Caravana",
+ "@language": "pt"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "oc"
+ },
+ {
+ "@value": "Караван",
+ "@language": "ru"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "cy"
+ },
+ {
+ "@value": "キャラバン",
+ "@language": "ja"
+ },
+ {
+ "@value": "Amharc ar gach eolas",
+ "@language": "ga"
+ },
+ {
+ "@value": "कारवां",
+ "@language": "hi"
+ },
+ {
+ "@value": "车队",
+ "@language": "zh"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "fr"
+ },
+ {
+ "@value": "Caravan",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Caravan",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Hostel",
+ "rdfs:label": [
+ {
+ "@value": "Hostel",
+ "@language": "en"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "ar"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "ku"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "es"
+ },
+ {
+ "@value": "Ostello",
+ "@language": "it"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "de"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "sw"
+ },
+ {
+ "@value": "Albergue",
+ "@language": "pt"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "oc"
+ },
+ {
+ "@value": "Хостел",
+ "@language": "ru"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "cy"
+ },
+ {
+ "@value": "ホステル",
+ "@language": "ja"
+ },
+ {
+ "@value": "brú",
+ "@language": "ga"
+ },
+ {
+ "@value": "छात्रावास",
+ "@language": "hi"
+ },
+ {
+ "@value": "人质",
+ "@language": "zh"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "fr"
+ },
+ {
+ "@value": "Hostel",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Hostel",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Yurt",
+ "rdfs:label": [
+ {
+ "@value": "Yurt",
+ "@language": "en"
+ },
+ {
+ "@value": "يوت",
+ "@language": "ar"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "ku"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "es"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "it"
+ },
+ {
+ "@value": "Rind",
+ "@language": "de"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "sw"
+ },
+ {
+ "@value": "Yurt.",
+ "@language": "pt"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "oc"
+ },
+ {
+ "@value": "Юрт",
+ "@language": "ru"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "cy"
+ },
+ {
+ "@value": "ユルト",
+ "@language": "ja"
+ },
+ {
+ "@value": "taiseachas aeir: fliuch",
+ "@language": "ga"
+ },
+ {
+ "@value": "युर्ट",
+ "@language": "hi"
+ },
+ {
+ "@value": "导 言",
+ "@language": "zh"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "fr"
+ },
+ {
+ "@value": "Yurt",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Yurt",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#Tipi",
+ "rdfs:label": [
+ {
+ "@value": "Tipi",
+ "@language": "en"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "ar"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "ku"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "es"
+ },
+ {
+ "@value": "Tipi di",
+ "@language": "it"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "de"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "sw"
+ },
+ {
+ "@value": "Sugestões",
+ "@language": "pt"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "oc"
+ },
+ {
+ "@value": "Советы",
+ "@language": "ru"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "cy"
+ },
+ {
+ "@value": "ログイン",
+ "@language": "ja"
+ },
+ {
+ "@value": "An tSeapáin",
+ "@language": "ga"
+ },
+ {
+ "@value": "टीका",
+ "@language": "hi"
+ },
+ {
+ "@value": "注",
+ "@language": "zh"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "fr"
+ },
+ {
+ "@value": "Tipi",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#Tipi",
+ "@type": "dfc-p:ProductType"
+ },
+ {
+ "@id": "https://accommodation/data/accommodationTypes.rdf#RV",
+ "rdfs:label": [
+ {
+ "@value": "RV",
+ "@language": "en"
+ },
+ {
+ "@value": "RV",
+ "@language": "ar"
+ },
+ {
+ "@value": "RV",
+ "@language": "ku"
+ },
+ {
+ "@value": "RV",
+ "@language": "es"
+ },
+ {
+ "@value": "RV",
+ "@language": "it"
+ },
+ {
+ "@value": "RV",
+ "@language": "de"
+ },
+ {
+ "@value": "RV",
+ "@language": "sw"
+ },
+ {
+ "@value": "RV",
+ "@language": "pt"
+ },
+ {
+ "@value": "RV",
+ "@language": "oc"
+ },
+ {
+ "@value": "РВ",
+ "@language": "ru"
+ },
+ {
+ "@value": "RV",
+ "@language": "cy"
+ },
+ {
+ "@value": "RVの特長",
+ "@language": "ja"
+ },
+ {
+ "@value": "RV",
+ "@language": "ga"
+ },
+ {
+ "@value": "आरवी",
+ "@language": "hi"
+ },
+ {
+ "@value": "RV",
+ "@language": "zh"
+ },
+ {
+ "@value": "RV",
+ "@language": "fr"
+ },
+ {
+ "@value": "RV",
+ "@language": "ca"
+ }
+ ],
+ "dfc-p:specialize": "https://accommodation/data/accommodationTypes.rdf#RV",
+ "@type": "dfc-p:ProductType"
+ }
+ ]
+}
diff --git a/tests.py b/tests.py
index 2018886f1..5890d6228 100644
--- a/tests.py
+++ b/tests.py
@@ -1598,10 +1598,16 @@ def testSharedItemsFederation():
bobDir + '/ontology/toolTypes.json')
copyfile(baseDir + '/ontology/clothesTypes.json',
bobDir + '/ontology/clothesTypes.json')
+ copyfile(baseDir + '/ontology/medicalTypes.json',
+ bobDir + '/ontology/medicalTypes.json')
+ copyfile(baseDir + '/ontology/accommodationTypes.json',
+ bobDir + '/ontology/accommodationTypes.json')
assert os.path.isfile(bobDir + '/logo.png')
assert os.path.isfile(bobDir + '/ontology/foodTypes.json')
assert os.path.isfile(bobDir + '/ontology/toolTypes.json')
assert os.path.isfile(bobDir + '/ontology/clothesTypes.json')
+ assert os.path.isfile(bobDir + '/ontology/medicalTypes.json')
+ assert os.path.isfile(bobDir + '/ontology/accommodationTypes.json')
sharedItemName = 'cheddar'
sharedItemDescription = 'Some cheese'
sharedItemImageFilename = 'logo.png'
From 9aeab4555bfdef3fe9c564ee97c198effd33a468 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Tue, 7 Sep 2021 21:44:25 +0100
Subject: [PATCH 143/385] Translations for accommodation
---
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 ++-
17 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/translations/ar.json b/translations/ar.json
index 9523f6978..dcbd4d777 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "قم بإنشاء عنصر مطلوب جديد",
"Wanted Items Search": "البحث عن العناصر المطلوبة",
"Website": "موقع إلكتروني",
- "Low Bandwidth": "انخفاض النطاق الترددي"
+ "Low Bandwidth": "انخفاض النطاق الترددي",
+ "accommodation": "الإقامة"
}
diff --git a/translations/ca.json b/translations/ca.json
index f44884e41..e8fb1f099 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Creeu un element desitjat",
"Wanted Items Search": "Cerca d'articles desitjats",
"Website": "Lloc web",
- "Low Bandwidth": "Ample de banda baixa"
+ "Low Bandwidth": "Ample de banda baixa",
+ "accommodation": "allotjament"
}
diff --git a/translations/cy.json b/translations/cy.json
index 6f761d3e9..a896b147d 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Creu eitem newydd ei heisiau",
"Wanted Items Search": "Chwilio Eitemau Eisiau",
"Website": "Gwefan",
- "Low Bandwidth": "Lled band isel"
+ "Low Bandwidth": "Lled band isel",
+ "accommodation": "llety"
}
diff --git a/translations/de.json b/translations/de.json
index d7d3dd56b..9c1177fd8 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Erstelle einen neuen gesuchten Artikel",
"Wanted Items Search": "Gesuchte Artikel suchen",
"Website": "Webseite",
- "Low Bandwidth": "Niedrige Bandbreite"
+ "Low Bandwidth": "Niedrige Bandbreite",
+ "accommodation": "unterkunft"
}
diff --git a/translations/en.json b/translations/en.json
index d65ce0eed..195afa909 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Create a new wanted item",
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
- "Low Bandwidth": "Low Bandwidth"
+ "Low Bandwidth": "Low Bandwidth",
+ "accommodation": "accommodation"
}
diff --git a/translations/es.json b/translations/es.json
index 53c6fab3d..6d34af573 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Crea un nuevo artículo buscado",
"Wanted Items Search": "Búsqueda de artículos deseados",
"Website": "Sitio web",
- "Low Bandwidth": "Ancho de banda bajo"
+ "Low Bandwidth": "Ancho de banda bajo",
+ "accommodation": "alojamiento"
}
diff --git a/translations/fr.json b/translations/fr.json
index 70788283f..102915216 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Créer un nouvel article recherché",
"Wanted Items Search": "Recherche d'objets recherchés",
"Website": "Site Internet",
- "Low Bandwidth": "Bas débit"
+ "Low Bandwidth": "Bas débit",
+ "accommodation": "hébergement"
}
diff --git a/translations/ga.json b/translations/ga.json
index 97441e317..294542ffd 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Cruthaigh mír nua a theastaigh",
"Wanted Items Search": "Cuardaigh Míreanna Teastaíonn",
"Website": "Suíomh gréasáin",
- "Low Bandwidth": "Bandaleithead íseal"
+ "Low Bandwidth": "Bandaleithead íseal",
+ "accommodation": "lóistín"
}
diff --git a/translations/hi.json b/translations/hi.json
index 00e79e04b..e01413f12 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "एक नई वांछित वस्तु बनाएँ",
"Wanted Items Search": "वांटेड आइटम सर्च",
"Website": "वेबसाइट",
- "Low Bandwidth": "कम बैंडविड्थ"
+ "Low Bandwidth": "कम बैंडविड्थ",
+ "accommodation": "निवास स्थान"
}
diff --git a/translations/it.json b/translations/it.json
index 64aa96724..d9eedff96 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Crea un nuovo oggetto ricercato",
"Wanted Items Search": "Ricerca articoli ricercati",
"Website": "Sito web",
- "Low Bandwidth": "Bassa larghezza di banda"
+ "Low Bandwidth": "Bassa larghezza di banda",
+ "accommodation": "struttura ricettiva"
}
diff --git a/translations/ja.json b/translations/ja.json
index 2aef39f71..ab7bd3f6d 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "新しい欲しいアイテムを作成する",
"Wanted Items Search": "欲しいアイテム検索",
"Website": "Webサイト",
- "Low Bandwidth": "低帯域幅"
+ "Low Bandwidth": "低帯域幅",
+ "accommodation": "宿泊施設"
}
diff --git a/translations/ku.json b/translations/ku.json
index 2d52651c1..7bfb58c67 100644
--- a/translations/ku.json
+++ b/translations/ku.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Tiştek xwestî ya nû biafirînin",
"Wanted Items Search": "Wanted Items Search",
"Website": "Malper",
- "Low Bandwidth": "Bandwidth kêm"
+ "Low Bandwidth": "Bandwidth kêm",
+ "accommodation": "cih"
}
diff --git a/translations/oc.json b/translations/oc.json
index 191b28fb7..fc9e2efdb 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -470,5 +470,6 @@
"Create a new wanted item": "Create a new wanted item",
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
- "Low Bandwidth": "Low Bandwidth"
+ "Low Bandwidth": "Low Bandwidth",
+ "accommodation": "accommodation"
}
diff --git a/translations/pt.json b/translations/pt.json
index 4565aa6d0..7451b2f15 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Crie um novo item desejado",
"Wanted Items Search": "Pesquisa de Itens Desejados",
"Website": "Local na rede Internet",
- "Low Bandwidth": "Baixa largura de banda"
+ "Low Bandwidth": "Baixa largura de banda",
+ "accommodation": "alojamento"
}
diff --git a/translations/ru.json b/translations/ru.json
index 7731076b2..76b2e6c93 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Создать новый требуемый предмет",
"Wanted Items Search": "Поиск требуемых предметов",
"Website": "Интернет сайт",
- "Low Bandwidth": "Низкая пропускная способность"
+ "Low Bandwidth": "Низкая пропускная способность",
+ "accommodation": "размещение"
}
diff --git a/translations/sw.json b/translations/sw.json
index f12cc4e80..155b08db4 100644
--- a/translations/sw.json
+++ b/translations/sw.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "Unda kipengee kipya kinachotafutwa",
"Wanted Items Search": "Utafutaji wa Vitu vinavyotafutwa",
"Website": "Tovuti",
- "Low Bandwidth": "Bandwidth ya chini"
+ "Low Bandwidth": "Bandwidth ya chini",
+ "accommodation": "malazi"
}
diff --git a/translations/zh.json b/translations/zh.json
index 8d176593c..4ae5e750e 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -474,5 +474,6 @@
"Create a new wanted item": "创建一个新的通缉物品",
"Wanted Items Search": "通缉物品搜索",
"Website": "网站",
- "Low Bandwidth": "低带宽"
+ "Low Bandwidth": "低带宽",
+ "accommodation": "住所"
}
From d9cda147828c48350f130232e00a7c173483255b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 11:05:45 +0100
Subject: [PATCH 144/385] Signing GET requests
---
httpsig.py | 22 ++++++++++++++++------
session.py | 6 +-----
utils.py | 8 ++++++++
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index 96fc2d338..cd3819ef8 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -24,6 +24,7 @@ from time import gmtime, strftime
import datetime
from utils import getFullDomain
from utils import getSHA256
+from utils import getSHA512
from utils import localActorUrl
@@ -49,11 +50,12 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
if not dateStr:
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
- if nickname != domain:
- keyID = localActorUrl(httpPrefix, nickname, domain) + '#main-key'
+ if nickname != domain and nickname.lower() != 'actor':
+ keyID = localActorUrl(httpPrefix, nickname, domain)
else:
# instance actor
- keyID = httpPrefix + '://' + domain + '/actor#main-key'
+ keyID = httpPrefix + '://' + domain + '/actor'
+ keyID += '#main-key'
if not messageBodyJsonStr:
headers = {
'(request-target)': f'get {path}',
@@ -82,7 +84,8 @@ def signPostHeaders(dateStr: str, privateKeyPem: str,
signedHeaderText = ''
for headerKey in signedHeaderKeys:
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
- signedHeaderText = signedHeaderText.strip()
+ # strip the trailing linefeed
+ signedHeaderText = signedHeaderText.rstrip('\n')
# signedHeaderText.encode('ascii') matches
headerDigest = getSHA256(signedHeaderText.encode('ascii'))
# print('headerDigest2: ' + str(headerDigest))
@@ -159,11 +162,18 @@ def signPostHeadersNew(dateStr: str, privateKeyPem: str,
for headerKey in signedHeaderKeys:
signedHeaderText += f'{headerKey}: {headers[headerKey]}\n'
signedHeaderText = signedHeaderText.strip()
- headerDigest = getSHA256(signedHeaderText.encode('ascii'))
# Sign the digest. Potentially other signing algorithms can be added here.
signature = ''
- if algorithm == 'rsa-sha256':
+ if algorithm == 'rsa-sha512':
+ headerDigest = getSHA512(signedHeaderText.encode('ascii'))
+ rawSignature = key.sign(headerDigest,
+ padding.PKCS1v15(),
+ hazutils.Prehashed(hashes.SHA512()))
+ signature = base64.b64encode(rawSignature).decode('ascii')
+ else:
+ # default sha256
+ headerDigest = getSHA256(signedHeaderText.encode('ascii'))
rawSignature = key.sign(headerDigest,
padding.PKCS1v15(),
hazutils.Prehashed(hashes.SHA256()))
diff --git a/session.py b/session.py
index f7cdfe899..474f83bf0 100644
--- a/session.py
+++ b/session.py
@@ -175,11 +175,8 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
toPort = 443
else:
toPort = 80
- # instance actor
- nickname = domain
# if debug:
- print('Signed GET nickname: ' + nickname)
print('Signed GET domain: ' + domain + ' ' + str(port))
print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
print('Signed GET url: ' + url)
@@ -191,7 +188,7 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
else:
path = '/actor'
signatureHeaderJson = \
- createSignedHeader(signingPrivateKeyPem, nickname, domain, port,
+ createSignedHeader(signingPrivateKeyPem, 'actor', domain, port,
toDomain, toPort, path, httpPrefix, withDigest,
messageStr)
print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
@@ -201,7 +198,6 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
sessionHeaders[key.title()] = value
if sessionHeaders.get(key.lower()):
del sessionHeaders[key.lower()]
- sessionHeaders['Content-Length'] = '0'
print('Signed GET sessionHeaders ' + str(sessionHeaders))
return _getJsonRequest(session, url, domainFull, sessionHeaders,
diff --git a/utils.py b/utils.py
index 5dc621815..592ef0c25 100644
--- a/utils.py
+++ b/utils.py
@@ -147,6 +147,14 @@ def getSHA256(msg: str):
return digest.finalize()
+def getSHA512(msg: str):
+ """Returns a SHA512 hash of the given string
+ """
+ digest = hashes.Hash(hashes.SHA512(), backend=default_backend())
+ digest.update(msg)
+ return digest.finalize()
+
+
def _localNetworkHost(host: str) -> bool:
"""Returns true if the given host is on the local network
"""
From 32bdf5cd8065ee7282f6ec192b249a7b19cd373a Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 11:23:26 +0100
Subject: [PATCH 145/385] Tidying
---
tests.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests.py b/tests.py
index 5890d6228..672b4b12d 100644
--- a/tests.py
+++ b/tests.py
@@ -164,7 +164,6 @@ thrEve = None
def _testHttpSignedGET():
print('testHttpSignedGET')
- boxpath = '"/users/Actor HTTP/1.1"'
boxpath = "/users/Actor"
host = "epicyon.libreserver.org"
content_length = "0"
From 619d7d5aa0094268cb313942cd1a1bd69e836fb7 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 12:25:11 +0100
Subject: [PATCH 146/385] Sign get requests if an instance actor key exists
---
session.py | 38 +++++++++++++++++++-------------------
1 file changed, 19 insertions(+), 19 deletions(-)
diff --git a/session.py b/session.py
index 474f83bf0..5af67253e 100644
--- a/session.py
+++ b/session.py
@@ -95,15 +95,7 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- if not signingPrivateKeyPem:
- print("WARN: getJson requires secure fetch url: " + url)
- else:
- if debug:
- print('Using signed GET for domain: ' + domainFull)
- return _getJsonSigned(session, url, domainFull,
- sessionHeaders, sessionParams,
- timeoutSec, signingPrivateKeyPem,
- quiet, debug)
+ print("WARN: getJson" + url + ' rejected by secure mode')
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
@@ -176,11 +168,11 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
else:
toPort = 80
-# if debug:
- print('Signed GET domain: ' + domain + ' ' + str(port))
- print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
- print('Signed GET url: ' + url)
- print('Signed GET httpPrefix: ' + httpPrefix)
+ if debug:
+ print('Signed GET domain: ' + domain + ' ' + str(port))
+ print('Signed GET toDomain: ' + toDomain + ' ' + str(toPort))
+ print('Signed GET url: ' + url)
+ print('Signed GET httpPrefix: ' + httpPrefix)
messageStr = ''
withDigest = False
if toDomainFull + '/' in url:
@@ -191,14 +183,16 @@ def _getJsonSigned(session, url: str, domainFull: str, sessionHeaders: {},
createSignedHeader(signingPrivateKeyPem, 'actor', domain, port,
toDomain, toPort, path, httpPrefix, withDigest,
messageStr)
- print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
+ if debug:
+ print('Signed GET signatureHeaderJson ' + str(signatureHeaderJson))
for key, value in signatureHeaderJson.items():
if key.startswith('(') or key.startswith('*('):
continue
sessionHeaders[key.title()] = value
if sessionHeaders.get(key.lower()):
del sessionHeaders[key.lower()]
- print('Signed GET sessionHeaders ' + str(sessionHeaders))
+ if debug:
+ print('Signed GET sessionHeaders ' + str(sessionHeaders))
return _getJsonRequest(session, url, domainFull, sessionHeaders,
sessionParams, timeoutSec, None, quiet, debug)
@@ -232,9 +226,15 @@ def getJson(signingPrivateKeyPem: str,
if debug:
HTTPConnection.debuglevel = 1
- return _getJsonRequest(session, url, domain, sessionHeaders,
- sessionParams, timeoutSec,
- signingPrivateKeyPem, quiet, debug)
+ if signingPrivateKeyPem:
+ return _getJsonSigned(session, url, domain,
+ sessionHeaders, sessionParams,
+ timeoutSec, signingPrivateKeyPem,
+ quiet, debug)
+ else:
+ return _getJsonRequest(session, url, domain, sessionHeaders,
+ sessionParams, timeoutSec,
+ None, quiet, debug)
def postJson(httpPrefix: str, domainFull: str,
From dd9a02ad2bec14b2d9a8df442825b20a09344795 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 12:46:13 +0100
Subject: [PATCH 147/385] Change terminology to be the same as in Mastodon
documentation: 'secure mode' rather than 'authorized fetch'
---
daemon.py | 43 +++++++++++++++++++++----------------------
epicyon.py | 9 +++++----
2 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/daemon.py b/daemon.py
index 038a27be1..d1480641e 100644
--- a/daemon.py
+++ b/daemon.py
@@ -594,10 +594,10 @@ class PubServer(BaseHTTPRequestHandler):
return False
return True
- def _authorizedFetch(self) -> bool:
+ def _secureMode(self) -> bool:
"""http authentication of GET requests for json
"""
- if not self.server.authorizedFetch:
+ if not self.server.secureMode:
return True
signature = None
@@ -609,7 +609,7 @@ class PubServer(BaseHTTPRequestHandler):
# check that the headers are signed
if not signature:
if self.server.debug:
- print('WARN: authorized fetch, ' +
+ print('AUTH: secure mode, ' +
'GET has no signature in headers')
return False
@@ -623,7 +623,7 @@ class PubServer(BaseHTTPRequestHandler):
break
if not keyId:
if self.server.debug:
- print('WARN: authorized fetch, ' +
+ print('AUTH: secure mode, ' +
'failed to obtain keyId from signature')
return False
@@ -634,8 +634,7 @@ class PubServer(BaseHTTPRequestHandler):
# is the keyId (actor) valid?
if not urlPermitted(keyId, self.server.federationList):
if self.server.debug:
- print('Authorized fetch failed: ' + keyId +
- ' is not permitted')
+ print('AUTH: Secure mode GET request not permitted: ' + keyId)
return False
# make sure we have a session
@@ -644,7 +643,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.session = createSession(self.server.proxyType)
if not self.server.session:
print('ERROR: GET failed to create session during ' +
- 'authorized fetch')
+ 'secure mode')
return False
# obtain the public key
@@ -656,7 +655,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.signingPrivateKeyPem)
if not pubKey:
if self.server.debug:
- print('DEBUG: Authorized fetch failed to ' +
+ print('AUTH: secure mode failed to ' +
'obtain public key for ' + keyId)
return False
@@ -666,7 +665,7 @@ class PubServer(BaseHTTPRequestHandler):
return True
if self.server.debug:
- print('Authorized fetch failed for ' + keyId)
+ print('AUTH: secure mode authorization failed for ' + keyId)
return False
def _login_headers(self, fileFormat: str, length: int,
@@ -8164,7 +8163,7 @@ class PubServer(BaseHTTPRequestHandler):
cookie, callingDomain, False)
self._write(msg)
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(repliesJson, ensure_ascii=False)
msg = msg.encode('utf-8')
protocolStr = 'application/json'
@@ -8258,7 +8257,7 @@ class PubServer(BaseHTTPRequestHandler):
'individual post done',
'post replies done')
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(repliesJson,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -8361,7 +8360,7 @@ class PubServer(BaseHTTPRequestHandler):
'post replies done',
'show roles')
else:
- if self._authorizedFetch():
+ if self._secureMode():
rolesList = getActorRolesList(actorJson)
msg = json.dumps(rolesList,
ensure_ascii=False)
@@ -8469,7 +8468,7 @@ class PubServer(BaseHTTPRequestHandler):
'post roles done',
'show skills')
else:
- if self._authorizedFetch():
+ if self._secureMode():
actorSkillsList = \
getOccupationSkills(actorJson)
skills = getSkillsFromList(actorSkillsList)
@@ -8605,7 +8604,7 @@ class PubServer(BaseHTTPRequestHandler):
'done',
'show status')
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(postJsonObject,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -10079,7 +10078,7 @@ class PubServer(BaseHTTPRequestHandler):
'show events done',
'show outbox')
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(outboxFeed,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -10323,7 +10322,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return True
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(shares,
ensure_ascii=False)
msg = msg.encode('utf-8')
@@ -10440,7 +10439,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 3')
return True
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(following,
ensure_ascii=False).encode('utf-8')
msglen = len(msg)
@@ -10557,7 +10556,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 4')
return True
else:
- if self._authorizedFetch():
+ if self._secureMode():
msg = json.dumps(followers,
ensure_ascii=False).encode('utf-8')
msglen = len(msg)
@@ -10693,7 +10692,7 @@ class PubServer(BaseHTTPRequestHandler):
'show profile 4 done',
'show profile posts')
else:
- if self._authorizedFetch():
+ if self._secureMode():
acceptStr = self.headers['Accept']
msgStr = json.dumps(actorJson, ensure_ascii=False)
msg = msgStr.encode('utf-8')
@@ -14286,7 +14285,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return
- if not self._authorizedFetch():
+ if not self._secureMode():
if self.server.debug:
print('WARN: Unauthorized GET')
self._404()
@@ -16289,7 +16288,7 @@ def runDaemon(lowBandwidth: bool,
httpPrefix: str = 'https',
fedList: [] = [],
maxMentions: int = 10, maxEmoji: int = 10,
- authorizedFetch: bool = False,
+ secureMode: bool = False,
proxyType: str = None, maxReplies: int = 64,
domainMaxPostsPerDay: int = 8640,
accountMaxPostsPerDay: int = 864,
@@ -16510,7 +16509,7 @@ def runDaemon(lowBandwidth: bool,
httpd.outboxThread = {}
httpd.newPostThread = {}
httpd.projectVersion = projectVersion
- httpd.authorizedFetch = authorizedFetch
+ httpd.secureMode = secureMode
# max POST size of 30M
httpd.maxPostLength = 1024 * 1024 * 30
httpd.maxMediaSize = httpd.maxPostLength
diff --git a/epicyon.py b/epicyon.py
index f2c9afbbb..c9922ac33 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -407,10 +407,11 @@ parser.add_argument("--debug", type=str2bool, nargs='?',
parser.add_argument("--notificationSounds", type=str2bool, nargs='?',
const=True, default=True,
help="Play notification sounds")
-parser.add_argument("--authorizedFetch", type=str2bool, nargs='?',
+parser.add_argument("--secureMode", type=str2bool, nargs='?',
const=True, default=False,
- help="Enable authorization on GET requests" +
- " for json (authorized fetch)")
+ help="Requires all GET requests to be signed, " +
+ "so that the sender can be identifies and " +
+ "blocked if neccessary")
parser.add_argument("--instanceOnlySkillsSearch", type=str2bool, nargs='?',
const=True, default=False,
help="Skills searches only return " +
@@ -2993,7 +2994,7 @@ if __name__ == "__main__":
args.YTReplacementDomain,
port, proxyPort, httpPrefix,
federationList, args.maxMentions,
- args.maxEmoji, args.authorizedFetch,
+ args.maxEmoji, args.secureMode,
proxyType, args.maxReplies,
args.domainMaxPostsPerDay,
args.accountMaxPostsPerDay,
From ac128bb122b51ba0b701e60af698ada757fb403e Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:01:59 +0100
Subject: [PATCH 148/385] Only get instance actor key in secure mode
---
daemon.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index d1480641e..d3ba03443 100644
--- a/daemon.py
+++ b/daemon.py
@@ -16737,7 +16737,8 @@ def runDaemon(lowBandwidth: bool,
# signing key used for authorized fetch
# this is the instance actor private key
- httpd.signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ if secureMode:
+ httpd.signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
if not unitTest:
print('Creating inbox queue watchdog')
From 26d71b93328504f9dcfc4b634be4a6692095f082 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:07:57 +0100
Subject: [PATCH 149/385] Check for secure mode when getting instance actor key
---
epicyon.py | 144 +++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 108 insertions(+), 36 deletions(-)
diff --git a/epicyon.py b/epicyon.py
index c9922ac33..38959d903 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -690,7 +690,9 @@ if args.posts:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getPublicPostsOfPerson(baseDir, nickname, domain, False, True,
proxyType, args.port, httpPrefix, debug,
__version__, args.language,
@@ -726,7 +728,9 @@ if args.postDomains:
domainList = []
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
domainList = getPublicPostDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -771,7 +775,9 @@ if args.postDomainsBlocked:
domainList = []
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
domainList = getPublicPostDomainsBlocked(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -814,7 +820,9 @@ if args.checkDomains:
maxBlockedDomains = 0
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
checkDomains(None,
baseDir, nickname, domain,
proxyType, args.port,
@@ -837,7 +845,9 @@ if args.socnet:
args.language = 'en'
if not args.domain:
args.domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
dotGraph = instancesGraph(baseDir, args.socnet,
proxyType, args.port,
httpPrefix, debug,
@@ -868,7 +878,9 @@ if args.postsraw:
proxyType = 'gnunet'
if not args.language:
args.language = 'en'
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
getPublicPostsOfPerson(baseDir, nickname, domain, False, False,
proxyType, args.port, httpPrefix, debug,
__version__, args.language,
@@ -883,7 +895,9 @@ if args.json:
}
if not args.domain:
args.domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, args.domain)
if debug:
print('baseDir: ' + str(baseDir))
if signingPrivateKeyPem:
@@ -1102,7 +1116,9 @@ if args.approve:
personCache = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualApproveFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1128,7 +1144,9 @@ if args.deny:
personCache = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
manualDenyFollowRequest(session, baseDir,
httpPrefix,
args.nickname, domain, port,
@@ -1219,7 +1237,9 @@ if args.message:
isArticle = False
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending post to ' + args.sendto)
sendPostViaServer(signingPrivateKeyPem, __version__,
@@ -1254,7 +1274,9 @@ if args.announce:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending announce/repeat of ' + args.announce)
sendAnnounceViaServer(baseDir, session, args.nickname, args.password,
@@ -1296,7 +1318,9 @@ if args.box:
proxyType = 'gnunet'
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
session = createSession(proxyType)
boxJson = c2sBoxJson(baseDir, session,
@@ -1355,7 +1379,9 @@ if args.itemName:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending shared item: ' + args.itemName)
sendShareViaServer(baseDir, session,
@@ -1396,7 +1422,9 @@ if args.undoItemName:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of shared item: ' + args.undoItemName)
sendUndoShareViaServer(baseDir, session,
@@ -1456,7 +1484,9 @@ if args.wantedItemName:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending wanted item: ' + args.wantedItemName)
sendWantedViaServer(baseDir, session,
@@ -1497,7 +1527,9 @@ if args.undoWantedItemName:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo of wanted item: ' + args.undoWantedItemName)
sendUndoWantedViaServer(baseDir, session,
@@ -1529,7 +1561,9 @@ if args.like:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending like of ' + args.like)
sendLikeViaServer(baseDir, session,
@@ -1560,7 +1594,9 @@ if args.undolike:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo like of ' + args.undolike)
sendUndoLikeViaServer(baseDir, session,
@@ -1592,7 +1628,9 @@ if args.bookmark:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending bookmark of ' + args.bookmark)
sendBookmarkViaServer(baseDir, session,
@@ -1624,7 +1662,9 @@ if args.unbookmark:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo bookmark of ' + args.unbookmark)
sendUndoBookmarkViaServer(baseDir, session,
@@ -1655,7 +1695,9 @@ if args.delete:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending delete request of ' + args.delete)
sendDeleteViaServer(baseDir, session,
@@ -1698,7 +1740,9 @@ if args.follow:
followHttpPrefix = 'https'
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendFollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1742,7 +1786,9 @@ if args.unfollow:
followHttpPrefix = 'https'
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
sendUnfollowRequestViaServer(baseDir, session,
args.nickname, args.password,
@@ -1775,7 +1821,9 @@ if args.followingList:
followHttpPrefix = httpPrefix
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followingJson = \
getFollowingViaServer(baseDir, session,
@@ -1806,7 +1854,9 @@ if args.followersList:
followHttpPrefix = httpPrefix
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followersJson = \
getFollowersViaServer(baseDir, session,
@@ -1838,7 +1888,9 @@ if args.followRequestsList:
followHttpPrefix = httpPrefix
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followRequestsJson = \
getFollowRequestsViaServer(baseDir, session,
@@ -1888,7 +1940,9 @@ if args.migrations:
session = createSession(proxyType)
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
ctr = migrateAccounts(baseDir, session,
httpPrefix, cachedWebfingers,
True, signingPrivateKeyPem)
@@ -1901,7 +1955,9 @@ if args.migrations:
if args.actor:
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
if debug:
print('baseDir: ' + str(baseDir))
if signingPrivateKeyPem:
@@ -1985,7 +2041,9 @@ if args.followers:
nickname = domain
handle = nickname + '@' + domain
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
None, __version__, debug, False,
@@ -2031,7 +2089,9 @@ if args.followers:
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
}
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
followersList = \
downloadFollowCollection(signingPrivateKeyPem,
'followers', session,
@@ -2286,7 +2346,9 @@ if args.skill:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending ' + args.skill + ' skill level ' +
str(args.skillLevelPercent) + ' for ' + nickname)
@@ -2319,7 +2381,9 @@ if args.availability:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending availability status of ' + nickname +
' as ' + args.availability)
@@ -2431,7 +2495,9 @@ if args.block:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending block of ' + args.block)
sendBlockViaServer(baseDir, session, nickname, args.password,
@@ -2461,7 +2527,9 @@ if args.mute:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending mute of ' + args.mute)
sendMuteViaServer(baseDir, session, nickname, args.password,
@@ -2491,7 +2559,9 @@ if args.unmute:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo mute of ' + args.unmute)
sendUndoMuteViaServer(baseDir, session, nickname, args.password,
@@ -2533,7 +2603,9 @@ if args.unblock:
cachedWebfingers = {}
if not domain:
domain = getConfigParam(baseDir, 'domain')
- signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
+ signingPrivateKeyPem = None
+ if args.secureMode:
+ signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
print('Sending undo block of ' + args.unblock)
sendUndoBlockViaServer(baseDir, session, nickname, args.password,
From 7df4fa173bd66b0644e1b697c26ec40a77cc9334 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:19:01 +0100
Subject: [PATCH 150/385] Accept jrd
---
httpsig.py | 4 +++-
tests.py | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/httpsig.py b/httpsig.py
index cd3819ef8..1b08cfa0c 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -209,7 +209,9 @@ def createSignedHeader(privateKeyPem: str, nickname: str,
dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime())
if not withDigest:
- contentType = 'application/activity+json, application/ld+json'
+ contentType = \
+ 'application/activity+json, ' + \
+ 'application/ld+json, application/jrd+json'
headers = {
'(request-target)': f'get {path}',
'host': headerDomain,
diff --git a/tests.py b/tests.py
index 672b4b12d..d4b379ef1 100644
--- a/tests.py
+++ b/tests.py
@@ -170,7 +170,8 @@ def _testHttpSignedGET():
user_agent = "http.rb/4.4.1 (Mastodon/3.4.1; +https://octodon.social/)"
dateStr = 'Wed, 01 Sep 2021 16:11:10 GMT'
accept_encoding = 'gzip'
- accept = 'application/activity+json, application/ld+json'
+ accept = \
+ 'application/activity+json, application/ld+json, application/jrd+json'
signature = \
'keyId="https://octodon.social/actor#main-key",' + \
'algorithm="rsa-sha256",' + \
From 674b75b29e232f50a23489cd1f89db8da642d8ae Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:27:59 +0100
Subject: [PATCH 151/385] Set host domain
---
person.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/person.py b/person.py
index a45d09a11..8fa404ec8 100644
--- a/person.py
+++ b/person.py
@@ -1337,7 +1337,7 @@ def getActorJson(hostDomain: str, handle: str, http: bool, gnunet: bool,
handle = nickname + '@' + domain
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
- None, __version__, debug,
+ hostDomain, __version__, debug,
groupAccount, signingPrivateKeyPem)
if not wfRequest:
if not quiet:
From aa7edaa2c4dfe25d89b58aec84ff06722e8570c8 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:34:13 +0100
Subject: [PATCH 152/385] Set domain for webfinger
---
epicyon.py | 5 ++++-
migrate.py | 2 +-
posts.py | 2 +-
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/epicyon.py b/epicyon.py
index 38959d903..496cc8df0 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -2040,13 +2040,16 @@ if args.followers:
if nickname == 'inbox':
nickname = domain
+ hostDomain = None
+ if args.domain:
+ hostDomain = args.domain
handle = nickname + '@' + domain
signingPrivateKeyPem = None
if args.secureMode:
signingPrivateKeyPem = getInstanceActorKey(baseDir, domain)
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
- None, __version__, debug, False,
+ hostDomain, __version__, debug, False,
signingPrivateKeyPem)
if not wfRequest:
print('Unable to webfinger ' + handle)
diff --git a/migrate.py b/migrate.py
index 4f355f718..33c5143b8 100644
--- a/migrate.py
+++ b/migrate.py
@@ -60,7 +60,7 @@ def _updateMovedHandle(baseDir: str, nickname: str, domain: str,
handle = handle[1:]
wfRequest = webfingerHandle(session, handle,
httpPrefix, cachedWebfingers,
- None, __version__, debug, False,
+ domain, __version__, debug, False,
signingPrivateKeyPem)
if not wfRequest:
print('updateMovedHandle unable to webfinger ' + handle)
diff --git a/posts.py b/posts.py
index 51d96c781..408490da9 100644
--- a/posts.py
+++ b/posts.py
@@ -2810,7 +2810,7 @@ def _hasSharedInbox(session, httpPrefix: str, domain: str,
tryHandles.append('inbox@' + domain)
for handle in tryHandles:
wfRequest = webfingerHandle(session, handle, httpPrefix, {},
- None, __version__, debug, False,
+ domain, __version__, debug, False,
signingPrivateKeyPem)
if wfRequest:
if isinstance(wfRequest, dict):
From 3cc5e1445c231b4d1662f009e002b23ae454c98b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:38:22 +0100
Subject: [PATCH 153/385] Space
---
session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/session.py b/session.py
index 5af67253e..317042a42 100644
--- a/session.py
+++ b/session.py
@@ -95,7 +95,7 @@ def _getJsonRequest(session, url: str, domainFull: str, sessionHeaders: {},
params=sessionParams, timeout=timeoutSec)
if result.status_code != 200:
if result.status_code == 401:
- print("WARN: getJson" + url + ' rejected by secure mode')
+ print("WARN: getJson " + url + ' rejected by secure mode')
elif result.status_code == 403:
print('WARN: getJson Forbidden url: ' + url)
elif result.status_code == 404:
From f84149cd3a6d861597569090979a3a2a61acfde6 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 13:44:50 +0100
Subject: [PATCH 154/385] Fix test
---
tests.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests.py b/tests.py
index d4b379ef1..9519156ed 100644
--- a/tests.py
+++ b/tests.py
@@ -171,7 +171,7 @@ def _testHttpSignedGET():
dateStr = 'Wed, 01 Sep 2021 16:11:10 GMT'
accept_encoding = 'gzip'
accept = \
- 'application/activity+json, application/ld+json, application/jrd+json'
+ 'application/activity+json, application/ld+json'
signature = \
'keyId="https://octodon.social/actor#main-key",' + \
'algorithm="rsa-sha256",' + \
From 9601220e66346ed48796678c5efb0e1c9b467473 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 19:37:04 +0100
Subject: [PATCH 155/385] Implement reply interval in hours
The time after publication of a post during which replies are permitted
---
daemon.py | 32 +++++++++++++++++++--
epicyon.py | 8 +++++-
inbox.py | 39 +++++++++++++++++++------
tests.py | 16 ++++++++---
translations/ar.json | 4 ++-
translations/ca.json | 4 ++-
translations/cy.json | 4 ++-
translations/de.json | 4 ++-
translations/en.json | 4 ++-
translations/es.json | 4 ++-
translations/fr.json | 4 ++-
translations/ga.json | 4 ++-
translations/hi.json | 4 ++-
translations/it.json | 4 ++-
translations/ja.json | 4 ++-
translations/ku.json | 4 ++-
translations/oc.json | 4 ++-
translations/pt.json | 4 ++-
translations/ru.json | 4 ++-
translations/sw.json | 4 ++-
translations/zh.json | 4 ++-
utils.py | 68 ++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 199 insertions(+), 32 deletions(-)
diff --git a/daemon.py b/daemon.py
index d3ba03443..0fb40703e 100644
--- a/daemon.py
+++ b/daemon.py
@@ -232,6 +232,7 @@ from categories import updateHashtagCategories
from languages import getActorLanguages
from languages import setActorLanguages
from like import updateLikesCollection
+from utils import canReplyTo
from utils import isDM
from utils import replaceUsersWithAt
from utils import localActorUrl
@@ -857,6 +858,14 @@ class PubServer(BaseHTTPRequestHandler):
'This is nothing less ' +
'than an utter triumph')
+ def _403(self) -> None:
+ if self.server.translate:
+ self._httpReturnCode(403, self.server.translate['Forbidden'],
+ self.server.translate["You're not allowed"])
+ else:
+ self._httpReturnCode(403, 'Forbidden',
+ "You're not allowed")
+
def _404(self) -> None:
if self.server.translate:
self._httpReturnCode(404, self.server.translate['Not Found'],
@@ -11287,6 +11296,18 @@ class PubServer(BaseHTTPRequestHandler):
if isNewPostEndpoint:
nickname = getNicknameFromActor(path)
+ if inReplyToUrl:
+ replyIntervalHours = self.server.defaultReplyIntervalHours
+ if not canReplyTo(baseDir, nickname, domain,
+ inReplyToUrl, replyIntervalHours):
+ print('Reply outside of time window ' + inReplyToUrl)
+ self._403()
+ self.server.GETbusy = False
+ return True
+ elif self.server.debug:
+ print('Reply is within time interval: ' +
+ str(replyIntervalHours) + ' hours')
+
accessKeys = self.server.accessKeys
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
@@ -16245,7 +16266,8 @@ def loadTokens(baseDir: str, tokensDict: {}, tokensLookup: {}) -> None:
break
-def runDaemon(lowBandwidth: bool,
+def runDaemon(defaultReplyIntervalHours: int,
+ lowBandwidth: bool,
maxLikeCount: int,
sharedItemsFederatedDomains: [],
userAgentsBlocked: [],
@@ -16376,6 +16398,11 @@ def runDaemon(lowBandwidth: bool,
'Public': 'p',
'Reminder': 'r'
}
+
+ # how many hours after a post was publushed can a reply be made
+ defaultReplyIntervalHours = 9999999
+ httpd.defaultReplyIntervalHours = defaultReplyIntervalHours
+
httpd.keyShortcuts = {}
loadAccessKeysForAccounts(baseDir, httpd.keyShortcuts, httpd.accessKeys)
@@ -16704,7 +16731,8 @@ def runDaemon(lowBandwidth: bool,
httpd.themeName,
httpd.systemLanguage,
httpd.maxLikeCount,
- httpd.signingPrivateKeyPem), daemon=True)
+ httpd.signingPrivateKeyPem,
+ httpd.defaultReplyIntervalHours), daemon=True)
print('Creating scheduled post thread')
httpd.thrPostSchedule = \
diff --git a/epicyon.py b/epicyon.py
index 496cc8df0..4953492ad 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -170,6 +170,11 @@ parser.add_argument('--dormantMonths',
default=3,
help='How many months does a followed account need to ' +
'be unseen for before being considered dormant')
+parser.add_argument('--defaultReplyIntervalHours',
+ dest='defaultReplyIntervalHours', type=int,
+ default=9999999999,
+ help='How many hours after publication of a post ' +
+ 'are replies to it permitted')
parser.add_argument('--sendThreadsTimeoutMins',
dest='sendThreadsTimeoutMins', type=int,
default=30,
@@ -3031,7 +3036,8 @@ if args.defaultCurrency:
print('Default currency set to ' + args.defaultCurrency)
if __name__ == "__main__":
- runDaemon(args.lowBandwidth, args.maxLikeCount,
+ runDaemon(args.defaultReplyIntervalHours,
+ args.lowBandwidth, args.maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
args.logLoginFailures,
diff --git a/inbox.py b/inbox.py
index 5161f4ca8..002721d7d 100644
--- a/inbox.py
+++ b/inbox.py
@@ -15,6 +15,8 @@ import random
from linked_data_sig import verifyJsonSignature
from languages import understoodPostLanguage
from like import updateLikesCollection
+from utils import getReplyIntervalHours
+from utils import canReplyTo
from utils import getUserPaths
from utils import getBaseContentFromPost
from utils import acctDir
@@ -2484,7 +2486,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
lastBounceMessage: [],
themeName: str, systemLanguage: str,
maxLikeCount: int,
- signingPrivateKeyPem: str) -> bool:
+ signingPrivateKeyPem: str,
+ defaultReplyIntervalHours: int) -> bool:
""" Anything which needs to be done after initial checks have passed
"""
actor = keyId
@@ -2765,11 +2768,29 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
if isinstance(inReplyTo, str):
if not isMuted(baseDir, nickname, domain,
inReplyTo, conversationId):
- actUrl = \
- localActorUrl(httpPrefix,
- nickname, domain)
- _replyNotify(baseDir, handle,
- actUrl + '/tlreplies')
+ # check if the reply is within the allowed
+ # time period after publication
+ hrs = defaultReplyIntervalHours
+ replyIntervalHours = \
+ getReplyIntervalHours(baseDir,
+ nickname,
+ domain, hrs)
+ if canReplyTo(baseDir, nickname, domain,
+ inReplyTo,
+ replyIntervalHours):
+ actUrl = \
+ localActorUrl(httpPrefix,
+ nickname, domain)
+ _replyNotify(baseDir, handle,
+ actUrl + '/tlreplies')
+ else:
+ if debug:
+ print('Reply to ' + inReplyTo +
+ ' is outside of the ' +
+ 'permitted interval of ' +
+ str(replyIntervalHours) +
+ ' hours')
+ return False
else:
isReplyToMutedPost = True
@@ -3119,7 +3140,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
peertubeInstances: [],
verifyAllSignatures: bool,
themeName: str, systemLanguage: str,
- maxLikeCount: int, signingPrivateKeyPem: str) -> None:
+ maxLikeCount: int, signingPrivateKeyPem: str,
+ defaultReplyIntervalHours: int) -> None:
"""Processes received items and moves them to the appropriate
directories
"""
@@ -3534,7 +3556,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
lastBounceMessage,
themeName, systemLanguage,
maxLikeCount,
- signingPrivateKeyPem)
+ signingPrivateKeyPem,
+ defaultReplyIntervalHours)
if debug:
pprint(queueJson['post'])
print('Queue: Queue post accepted')
diff --git a/tests.py b/tests.py
index 9519156ed..fe0a63615 100644
--- a/tests.py
+++ b/tests.py
@@ -649,8 +649,10 @@ def createServerAlice(path: str, domain: str, port: int,
logLoginFailures = False
userAgentsBlocked = []
maxLikeCount = 10
+ defaultReplyIntervalHours = 9999999999
print('Server running: Alice')
- runDaemon(lowBandwidth, maxLikeCount,
+ runDaemon(defaultReplyIntervalHours,
+ lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@@ -785,8 +787,10 @@ def createServerBob(path: str, domain: str, port: int,
logLoginFailures = False
userAgentsBlocked = []
maxLikeCount = 10
+ defaultReplyIntervalHours = 9999999999
print('Server running: Bob')
- runDaemon(lowBandwidth, maxLikeCount,
+ runDaemon(defaultReplyIntervalHours,
+ lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@@ -850,8 +854,10 @@ def createServerEve(path: str, domain: str, port: int, federationList: [],
userAgentsBlocked = []
maxLikeCount = 10
lowBandwidth = True
+ defaultReplyIntervalHours = 9999999999
print('Server running: Eve')
- runDaemon(lowBandwidth, maxLikeCount,
+ runDaemon(defaultReplyIntervalHours,
+ lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
@@ -917,8 +923,10 @@ def createServerGroup(path: str, domain: str, port: int,
userAgentsBlocked = []
maxLikeCount = 10
lowBandwidth = True
+ defaultReplyIntervalHours = 9999999999
print('Server running: Group')
- runDaemon(lowBandwidth, maxLikeCount,
+ runDaemon(defaultReplyIntervalHours,
+ lowBandwidth, maxLikeCount,
sharedItemsFederatedDomains,
userAgentsBlocked,
logLoginFailures, city,
diff --git a/translations/ar.json b/translations/ar.json
index dcbd4d777..dfc32a780 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "البحث عن العناصر المطلوبة",
"Website": "موقع إلكتروني",
"Low Bandwidth": "انخفاض النطاق الترددي",
- "accommodation": "الإقامة"
+ "accommodation": "الإقامة",
+ "Forbidden": "محرم",
+ "You're not allowed": "كنت لا يسمح"
}
diff --git a/translations/ca.json b/translations/ca.json
index e8fb1f099..8f73ec075 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Cerca d'articles desitjats",
"Website": "Lloc web",
"Low Bandwidth": "Ample de banda baixa",
- "accommodation": "allotjament"
+ "accommodation": "allotjament",
+ "Forbidden": "Prohibit",
+ "You're not allowed": "No està permès"
}
diff --git a/translations/cy.json b/translations/cy.json
index a896b147d..abb119458 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Chwilio Eitemau Eisiau",
"Website": "Gwefan",
"Low Bandwidth": "Lled band isel",
- "accommodation": "llety"
+ "accommodation": "llety",
+ "Forbidden": "Wedi'i wahardd",
+ "You're not allowed": "Ni chaniateir i chi"
}
diff --git a/translations/de.json b/translations/de.json
index 9c1177fd8..51f748e04 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Gesuchte Artikel suchen",
"Website": "Webseite",
"Low Bandwidth": "Niedrige Bandbreite",
- "accommodation": "unterkunft"
+ "accommodation": "unterkunft",
+ "Forbidden": "Verboten",
+ "You're not allowed": "Du darfst nicht"
}
diff --git a/translations/en.json b/translations/en.json
index 195afa909..8c845274e 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
"Low Bandwidth": "Low Bandwidth",
- "accommodation": "accommodation"
+ "accommodation": "accommodation",
+ "Forbidden": "Forbidden",
+ "You're not allowed": "You're not allowed"
}
diff --git a/translations/es.json b/translations/es.json
index 6d34af573..5f1954c63 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Búsqueda de artículos deseados",
"Website": "Sitio web",
"Low Bandwidth": "Ancho de banda bajo",
- "accommodation": "alojamiento"
+ "accommodation": "alojamiento",
+ "Forbidden": "Prohibida",
+ "You're not allowed": "No tienes permiso"
}
diff --git a/translations/fr.json b/translations/fr.json
index 102915216..4faf980a8 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Recherche d'objets recherchés",
"Website": "Site Internet",
"Low Bandwidth": "Bas débit",
- "accommodation": "hébergement"
+ "accommodation": "hébergement",
+ "Forbidden": "Interdite",
+ "You're not allowed": "Tu n'as pas le droit"
}
diff --git a/translations/ga.json b/translations/ga.json
index 294542ffd..f50567c05 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Cuardaigh Míreanna Teastaíonn",
"Website": "Suíomh gréasáin",
"Low Bandwidth": "Bandaleithead íseal",
- "accommodation": "lóistín"
+ "accommodation": "lóistín",
+ "Forbidden": "Toirmiscthe",
+ "You're not allowed": "Níl cead agat"
}
diff --git a/translations/hi.json b/translations/hi.json
index e01413f12..cb948e1c6 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "वांटेड आइटम सर्च",
"Website": "वेबसाइट",
"Low Bandwidth": "कम बैंडविड्थ",
- "accommodation": "निवास स्थान"
+ "accommodation": "निवास स्थान",
+ "Forbidden": "निषिद्ध",
+ "You're not allowed": "आपको अनुमति नहीं है"
}
diff --git a/translations/it.json b/translations/it.json
index d9eedff96..b1e46e249 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Ricerca articoli ricercati",
"Website": "Sito web",
"Low Bandwidth": "Bassa larghezza di banda",
- "accommodation": "struttura ricettiva"
+ "accommodation": "struttura ricettiva",
+ "Forbidden": "Proibita",
+ "You're not allowed": "Non ti è permesso"
}
diff --git a/translations/ja.json b/translations/ja.json
index ab7bd3f6d..5ff84212d 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "欲しいアイテム検索",
"Website": "Webサイト",
"Low Bandwidth": "低帯域幅",
- "accommodation": "宿泊施設"
+ "accommodation": "宿泊施設",
+ "Forbidden": "禁断",
+ "You're not allowed": "あなたは許可されていません"
}
diff --git a/translations/ku.json b/translations/ku.json
index 7bfb58c67..3915ac9df 100644
--- a/translations/ku.json
+++ b/translations/ku.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Malper",
"Low Bandwidth": "Bandwidth kêm",
- "accommodation": "cih"
+ "accommodation": "cih",
+ "Forbidden": "Qedexekirî",
+ "You're not allowed": "Destûrê nadin te"
}
diff --git a/translations/oc.json b/translations/oc.json
index fc9e2efdb..1199e3154 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -471,5 +471,7 @@
"Wanted Items Search": "Wanted Items Search",
"Website": "Website",
"Low Bandwidth": "Low Bandwidth",
- "accommodation": "accommodation"
+ "accommodation": "accommodation",
+ "Forbidden": "Forbidden",
+ "You're not allowed": "You're not allowed"
}
diff --git a/translations/pt.json b/translations/pt.json
index 7451b2f15..9c7f14fd5 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Pesquisa de Itens Desejados",
"Website": "Local na rede Internet",
"Low Bandwidth": "Baixa largura de banda",
- "accommodation": "alojamento"
+ "accommodation": "alojamento",
+ "Forbidden": "Proibida",
+ "You're not allowed": "Você não tem permissão"
}
diff --git a/translations/ru.json b/translations/ru.json
index 76b2e6c93..514ef74af 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Поиск требуемых предметов",
"Website": "Интернет сайт",
"Low Bandwidth": "Низкая пропускная способность",
- "accommodation": "размещение"
+ "accommodation": "размещение",
+ "Forbidden": "Запрещенный",
+ "You're not allowed": "Вам не разрешено"
}
diff --git a/translations/sw.json b/translations/sw.json
index 155b08db4..f7bb38937 100644
--- a/translations/sw.json
+++ b/translations/sw.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "Utafutaji wa Vitu vinavyotafutwa",
"Website": "Tovuti",
"Low Bandwidth": "Bandwidth ya chini",
- "accommodation": "malazi"
+ "accommodation": "malazi",
+ "Forbidden": "Imekatazwa",
+ "You're not allowed": "Hauruhusiwi"
}
diff --git a/translations/zh.json b/translations/zh.json
index 4ae5e750e..684fe5322 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -475,5 +475,7 @@
"Wanted Items Search": "通缉物品搜索",
"Website": "网站",
"Low Bandwidth": "低带宽",
- "accommodation": "住所"
+ "accommodation": "住所",
+ "Forbidden": "禁止的",
+ "You're not allowed": "你不被允许"
}
diff --git a/utils.py b/utils.py
index 592ef0c25..716c35112 100644
--- a/utils.py
+++ b/utils.py
@@ -1317,6 +1317,74 @@ def locatePost(baseDir: str, nickname: str, domain: str,
return None
+def _getPublishedDate(postJsonObject: {}) -> str:
+ """Returns the published date on the given post
+ """
+ published = None
+ if postJsonObject.get('published'):
+ published = postJsonObject['published']
+ elif postJsonObject.get('object'):
+ if isinstance(postJsonObject['object'], dict):
+ if postJsonObject['object'].get('published'):
+ published = postJsonObject['object']['published']
+ if not published:
+ return None
+ if not isinstance(published, str):
+ return None
+ return published
+
+
+def getReplyIntervalHours(baseDir: str, nickname: str, domain: str,
+ defaultReplyIntervalHours: int) -> int:
+ """Returns the reply interval for the given account.
+ The reply interval is the number of hours after a post being made
+ during which replies are allowed
+ """
+ replyIntervalFilename = \
+ acctDir(baseDir, nickname, domain) + '/.replyIntervalHours'
+ if os.path.isfile(replyIntervalFilename):
+ with open(replyIntervalFilename, 'r') as fp:
+ hoursStr = fp.read()
+ if hoursStr.isdigit():
+ return int(hoursStr)
+ return defaultReplyIntervalHours
+
+
+def canReplyTo(baseDir: str, nickname: str, domain: str,
+ postUrl: str, replyIntervalHours: int,
+ currDateStr: str = None) -> bool:
+ """Is replying to the given post permitted?
+ This is a spam mitigation feature, so that spammers can't
+ add a lot of replies to old post which you don't notice.
+ """
+ postFilename = locatePost(baseDir, nickname, domain, postUrl)
+ if not postFilename:
+ return False
+ postJsonObject = loadJson(postFilename)
+ if not postJsonObject:
+ return False
+ published = _getPublishedDate(postJsonObject)
+ if not published:
+ return False
+ try:
+ pubDate = datetime.datetime.strptime(published, '%Y-%m-%dT%H:%M:%SZ')
+ except BaseException:
+ return False
+ if not currDateStr:
+ currDate = datetime.datetime.utcnow()
+ else:
+ try:
+ currDate = datetime.datetime.strptime(currDateStr,
+ '%Y-%m-%dT%H:%M:%SZ')
+ except BaseException:
+ return False
+ hoursSincePublication = int((currDate - pubDate).total_seconds() / 3600)
+ if hoursSincePublication < 0 or \
+ hoursSincePublication > replyIntervalHours:
+ return False
+ return True
+
+
def _removeAttachment(baseDir: str, httpPrefix: str, domain: str,
postJson: {}):
if not postJson.get('attachment'):
From 4ae19caeb4417d1044754035f2d22085d757cb4f Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 20:30:49 +0100
Subject: [PATCH 156/385] Set reply interval from edit profile screen
---
daemon.py | 11 ++++++++++-
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 ++-
utils.py | 19 ++++++++++++++++++-
webapp_profile.py | 20 +++++++++++++++++---
20 files changed, 79 insertions(+), 22 deletions(-)
diff --git a/daemon.py b/daemon.py
index 0fb40703e..8de90078e 100644
--- a/daemon.py
+++ b/daemon.py
@@ -232,6 +232,7 @@ from categories import updateHashtagCategories
from languages import getActorLanguages
from languages import setActorLanguages
from like import updateLikesCollection
+from utils import setReplyIntervalHours
from utils import canReplyTo
from utils import isDM
from utils import replaceUsersWithAt
@@ -4539,6 +4540,12 @@ class PubServer(BaseHTTPRequestHandler):
storeBasicCredentials(baseDir, nickname,
fields['password'])
+ # reply interval in hours
+ if fields.get('replyhours'):
+ if fields['replyhours'].isdigit():
+ setReplyIntervalHours(baseDir, nickname, domain,
+ fields['replyhours'])
+
# change city
if fields.get('cityDropdown'):
cityFilename = \
@@ -11367,6 +11374,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.keyShortcuts.get(nickname):
accessKeys = self.server.keyShortcuts[nickname]
+ defaultReplyIntervalHours = self.server.defaultReplyIntervalHours
msg = htmlEditProfile(self.server.cssCache,
translate,
baseDir,
@@ -11379,7 +11387,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.textModeBanner,
city,
self.server.userAgentsBlocked,
- accessKeys).encode('utf-8')
+ accessKeys,
+ defaultReplyIntervalHours).encode('utf-8')
if msg:
msglen = len(msg)
self._set_headers('text/html', msglen,
diff --git a/translations/ar.json b/translations/ar.json
index dfc32a780..fb9071fad 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "انخفاض النطاق الترددي",
"accommodation": "الإقامة",
"Forbidden": "محرم",
- "You're not allowed": "كنت لا يسمح"
+ "You're not allowed": "كنت لا يسمح",
+ "Hours after posting during which replies are allowed": "ساعات بعد النشر المسموح بها خلال الردود"
}
diff --git a/translations/ca.json b/translations/ca.json
index 8f73ec075..059adea12 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Ample de banda baixa",
"accommodation": "allotjament",
"Forbidden": "Prohibit",
- "You're not allowed": "No està permès"
+ "You're not allowed": "No està permès",
+ "Hours after posting during which replies are allowed": "Hores després de la publicació durant les respostes"
}
diff --git a/translations/cy.json b/translations/cy.json
index abb119458..23ea0077e 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Lled band isel",
"accommodation": "llety",
"Forbidden": "Wedi'i wahardd",
- "You're not allowed": "Ni chaniateir i chi"
+ "You're not allowed": "Ni chaniateir i chi",
+ "Hours after posting during which replies are allowed": "Oriau ar ôl postio pan ganiateir atebion"
}
diff --git a/translations/de.json b/translations/de.json
index 51f748e04..d4134fc3b 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Niedrige Bandbreite",
"accommodation": "unterkunft",
"Forbidden": "Verboten",
- "You're not allowed": "Du darfst nicht"
+ "You're not allowed": "Du darfst nicht",
+ "Hours after posting during which replies are allowed": "Stunden nach dem Posten, während denen Antworten erlaubt sind"
}
diff --git a/translations/en.json b/translations/en.json
index 8c845274e..3a80afc89 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Low Bandwidth",
"accommodation": "accommodation",
"Forbidden": "Forbidden",
- "You're not allowed": "You're not allowed"
+ "You're not allowed": "You're not allowed",
+ "Hours after posting during which replies are allowed": "Hours after posting during which replies are allowed"
}
diff --git a/translations/es.json b/translations/es.json
index 5f1954c63..6427d1542 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Ancho de banda bajo",
"accommodation": "alojamiento",
"Forbidden": "Prohibida",
- "You're not allowed": "No tienes permiso"
+ "You're not allowed": "No tienes permiso",
+ "Hours after posting during which replies are allowed": "Horas después de la publicación durante las cuales se permiten las respuestas"
}
diff --git a/translations/fr.json b/translations/fr.json
index 4faf980a8..4e3965c5d 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Bas débit",
"accommodation": "hébergement",
"Forbidden": "Interdite",
- "You're not allowed": "Tu n'as pas le droit"
+ "You're not allowed": "Tu n'as pas le droit",
+ "Hours after posting during which replies are allowed": "Heures après la publication pendant laquelle les réponses sont autorisées"
}
diff --git a/translations/ga.json b/translations/ga.json
index f50567c05..2b1bbdf25 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Bandaleithead íseal",
"accommodation": "lóistín",
"Forbidden": "Toirmiscthe",
- "You're not allowed": "Níl cead agat"
+ "You're not allowed": "Níl cead agat",
+ "Hours after posting during which replies are allowed": "Uair an chloig tar éis an phostála ina gceadaítear freagraí"
}
diff --git a/translations/hi.json b/translations/hi.json
index cb948e1c6..73c393041 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "कम बैंडविड्थ",
"accommodation": "निवास स्थान",
"Forbidden": "निषिद्ध",
- "You're not allowed": "आपको अनुमति नहीं है"
+ "You're not allowed": "आपको अनुमति नहीं है",
+ "Hours after posting during which replies are allowed": "पोस्ट करने के कुछ घंटे जिसके बाद जवाब देने की अनुमति है"
}
diff --git a/translations/it.json b/translations/it.json
index b1e46e249..caac28a7f 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Bassa larghezza di banda",
"accommodation": "struttura ricettiva",
"Forbidden": "Proibita",
- "You're not allowed": "Non ti è permesso"
+ "You're not allowed": "Non ti è permesso",
+ "Hours after posting during which replies are allowed": "Ore dopo la pubblicazione durante le quali le risposte sono consentite"
}
diff --git a/translations/ja.json b/translations/ja.json
index 5ff84212d..60e77ad1c 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "低帯域幅",
"accommodation": "宿泊施設",
"Forbidden": "禁断",
- "You're not allowed": "あなたは許可されていません"
+ "You're not allowed": "あなたは許可されていません",
+ "Hours after posting during which replies are allowed": "転記後の時間返信が許可されています"
}
diff --git a/translations/ku.json b/translations/ku.json
index 3915ac9df..263c8e2fc 100644
--- a/translations/ku.json
+++ b/translations/ku.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Bandwidth kêm",
"accommodation": "cih",
"Forbidden": "Qedexekirî",
- "You're not allowed": "Destûrê nadin te"
+ "You're not allowed": "Destûrê nadin te",
+ "Hours after posting during which replies are allowed": "Demjimêran piştî şandina di dema bersivê de destûr tê dayîn"
}
diff --git a/translations/oc.json b/translations/oc.json
index 1199e3154..8904ef514 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -473,5 +473,6 @@
"Low Bandwidth": "Low Bandwidth",
"accommodation": "accommodation",
"Forbidden": "Forbidden",
- "You're not allowed": "You're not allowed"
+ "You're not allowed": "You're not allowed",
+ "Hours after posting during which replies are allowed": "Hours after posting during which replies are allowed"
}
diff --git a/translations/pt.json b/translations/pt.json
index 9c7f14fd5..b577723da 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Baixa largura de banda",
"accommodation": "alojamento",
"Forbidden": "Proibida",
- "You're not allowed": "Você não tem permissão"
+ "You're not allowed": "Você não tem permissão",
+ "Hours after posting during which replies are allowed": "Horas após a postagem durante as quais as respostas são permitidas"
}
diff --git a/translations/ru.json b/translations/ru.json
index 514ef74af..5268bd9bb 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Низкая пропускная способность",
"accommodation": "размещение",
"Forbidden": "Запрещенный",
- "You're not allowed": "Вам не разрешено"
+ "You're not allowed": "Вам не разрешено",
+ "Hours after posting during which replies are allowed": "Часы после размещения, в течение которых разрешены ответы"
}
diff --git a/translations/sw.json b/translations/sw.json
index f7bb38937..ff4a574b5 100644
--- a/translations/sw.json
+++ b/translations/sw.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "Bandwidth ya chini",
"accommodation": "malazi",
"Forbidden": "Imekatazwa",
- "You're not allowed": "Hauruhusiwi"
+ "You're not allowed": "Hauruhusiwi",
+ "Hours after posting during which replies are allowed": "Masaa baada ya kuchapisha wakati majibu yanaruhusiwa."
}
diff --git a/translations/zh.json b/translations/zh.json
index 684fe5322..3c2f75a98 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -477,5 +477,6 @@
"Low Bandwidth": "低带宽",
"accommodation": "住所",
"Forbidden": "禁止的",
- "You're not allowed": "你不被允许"
+ "You're not allowed": "你不被允许",
+ "Hours after posting during which replies are allowed": "发布后的时间允许答复"
}
diff --git a/utils.py b/utils.py
index 716c35112..6d85e6955 100644
--- a/utils.py
+++ b/utils.py
@@ -1350,6 +1350,23 @@ def getReplyIntervalHours(baseDir: str, nickname: str, domain: str,
return defaultReplyIntervalHours
+def setReplyIntervalHours(baseDir: str, nickname: str, domain: str,
+ replyIntervalHours: int) -> bool:
+ """Sets the reply interval for the given account.
+ The reply interval is the number of hours after a post being made
+ during which replies are allowed
+ """
+ replyIntervalFilename = \
+ acctDir(baseDir, nickname, domain) + '/.replyIntervalHours'
+ with open(replyIntervalFilename, 'w+') as fp:
+ try:
+ fp.write(str(replyIntervalHours))
+ return True
+ except BaseException:
+ pass
+ return False
+
+
def canReplyTo(baseDir: str, nickname: str, domain: str,
postUrl: str, replyIntervalHours: int,
currDateStr: str = None) -> bool:
@@ -1380,7 +1397,7 @@ def canReplyTo(baseDir: str, nickname: str, domain: str,
return False
hoursSincePublication = int((currDate - pubDate).total_seconds() / 3600)
if hoursSincePublication < 0 or \
- hoursSincePublication > replyIntervalHours:
+ hoursSincePublication >= replyIntervalHours:
return False
return True
diff --git a/webapp_profile.py b/webapp_profile.py
index ac87c99c2..0ee01a0da 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -25,6 +25,7 @@ from utils import getImageFormats
from utils import acctDir
from utils import getSupportedLanguages
from utils import localActorUrl
+from utils import getReplyIntervalHours
from languages import getActorLanguages
from skills import getSkills
from theme import getThemesList
@@ -1435,7 +1436,8 @@ def _htmlEditProfileSharedItems(baseDir: str, nickname: str, domain: str,
def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
- userAgentsBlocked: str, translate: {}) -> str:
+ userAgentsBlocked: str, translate: {},
+ replyIntervalHours: int) -> str:
"""Filtering and blocking section of edit profile screen
"""
filterStr = ''
@@ -1489,6 +1491,14 @@ def _htmlEditProfileFiltering(baseDir: str, nickname: str, domain: str,
editProfileForm = beginEditSection(translate['Filtering and Blocking'])
+ idx = 'Hours after posting during which replies are allowed'
+ editProfileForm += \
+ ' ' + \
+ translate[idx] + \
+ ': \n'
+
editProfileForm += \
'' + \
translate['City for spoofed GPS image metadata'] + \
@@ -1864,7 +1874,8 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
peertubeInstances: [],
textModeBanner: str, city: str,
userAgentsBlocked: str,
- accessKeys: {}) -> str:
+ accessKeys: {},
+ defaultReplyIntervalHours: int) -> str:
"""Shows the edit profile screen
"""
path = path.replace('/inbox', '').replace('/outbox', '')
@@ -2048,9 +2059,12 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
editProfileForm += libretranslateStr
# Filtering and blocking section
+ replyIntervalHours = getReplyIntervalHours(baseDir, nickname, domain,
+ defaultReplyIntervalHours)
editProfileForm += \
_htmlEditProfileFiltering(baseDir, nickname, domain,
- userAgentsBlocked, translate)
+ userAgentsBlocked, translate,
+ replyIntervalHours)
# git projects section
editProfileForm += \
From 88cacd5f1ed8d9b702f6094f876f95b8943a8704 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 20:46:21 +0100
Subject: [PATCH 157/385] Better default
---
epicyon.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/epicyon.py b/epicyon.py
index 4953492ad..5711f324f 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -172,7 +172,7 @@ parser.add_argument('--dormantMonths',
'be unseen for before being considered dormant')
parser.add_argument('--defaultReplyIntervalHours',
dest='defaultReplyIntervalHours', type=int,
- default=9999999999,
+ default=1000,
help='How many hours after publication of a post ' +
'are replies to it permitted')
parser.add_argument('--sendThreadsTimeoutMins',
From d38875a705425d67d441297c612a90834f67a752 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 21:12:03 +0100
Subject: [PATCH 158/385] More deterministic test
---
tests.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
utils.py | 12 ++++++-----
2 files changed, 68 insertions(+), 5 deletions(-)
diff --git a/tests.py b/tests.py
index fe0a63615..dd5babc0a 100644
--- a/tests.py
+++ b/tests.py
@@ -42,6 +42,7 @@ from follow import clearFollowers
from follow import sendFollowRequestViaServer
from follow import sendUnfollowRequestViaServer
from siteactive import siteIsActive
+from utils import canReplyTo
from utils import isGroupAccount
from utils import getActorLanguagesList
from utils import getCategoryTypes
@@ -5447,12 +5448,72 @@ def _translateOntology() -> None:
saveJson(ontologyJson, filename + '.new')
+def _testCanReplyTo() -> None:
+ print('testCanReplyTo')
+ baseDir = os.getcwd()
+ systemLanguage = 'en'
+ nickname = 'test27637'
+ domain = 'rando.site'
+ port = 443
+ httpPrefix = 'https'
+ content = 'This is a test post with links.\n\n' + \
+ 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://freedombone.net'
+ followersOnly = False
+ saveToFile = False
+ clientToServer = False
+ commentsEnabled = True
+ attachImageFilename = None
+ mediaType = None
+ imageDescription = None
+ city = 'London, England'
+ testInReplyTo = None
+ testInReplyToAtomUri = None
+ testSubject = None
+ testSchedulePost = False
+ testEventDate = None
+ testEventTime = None
+ testLocation = None
+ testIsArticle = False
+ conversationId = None
+ lowBandwidth = True
+
+ postJsonObject = \
+ createPublicPost(baseDir, nickname, domain, port, httpPrefix,
+ content, followersOnly, saveToFile,
+ clientToServer, commentsEnabled,
+ attachImageFilename, mediaType,
+ imageDescription, city,
+ testInReplyTo, testInReplyToAtomUri,
+ testSubject, testSchedulePost,
+ testEventDate, testEventTime, testLocation,
+ testIsArticle, systemLanguage, conversationId,
+ lowBandwidth)
+ # set the date on the post
+ currDateStr = "2021-09-08T20:45:00Z"
+ postJsonObject['published'] = currDateStr
+ postJsonObject['object']['published'] = currDateStr
+
+ postUrl = postJsonObject['object']['id']
+ replyIntervalHours = 2
+ currDateStr = "2021-09-08T21:32:10Z"
+ assert canReplyTo(baseDir, nickname, domain,
+ postUrl, replyIntervalHours,
+ currDateStr,
+ postJsonObject)
+ currDateStr = "2021-09-09T09:24:47Z"
+ assert not canReplyTo(baseDir, nickname, domain,
+ postUrl, replyIntervalHours,
+ currDateStr,
+ postJsonObject)
+
+
def runAllTests():
print('Running tests...')
updateDefaultThemesList(os.getcwd())
_translateOntology()
_testGetPriceFromString()
_testFunctions()
+ _testCanReplyTo()
_testDateConversions()
_testAuthorizeSharedItems()
_testValidPassword()
diff --git a/utils.py b/utils.py
index 6d85e6955..fc6ebedc1 100644
--- a/utils.py
+++ b/utils.py
@@ -1369,15 +1369,17 @@ def setReplyIntervalHours(baseDir: str, nickname: str, domain: str,
def canReplyTo(baseDir: str, nickname: str, domain: str,
postUrl: str, replyIntervalHours: int,
- currDateStr: str = None) -> bool:
+ currDateStr: str = None,
+ postJsonObject: {} = None) -> bool:
"""Is replying to the given post permitted?
This is a spam mitigation feature, so that spammers can't
add a lot of replies to old post which you don't notice.
"""
- postFilename = locatePost(baseDir, nickname, domain, postUrl)
- if not postFilename:
- return False
- postJsonObject = loadJson(postFilename)
+ if not postJsonObject:
+ postFilename = locatePost(baseDir, nickname, domain, postUrl)
+ if not postFilename:
+ return False
+ postJsonObject = loadJson(postFilename)
if not postJsonObject:
return False
published = _getPublishedDate(postJsonObject)
From 9850ef0c3ee757891161cf5b81a12ecc639eab12 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Wed, 8 Sep 2021 21:13:41 +0100
Subject: [PATCH 159/385] Comments
---
tests.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests.py b/tests.py
index dd5babc0a..8ce77d41c 100644
--- a/tests.py
+++ b/tests.py
@@ -5493,6 +5493,7 @@ def _testCanReplyTo() -> None:
postJsonObject['published'] = currDateStr
postJsonObject['object']['published'] = currDateStr
+ # test a post within the reply interval
postUrl = postJsonObject['object']['id']
replyIntervalHours = 2
currDateStr = "2021-09-08T21:32:10Z"
@@ -5500,6 +5501,8 @@ def _testCanReplyTo() -> None:
postUrl, replyIntervalHours,
currDateStr,
postJsonObject)
+
+ # test a post outside of the reply interval
currDateStr = "2021-09-09T09:24:47Z"
assert not canReplyTo(baseDir, nickname, domain,
postUrl, replyIntervalHours,
From 68c93ce5e57d8cde6126546b4da4ae0ca66d6595 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Thu, 9 Sep 2021 15:32:09 +0100
Subject: [PATCH 160/385] KDE emoji
---
emoji/default_emoji.json | 3 ++-
emoji/kde.png | Bin 0 -> 17102 bytes
2 files changed, 2 insertions(+), 1 deletion(-)
create mode 100644 emoji/kde.png
diff --git a/emoji/default_emoji.json b/emoji/default_emoji.json
index ab8ac89f3..f43d04a1c 100644
--- a/emoji/default_emoji.json
+++ b/emoji/default_emoji.json
@@ -769,5 +769,6 @@
"void": "void",
"openbsd": "openbsd",
"freebsd": "freebsd",
- "orgmode": "orgmode"
+ "orgmode": "orgmode",
+ "kde": "kde"
}
diff --git a/emoji/kde.png b/emoji/kde.png
new file mode 100644
index 0000000000000000000000000000000000000000..7dc2d0adfebaa3ce4da1e1b4e5cf3ab3914540ec
GIT binary patch
literal 17102
zcmeIXbyVEVwk_JY1b3(D;BJjH5Zv9}>Bc3v1q<#@fZ!6G;2u19a1tB>!7V^=l1uXK
zv(LBRJ@4Fi#(4L?-8~w9RW;XKt7fgL>gtM8Q<23)CqoAS0GRS}QW{VH<$ixq;h+94
zCD7sk02Fn;T6*pp=3YP-S7(^50~F});{pXjy=`FtfcH{$_To!g!FuUmG=q^ruW(T+
z7j4c4^4xGUS(673_M7>w_*2H|;Db8W2ZOoqfBk$l-hIRTNgrRcyzQIAZ8RI-`fbV5
zZsmI3r;qm+0fX1}Cx;h1TS$|d!9!;c`WJ_fr@N~|NVyT8ocCwL4*a_~KYW<_dU*F^
zIqcyAhZpqt!GzeZ_8HQz&J7ddy~#!grzZd0zAuwohZmFFXWi2Ysuw>$U`?)v%^ODD
zBc6J>^q$Y6{K5e41zJn&W?VgN$Fb7}U<)5)UTf(m<}Hi_kfq&Hr6sJdQ0M+4N=?k$
z#Z>h-9)I|LvvtA#%I~L#xLd}}Y)=0}>qW+{z5C<6``WL(h6+Jz(S}2=hT>ar`L))G
zbLY$V+nWKb7sfZD-~HOw<4)~{*W`yYay-vOjNNkIQxLDZu2Qy~Zn>w>$aKHjoW7UO
zzcj)eIsG*k|T9iTCmy8hoqKyEpXf>wN+-
zM3zZa|8qi8WY7JuqaS-3=PD8o!J8yiM1MDWMP3
zeIqVv=-q;me(kgOQY%7SB*c+H6SUo9pNLL-X%_16@;-%CU{7O9Y-pUvsNa!D&4zVC@j)7AHAnr~Syo{1Ma?i{P>I1Z3&!dDUA-_}$SIlfFN6TArh
z(Q?{N4Ze=g>Q0&=ACsNCv0*w@6zG-L7>|6D5Fbe_9Ta*MomtH;<4gb<9OqNjrk!L=
z32Ar>l0n?^d_j4Q+hLk?kH?R{^^3*nC#7J~)FTJTnxI`&>g2lh5vPf+HJk4ZIZavt
z(LC}f_RaUE`?_qA>ZN-W&KpyziASVn8zVu_^~NK=!8%Pwk=ue>h7AAB`fM)NK$4Tq
za7zlvXo^m-{Y}{*XYp$O@cY{0P6UzRdT!Hs=Ua
z{9lWKSB!B{{qN@}4^zEp$Y$CJ4_JLRXvg7qG06*Kj2=yNEKPPm^>t*&;@E
zR;(u-!CT(zG+zdwVP)RZ#4>=Z77xGT+!ZIl)K}+)y~y`4X7=PFg<5HN3Sio}N{q!b
z)(Lf(kVyeBx?*HW0`(e5Ol~eRySht%NT~azccGr2)WxU*Hoi?XH)UR?;d*I4Id!-O
zO$vDNao(EMGR^%?smqJF0(O*3ni7-}s~9B8L^#7do$`w@M&+iAaOapQX-D?mm--*u
zmT~AAEv%>L+x!+Bw_iw5#-PA5?dN)(W$NN{dX`mMk{pgZ^|P5N%f%FsLs2iZ*CFP;
zccV3$S4_F!Nw|cMBiodgXNXN~$-iksE-vo~3ukEH@p4{KsV`ue(D*LQv)lp6M%XG}Z@c}EMCE~gB950ULg_T91Sg9(%ruF?P;pj4I7{P(X&8w1Z
z957*u5jKAoxyw4!gm_3&USm!t*=$`Mke$ln;fvKB59r9jbVT*HJ+n?$(vy4CyOI^+D5#U;Gf~d
zS76#tC}l7@4okda!nNp&{m5LbxEWWmo~B{E%_MIwZaV6p5=7u^_agH|ZVP@Yc<{xu
zFRyaE$!9WN66%mQTkcVw+W_^08;JsgrO=c$0a=9_vF!fYpw(I^fx$RG_#K{3&@??_r@w>TU2RvRRU&5+ZSDF?zMgJ7)UXvpj#UI;m+ABrA@A8Wgo`(xfox<@A=j_ah@YDVo)HPj{6}9#wHM=
z{CKTMIaNarE_m6G2VCfLq~KvFHjE0~)J)~^4Aa5X&yTWiu
z(m`K}R)Lx}T|ekfhPBbCtS#S_S-VU-HTgA?uQVka&zT9^tl(BCM-a_I
zM{T!1M$bI9aqAFa>t;*wfG4*u+@G-`V(GoZj4kv;HfQ((RojUq^f9p8AE&KIsb@iW
ztoQz!%7^vL09AMqg<2Cx_(PsBX0win{ZzrH1YsI;d6!ZE;WI|fHNV#RXbtonk@a8{&(kI5lpsziJU;K?$eNY!6c7i0OU##m0&{wc%
zoh!8^3HJh01zlU?G!`V%TNF!tSo0djYi9_#-_?_89u^{oMhEG$3I|OE5qzl`L@(DJ
z;GKfwDwX(775g$63Djt4eWv3v?wiG4Jv83>L*?~&L^6$gv8H-^48e#o=et5??lv?x
z4%gdQvc{S!y=#mwE2KELj-m>Xz|>21>6Cq+m-?5^n`l~;cnX8ai{|ti*;TMXsAhGo
z0d!_q;w(qJ==8dDT;UYo-I5fH=K8i+EVw#)-h6`CuALn2e)Y+f{{)MQM;vy0H8fHP)M#_AIs~vuq{@L>f&kR#e1pzA$}&
zEFoUKM_+`JM+9BK8L>~i>}e=RXqEVs0L!i?j$Fl8p9*u-sZJdkUahDw^p21iYD!@k
zAdHW^Z|t$qN^z27LeWXfl92}=kmG##Y|VO@4_8X>Vvo$yjpVVnhAYyoOE#v26HThKlIzf6nVwVk^iqb6>XY3{#qJPy^nuR3UDK
zNRvy-nk&_5iKbP0K_wP2;i|yRd8|r_z?9bUd!UTEtXvY^D_`1Y&l5j0CmoMc%TWa{
zieZh;My+Ku0A8q#8b^nBiXjx|p`eI`WJE;Z2t
zY
zHMc(agScXkaIuOVP~T-Uf%xph7=B{vidmQBvzQZ1}28z(RIQ02jvVBY`U}orLC9+jVdnIgjXKU9N0q{lnhpQ902&
zB6cGcdBMl4&NhzJ3Q`Mt`mY*_1XGkVwLdddvMY=o(`csKn{7iBjE>glJHT4bbx9I%
zi3Y(@ot2aBv|sC`E0KjcJv#Io*k0=B*E+?l7JqA|btUD?l&7=25_kF`3}0%Ag=?}J
ziMG}>pDky*$yUztUPoRW`GTqdRmVyaF77O5l@*z=&0O6=j;m(7RlrwDer-Tym~M}=
zqEar|6a`sGQ!t9RN0`HzF>YHXIGIO*d)3^L-crgLeSgrKq7s=t^lv^s~ZTOB*
zM5n|cVutVXg?RY*#WOtwX+(|8o%9I($8rYzHGWA3Faj#G(&(lw!-Z7FJbX9Nrl6Ps=_$|>;!eOEPM$K}Xr__w
z1$*m)}>CC#10n?=}d-?>v;ta9IJU
zoVpP*M5av5=vwJ$(}YPxbl*hK5m!o-uoCHKmsc^z!Y9cmOREdAyjE*M<5q54{aJH@
zfKYmbRWI3rgSzmJ&y8^3sVpt|#3#yDpXEhT*C&cZVttWjUNO)>Y#8z+RO^yUcM2pO
zMFmFdRhe!QU@d{XER7p|o=EbxI<_L*V)r8Qgb@UdBj|#-_{pom*L+osya5d*M2pe)
zR9JC#SknZAH&ZwF@_XOHhed4`i8Wy`nm^np>$8WD3QS^-Gy!FGrrVH2L#XRpZv7aa
z4$FHUal_2~vhcA#C8zsBHT3DpaiC{cgK)7=br8jC+IxVQqRaZ
z#`UdfKNm`)*JYK#g`(QWQ6nZa=GnB|TKnHU&+(lu|NX#|bB7GoB$i9K?+AG1CB#@>
zn=_(;Zd2m5xj;M%pCsxGPcr(GsH@ag3P{uI4O?BuH&2NP=@mv~>Z?Yn$owm5Nx
z@J`(6jWe*=P};p`v4Pq;ygA_0W`frnh}Z9WIZPo=AAiUxgLGvL!+
zwiU!vsHhWaVgbk#m&t166_SR3X6#}Oq7%c#rOiQrX&Lupk10Fm1K`|WVC%SXfh%z#
zsf{QQJ)_gyLb}joA>DvOo6p(r^zXu@T@O64D5cB$_nAD@tjw9r)OMK_3Xu!Ad9Kf}
zxsa~8V8cB>Z}1i>C#Fzd6hOqT%c{e_BR3;>`M2TCLYj&BC|JE;qs*E0tryI}nD&_r
zorJLYS)#Xn!LK+cjcSWOF9c1Ch#2^Q2Ft{XSKUxjr{3c~g8;yK8NnytNDNJgV{(5|
zDM=Z4l-ia;So|rE^Fd4L@ksg0GHZ)0`3Ptfo_O={ShQPdrUK1Lzp`l3
zCkC0ZD>}JF(;u}zT3wC>p%1UWS{6{g@9j^U7aeCCi|*!&Ez!6ABvMU=x`)*GAwDQI
z+OXY|50YP6arkf~8(n^NrhIIXuRIZFzD-Rdh)8JuZ0Y0*A{I>geJS6jkXDh3KgO`e
z!tp_Xbb0;x$0W%A13&<)k|=-Xpc}m
zW%^Ocg{2cqJF-RNv4IKSu_KrEoVzWF;ZZ!{{WY?In9P%Fn(SY`I5A%P=M)Of3{x$l
z6ci;c%*%SWNXBUf5#Zh`8At+cQk55JADCruD=80;blwxm4S@)$TYNMgEA|Zh9`>Z$|!XA(|vezo4gAwL9E1`C~^d_&XpQ
zagvAxcPCsnl&vX=72RyPDn8w}iIH_quCr@N4kp!yg&rX{BNT%xBV068%E#jYWInF4
zGpy0DO2yk#u6EL0L*XN13IGouGo)6=`@E=lM4nSd(03mx0e-t#x^77HPDQgg_Go4*
zs|$e|%+KGwa7jvL5GU#6TUX?P_$9G^_dBjEK-j9d#4e(Nf;?AB4}$D6hFOGURV#jd}VpM2%;psaXgK`@1JNHnUuFCE+5o
zuVv38C~@(7Lzheo^L%)W>XJP>q4){LV0%)pJ_F8yk>76B2`CTLwCpHt7dW`pmuu3F
zkHVN}wkpR4+lT1J9#AeS?X-u|H=mrGYTNY)B}R}*Iy>}|(ap@CBjDl*a~0-XNK&}Z
z(qU#w27E)fAvLhHn))olAEroaNz2_BCTU>1t9={@xQy=aSY+;1$=^MAAw^Ov2hWpM
zC@ov)M=bZ~#S@fdB_AFeYfj%=8RL=YLx~cFNrLok#h8%*2?1#vr`eEC(2mC>;`tnh
z7{u@koemxtbc&}q(Nr}XKg4P0xa&6+@vG07oY!R44+A&)+2_+uCG5ae_e~kBD-4|u
z1-WjWkdycn{9>+mB)Zd^9WH0xkkuk&)zHQB=S~?&mND%F%7cM(*GkJ`Q=5wwS)7Hn
zXIQbU1^GFeGor&wi>0$-Wyne#0t;YCN^`(2pJwz@sPBssdQ68+G2}&57Q4y*{nj^~
zB}Z~*Zq=n7-o
zkvyxgljzZFR+-1X)#t=3>H#;i$68rc6kDG0xAlGnN64ClYovy7G77Gw+p9+$T8txG
zP~SGoSE>|;A(8@LFM(?_>HrK{pm4vD7=7hM1~BNW%5_!hr!^%6?@qq7^WzNzw9q?L
zGVZ|q(kX|yuQWoSL^xOyV|$nMqSP_P`fCKpevnX*BHpZ!UEq(yhsrx59IrjaIHm!$
zZslTHO?1fZ*NQuPLrj|BK>ZMM1-?jBxAI}cGZ@@!X(~ec$xTuo=74=#8PX+_Q#;sr
z$7HZMH^d@3NDvDPxRVJUtZ=C0wOzJDpj@EQMJFVt-N>stoQcL@>-3bvK&sp#%+-I|
z*o)uH&O56{>C(*$)n1@J_UI!a98BkBXT3#k`boI!-Dyiat?obTxxN0tz&K8TS^FzBYIc^Ut??b
z(DxcG?Lz7dz*9-9ZSXJMH8km
z_yl1r{H157EUBG(M9$lxs9itS(Cr?)iHe<%A_;EuF{p|^)HxNTD2d?P2ew;+QMw46
zA6%YGRBip}6VcN-NUevB_w771)-fBALfXV2p|9)rVI6as6
z)YEh}9#A<}GPNKu;rR|kH^LGrW@Ax-Mv|Aal~un)_5td^ZSRn`8Yyo95(!uFmQTGe
z7crbs?(%Oe@KTIIoaXTO($r?_A%=aYfwd~V&L3CW+{Y9s*xTsdx10MOxdTK!sc3%2
zvpP2=Z|YiPLA#x$XYA8|)R?4-o@Aav$ED%NC5TUKY@otp&oZ7;LHh+
zLg>G*umeGfUEUEb^9N<0aL=^ufa+H(6iyi-1L6XbWQ9&yGxP^R`RQD^^mFxzAKjul2!n5E$Cx@m9(Lg>fj%^(bQvS*
z6Dfa12|56B9IaGVMMP0*C`3hz}MXg?aEf;lm`1jt$Lj6
z?R;)Tx7dfRLG;tkg0!hg0t|mu4L%H|1R^P50eVwZh4#i43Q3lW=h98P|mm%f;2%Oj5p!kWKQlP0&s^?5I-%{~aNQAC2`0RXr#TS-YZc}dCt
zxV(J2vV8k0Nkpz+j3j)$R6)A}%M(X^SS^S3CPKiDFj|df4!s)k#^?@3nJk%rShlxm
zV`I`{zIRYx7e!Yb=K=1eo12Dv5?;3=I-5n;R%^udhwE=ALSkh^nXslfxGbmaC_OEu
z65YWb7KrVY8R`~yL#&{DbkVn)_K1X2FWlVu_LIr!Y8Wf>jnr3t*c-m@$lotZ9bMKK
zuP
z`(#izesI0bfdRWrgVxNFNnyn3g&^Xhk22>a;mjr(>%(tI7G<>9Zhb@-uOV|yEg%zAc
z0j+KE_L6m5|5;sreTovOp$
z==d)dZtl{a|LO0)v~bgUy42^;fVw$*xLQJ`J)utS&;Rb!#lgev?>;@;puemB#O+`O
z<9G_{kLSPJ$jU3L{nO^RjMlb}E`Kb3qyLVyviv8`#lzL%55~%p1L^>EdCf+$|1FRwcmL%77xaJV^(U4;x->mYi(7U~Vfmb8fH&n*~gems?Q4+>+ni>TeXPuC`Az!QA2RQT?W}
zdZOYN0CVy1@`Bko`MJStycSSyHghN^KO2~j7bXbhiaEmaQXH)7|{HXiwpCaS8GZ@PWAmz`TNgv(|ySx;-WN
zZ%!^wc5d!J=2n&vnI}f`r)0BrG`EIwxHwt=Y4|-Xkf+W(32Xj)ZagvnsekGVMA8*%
z?(XcWxib{WZKmVUFJm>VI@k`1g{d
ztPJ_P0K&hQB#8X)%+vI6adEJPy8f#$|B)#FFSx(i|05~?Pv(Dz{beob?Beq@X>Ht9
zy`27A_x}a(F9s!BOQ@5Z^M4om-ywg=^0$5GDdxZGp7yh+?UCc3`{O^-;`hw?zxet`
zy8U1D@I?J@C;uaT|1HxWu^X{!U4CE9f40zXfAU4ZUDeD{NEoqK-OE5r%Dued1YyoZ)j*(
z)JV#QOD_Qc0#A79*42F|X*XyC
z$Zg}rbW9?Mp=rs3w3*Wu-Lnvr2;Pk262)
zKP~`(1NeT^i*Sm7?e|L+MFuq}FL!GzA`*tugX~Fs_4&k2FNC9JZXF#1H!2b5O(HHy
zLTzXdA|eb{t~Z`Oh#us>uaAiX7dgcv0MDt`R7j6*mk;p#a>a4{pb8%zy7#8@U347`
zBQ|&n-&baGXG*G(+G=^s#DSX=@wQ(XfP*TsRn_+qj%>S%%N{YCV4&xSD2}r3QOXP!
z896>T=d{);onO*VYB{(~BJ$eWt(a*9Ik?acp6jO7*1LXpn1Z6#uHD8(y8wYj-eN|}
zfjNCHT3me$CJ(GkTXuXP?U@3gm7hCxm9gt=7H&REVEGuGgd5pY2S3dFtwnkU3nl3L
ziElO>e6nQCs=tm%c1K{|>?|1>JRzID;s?cSpm3v
z5=Ibk6Ng_e-&kLqxe^8#hri?TH4-0-w6TL(fXFmS=`o7aODxY&QS%Bh6OuufRrz8g>ym#ciIg;RhSZQiV~d+#(rYP@E53&i`O*jL1yb(KNmp{TjSkE9YdJl*c0|5Fv3LgJKm$MAMB4G
zSbnw4@=rsMgj{k?;b9T&A!=NR@PJ_pd8MN_VF8G9n>)Q772gvw<{L@^Ke(tG82Dm%
zXS~bKR8VL;-TJ}zVYx)t@3vOn8U4#D{-v5(H0nTFzX#G7z@|CRif2)+2LKSQ%RU{7
zfCWi4EQuKCPEJ-ZRJxd4ESXDgoKQ$05l%o!X6_yD9_b&O)4bHZ;P+;~_gy(mLDP$(X3$?dJSvB)$jYlg8_4f`#HtBy$K1@lHM(Eo$3UE2VY
z)BWW={jWUo+gDSE7Vvo&<@dp)Kp^r^44-gT7z59>Mfh%-Pg*%!cBw+;J0-z2^RHKL0dX`AjzX
z7RM~IhaJ(Yt}5V_R+rx0E~bd9PNu?WKgjfS_E5RTYcbdaswOS%f+}UIH^`BPtmS(Y935qPss&-$i;UcXZxh9w6L0Z|G
z%C0>dd0vTbu%3D*!trj`!?!8pdc-&E{0G6S^?2OEt&P>fuMJs~c3FN^xShC(&n2Eq
z3>_=w${cfFPM6lD7ONGxZG0z3!3};Rp~pF?`*VL(Bzu9HXDsl6dG<7S3~oS3d+m
zpd>GkEh-vO<2&nT2A{uh;|1h$xw5~?t8psz;@@}FU8C>7i}X~b^1KGqAXhMx#b6D7
zgvS)mxBMI(89(JyGfr05{AJHY7hOpY=qr_#ceFLHm{raDCanamIC|i
z8$M&-7WrDQZ>wdW2d_Nm3eHp&5RmiH1y%6_NW9mC&g0=K0fd2{yeQ%7nV2$V>P)zp
zLOF(r;j-LXZQv;2zIK+9zDC*5G_%W6AlnKc=J48YQTZvj*I~8Qj~d*;z*G^kM6hXm
znt##b6#m|<3iWYhsiGo!$4F!RRjT7$94_knSAw`i(#P
zy!~_6JDq|@0|_ah*yM05pa=CbqJmE2j>+`od|5f5BV)|UFc`k)hWO}99Z~rbFJX|k
zq_^?bkGGw8c)Ss)NKiGeIrUWbxXLqd;d?htyjC|E#_}UV_GaCVmLPM%V^_we)
zGoOi(a}5Gne^g4=%qmh)C+{77fPPjTnetM?>Wm^}55?Umr$m
zecQ(Q$jCqe<-6So<4P0tW_a+SJtQ~0-Z+VGR;#YwUzWBL-z1jcFS9TTm#Mp&CS_tV
ztj0fbThO>G9r>Kt4o(HxF~7=lYQHRCsYUUj^xzx^_$K`HCzr5z3^mpQ8q7t4DGl@<
z=G4{7-cD4e^+|_hq!89!FY=+Mte(DWt);^d7W^zHms|OfW7%cxEG39VEAAFXEb1^5
zQQ&}n8GgG73bJLYC*N?45Hr#_62>bK!eo;;X9tdBP)qv>Mn#eJym6>MU8#G$aY73>Uht;9CYS9oCY{RX8MEZYZYAHQ*AlTWZn9$E
z)1WeZLgMBzr+c53@iw|Fj$UZGEPa#8{wo0qw|ZG?xwrcFrCIKcam~ZB?{kQs5nIY`
z{E?q6Xeh+G^6Y)f&&E*iUJ>$VnXXV^p3OJ3ESDqu6uArc$aD{oqCNHD!nR>X%_+_<
z)(q|vN1XC&D_k@WE~{C!qxxj{tGHl@kb@-)#ZIIpm+WI!HAtGJ1EiUJY^93}@nIxy
zO&OUGiyl}ZTs!rt$^p%zVs5VD#g5PM=Hk?g^Y1&QvW6gSS9+WikO1L*Wg9-`u~jm-
z{VFVo5sMLvdpD{+L4qpGSf;1LnA~tR=m}pG@>9@qPAew1=tl=ZS0&oQCvZGqdg~n@
zMHG&)c7NesN=Y#-YX0l^l)>7(lw}t%qSTPE^TQ(OEyu%bR<3{zB}4+`j_RLSFrQyW
zh+8*e7UUN{@Cs?hmDxChdphJ;J?T9Q83ff?oLlTo_b}=
zceY5A53{1LC^g5G!gBg-O!f4=_?bXl+|);cXAdRAfIJsk>l2I|e=&fFtf3MlCY
zX0f)1!pynVgR{BDKGK}Qn;#n=qLQBOMN938kbg3GRnMv;)r&k|tyfoj^hMmAsc=i{
z>eW*I1W#Q=_%tpah=-d7K|l;BB>@RQzXVj?FW=(!YI(%cc`^ZWP>lEqgh|B|aZc3O
zXiqDaYKg?>Wx}$J+Cf@Cc&mGN{}tIZOfgILWp?hYctP#XYMY(%Du8+JuKw_sDxCWnbYrK-s%rDD@y|P#c^0tV
z*oO+L1Kk!;n%*d?PecvLEeG#By>|&G?YkZN
zx5D#UTy{t)b#sZRbg04|w!{N}9Xt=*>{b~fQK4W~Y+JdMr$iaxxfje(l;^mghl5(^1r<@rs)kl`&LQR%VIx8gZ*kS9N2HDq=fdCSyfQgkq%Rg}sS
zo?v>qx?KE3?NMQC>Q4|u__d8r>JpV!!B~IDFJh|8(ifE6gXrpi`Pz
zK{%~M@*@adiZH)Sj(_CEVez|JmWU_>hbbaP6GiH=Z+pU)W#z77s4#k0rBzU&zH41s
z`mtQ?NnO&BH+y^sYCcnx*&0%N^hy1Dqzb!h?|o&Dl(AEFQl%HQsyb4d3*r;m_4?|H
zg&?|&dh+uQ6TU^{Aoz&nNSPzboR$X%@fhTu%XSu4PR-mVRI6k#J!wj;{E(If>uhR^
z?4nYArWL+Nb|Xjn(Z#1n^Bx@BC#HrvCIg*m)>cfp+I`X(3fFK&H+D
zmd}-TSUt#O$}xKho*tuIXjxbAg_f`U8^_`xHoQzE%~X}gSf-&)r`cOvUl|!$SY;SRvT<(+
zEp?%%MbUwPo#{?!|=qm%N6gqc8d`aej?h#D`~P9`*@5A?QJ96K(C)0JNM78n*0*W|rEBbElbX
zt0IX`wyeBj0tA8g#Hxe#Rt#!OBx>9IPJC
zmXb4^8@fjkv9VJvYIQoXtZ*1eO&cMqIgnI7YiRLRI98oUB8Ex`(GvZQyTN!IQdR1-9e8}j_2
zVm-Mmmtu(+?DWUs8r*up#Y2GmwIZ7#E{>s39SavL)3F7G%Y@)r%U`A6@?Vog(B!lbhztEnA{8EG?9>xt$?`5V;=2
zSU`oKA~iIcn>I5V3cNT9$)0ywJR7$>%!iDRlHz;`mWK&ZIozkj(}$40v5|*noqK*g
z99D*+M%t`txn-6%9$zdw`YwMP8}XFLG%9;^-Qn2zc{^h~ZhOK6c>n4xDmOgRyxyp1
z-kS4l#H%(ct87Qa=8feAQT?!8mcVHU7h*U-0VJpp3sqVo{K7JDFOzdJ7G04(#=EJk
z>g*+FR?;W6qpdLhm{@zgZ<>8s!Q#x&fg5ckF*THeiC$A!0Zl+z#kzeSMZm+PsUVrR
z=J-{#<@PxL^WxR;4ivm^!W#9FRdb>!f-F@bO0sk5TctYRA|%Wv0&Q8;x+WWrQI3?s
zyG0s&7|~hzeY_l>^j-b_*n}E8$}NtErh^&_(#K@w0?*dBZXGVY`S4{br%ZNC8C;l>
zcT*Ybez%DZTG;?GW#xiYfi}n8njNQAHOz3Xl2=QYx2DEEc~!GTJJ$&DrKFMc!x?_d
z)?JIU%^^Is#r5Rd{rMfmMPiNph)o6M+cV@2DD6=JNsc;ZaGV{4KPE7uQ}t%v+!q)n
zoU<>g#KNHx4!F*Z(RR>u*WUV{9g^zWaF#ZzJ~HYq)}Uih$NWsVwJfzrktdKtgVui{
zE%O|yVW31LP-z;Hq)+8i%&1c2vl@=*5ruyZ;*;KI*e|-&_p2+zL7l=1NiJX1bH|G-
z?HHiKv|uVyTJ#OuZ1&$YwS34d)YDlZAV-~IWv0EKyV@EOM*tua&erMWnT-OmVnuTX
zSeF;JT110&O$!Kn=8&`h6e@3_IUkx9@@R-8aSqU
fo89x)&RitVp%tHPbf)+HV
literal 0
HcmV?d00001
From 67a2c2939fb181dd76184179b0fe5ab9e4b8340b Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 10 Sep 2021 12:12:52 +0100
Subject: [PATCH 161/385] Check that the post replies to is a status
---
daemon.py | 3 ++-
utils.py | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/daemon.py b/daemon.py
index 8de90078e..6a926acb1 100644
--- a/daemon.py
+++ b/daemon.py
@@ -11307,7 +11307,8 @@ class PubServer(BaseHTTPRequestHandler):
replyIntervalHours = self.server.defaultReplyIntervalHours
if not canReplyTo(baseDir, nickname, domain,
inReplyToUrl, replyIntervalHours):
- print('Reply outside of time window ' + inReplyToUrl)
+ print('Reply outside of time window ' + inReplyToUrl +
+ str(replyIntervalHours) + ' hours')
self._403()
self.server.GETbusy = False
return True
diff --git a/utils.py b/utils.py
index fc6ebedc1..2042b248b 100644
--- a/utils.py
+++ b/utils.py
@@ -1375,6 +1375,8 @@ def canReplyTo(baseDir: str, nickname: str, domain: str,
This is a spam mitigation feature, so that spammers can't
add a lot of replies to old post which you don't notice.
"""
+ if '/statuses/' not in postUrl:
+ return True
if not postJsonObject:
postFilename = locatePost(baseDir, nickname, domain, postUrl)
if not postFilename:
From ef1696e751aed8c2e5e05d2fb2e85b902315ed82 Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 10 Sep 2021 15:45:44 +0100
Subject: [PATCH 162/385] Update webserver proxy
---
gemini/EN/install.gmi | 9 ---------
website/EN/index.html | 46 +++++++++++++++++--------------------------
2 files changed, 18 insertions(+), 37 deletions(-)
diff --git a/gemini/EN/install.gmi b/gemini/EN/install.gmi
index 23f342289..6994083b3 100644
--- a/gemini/EN/install.gmi
+++ b/gemini/EN/install.gmi
@@ -62,8 +62,6 @@ Create a web server configuration:
And paste the following:
- proxy_cache_path /var/www/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
- inactive=60m use_temp_path=off;
server {
listen 80;
listen [::]:80;
@@ -118,8 +116,6 @@ And paste the following:
location / {
proxy_http_version 1.1;
client_max_body_size 31M;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
@@ -135,10 +131,6 @@ And paste the following:
proxy_redirect off;
proxy_request_buffering off;
proxy_buffering off;
- location ~ ^/accounts/(avatars|headers)/(.*).(png|jpg|gif|webp|svg) {
- expires 1d;
- proxy_pass http://localhost:7156;
- }
proxy_pass http://localhost:7156;
}
}
@@ -146,7 +138,6 @@ And paste the following:
Enable the site:
ln -s /etc/nginx/sites-available/YOUR_DOMAIN /etc/nginx/sites-enabled/
- mkdir /var/www/cache
Forward port 443 from your internet router to your server. If you have dynamic DNS make sure its configured. Add a TLS certificate:
diff --git a/website/EN/index.html b/website/EN/index.html
index 01292cbf5..456348e37 100644
--- a/website/EN/index.html
+++ b/website/EN/index.html
@@ -1370,9 +1370,6 @@
And paste the following:
- proxy_cache_path /var/www/cache levels=1:2 keys_zone=my_cache:10m max_size=10g
- inactive=60m use_temp_path=off;
-
server {
listen 80;
listen [::]:80;
@@ -1425,30 +1422,24 @@
}
location / {
- proxy_http_version 1.1;
- client_max_body_size 31M;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host $http_host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forward-Proto http;
- proxy_set_header X-Nginx-Proxy true;
- proxy_temp_file_write_size 64k;
- proxy_connect_timeout 10080s;
- proxy_send_timeout 10080;
- proxy_read_timeout 10080;
- proxy_buffer_size 64k;
- proxy_buffers 16 32k;
- proxy_busy_buffers_size 64k;
- proxy_redirect off;
- proxy_request_buffering off;
- proxy_buffering off;
- location ~ ^/accounts/(avatars|headers)/(.*).(png|jpg|gif|webp|svg) {
- expires 1d;
- proxy_pass http://localhost:7156;
- }
- proxy_pass http://localhost:7156;
+ proxy_http_version 1.1;
+ client_max_body_size 31M;
+ proxy_set_header Host $http_host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forward-Proto http;
+ proxy_set_header X-Nginx-Proxy true;
+ proxy_temp_file_write_size 64k;
+ proxy_connect_timeout 10080s;
+ proxy_send_timeout 10080;
+ proxy_read_timeout 10080;
+ proxy_buffer_size 64k;
+ proxy_buffers 16 32k;
+ proxy_busy_buffers_size 64k;
+ proxy_redirect off;
+ proxy_request_buffering off;
+ proxy_buffering off;
+ proxy_pass http://localhost:7156;
}
}
@@ -1458,7 +1449,6 @@
ln -s /etc/nginx/sites-available/YOUR_DOMAIN /etc/nginx/sites-enabled/
- mkdir /var/www/cache
From 2a94dc95162950605b63c479b88a30bab52157cc Mon Sep 17 00:00:00 2001
From: Bob Mottram
Date: Fri, 10 Sep 2021 17:14:50 +0100
Subject: [PATCH 163/385] Change domain to libreserver.org
---
CONTRIBUTING.md | 4 ++--
acceptreject.py | 2 +-
announce.py | 2 +-
auth.py | 2 +-
availability.py | 2 +-
blocking.py | 2 +-
blog.py | 2 +-
bookmarks.py | 2 +-
briar.py | 2 +-
cache.py | 2 +-
categories.py | 2 +-
city.py | 2 +-
code-of-conduct.md | 3 +--
content.py | 2 +-
context.py | 2 +-
conversation.py | 2 +-
cwtch.py | 2 +-
daemon.py | 2 +-
delete.py | 2 +-
desktop_client.py | 2 +-
devices.py | 2 +-
donate.py | 2 +-
epicyon.py | 2 +-
feeds.py | 2 +-
filters.py | 2 +-
follow.py | 2 +-
followingCalendar.py | 2 +-
git.py | 2 +-
happening.py | 2 +-
httpsig.py | 2 +-
i2pdomain | 2 +-
inbox.py | 2 +-
jami.py | 2 +-
languages.py | 2 +-
like.py | 2 +-
linked_data_sig.py | 2 +-
manualapprove.py | 2 +-
markdown.py | 2 +-
mastoapiv1.py | 2 +-
matrix.py | 2 +-
media.py | 2 +-
metadata.py | 2 +-
migrate.py | 2 +-
newsdaemon.py | 2 +-
newswire.py | 2 +-
notifyOnPost.py | 2 +-
outbox.py | 2 +-
person.py | 2 +-
petnames.py | 2 +-
pgp.py | 2 +-
posts.py | 2 +-
question.py | 2 +-
roles.py | 2 +-
schedule.py | 2 +-
scripts/epicyon-notification | 2 +-
session.py | 2 +-
setup.cfg | 4 ++--
shares.py | 2 +-
siteactive.py | 2 +-
skills.py | 2 +-
socnet.py | 2 +-
speaker.py | 2 +-
ssb.py | 2 +-
tests.py | 10 +++++-----
theme.py | 2 +-
threads.py | 2 +-
tox.py | 2 +-
utils.py | 2 +-
webapp_about.py | 2 +-
webapp_accesskeys.py | 2 +-
webapp_calendar.py | 2 +-
webapp_column_left.py | 2 +-
webapp_column_right.py | 2 +-
webapp_confirm.py | 2 +-
webapp_create_post.py | 2 +-
webapp_frontscreen.py | 2 +-
webapp_hashtagswarm.py | 2 +-
webapp_headerbuttons.py | 2 +-
webapp_login.py | 2 +-
webapp_media.py | 2 +-
webapp_minimalbutton.py | 2 +-
webapp_moderation.py | 2 +-
webapp_person_options.py | 2 +-
webapp_post.py | 2 +-
webapp_profile.py | 2 +-
webapp_question.py | 2 +-
webapp_search.py | 2 +-
webapp_suspended.py | 2 +-
webapp_timeline.py | 2 +-
webapp_tos.py | 2 +-
webapp_utils.py | 2 +-
webapp_welcome.py | 2 +-
webapp_welcome_final.py | 2 +-
webapp_welcome_profile.py | 2 +-
webfinger.py | 2 +-
website/EN/index.html | 8 ++++----
xmpp.py | 2 +-
97 files changed, 106 insertions(+), 107 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 181e06f4e..25ea8abd9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ By submitting code, documentation or artwork you agree that it will be licensed
## Security Vulnerability Disclosure
-Create an issue on https://gitlab.com/bashrc2/epicyon/issues. If the vulnerability is especially sensitive then send an XMPP message to **bob@freedombone.net** or a Matrix message to **@bob:matrix.freedombone.net**.
+Create an issue on https://gitlab.com/bashrc2/epicyon/issues. If the vulnerability is especially sensitive then send an XMPP message to **bob@libreserver.org** or a Matrix message to **@bob:matrix.libreserver.org**.
## Code of Conduct
@@ -14,7 +14,7 @@ The code of conduct can be found [here](code-of-conduct.md).
Submit to https://gitlab.com/bashrc2/epicyon/issues
-You can also post patches in the old-fashioned style via email to **bob@freedombone.net**. Include **[Epicyon]** in the subject line, otherwise it may be ignored.
+You can also post patches in the old-fashioned style via email to **bob@libreserver.org**. Include **[Epicyon]** in the subject line, otherwise it may be ignored.
## Development Style
diff --git a/acceptreject.py b/acceptreject.py
index d4d0e22ec..bd54600c3 100644
--- a/acceptreject.py
+++ b/acceptreject.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/announce.py b/announce.py
index 8a1fd261b..c523ecf9d 100644
--- a/announce.py
+++ b/announce.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/auth.py b/auth.py
index de23e6130..0305dca68 100644
--- a/auth.py
+++ b/auth.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Security"
diff --git a/availability.py b/availability.py
index a565c6f5e..b23cd0227 100644
--- a/availability.py
+++ b/availability.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/blocking.py b/blocking.py
index d39b17c70..68b1e3e74 100644
--- a/blocking.py
+++ b/blocking.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/blog.py b/blog.py
index 58ac947ca..fbdcadad0 100644
--- a/blog.py
+++ b/blog.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/bookmarks.py b/bookmarks.py
index 3cfc09fd0..fa9fbb86e 100644
--- a/bookmarks.py
+++ b/bookmarks.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/briar.py b/briar.py
index 6e3f1e1d0..76369208b 100644
--- a/briar.py
+++ b/briar.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/cache.py b/cache.py
index c6bb925ff..7b4654944 100644
--- a/cache.py
+++ b/cache.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/categories.py b/categories.py
index b21a15e25..f2834e9c2 100644
--- a/categories.py
+++ b/categories.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "RSS Feeds"
diff --git a/city.py b/city.py
index a780996f6..b486c2de0 100644
--- a/city.py
+++ b/city.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Metadata"
diff --git a/code-of-conduct.md b/code-of-conduct.md
index 73e4b2a1e..83ed3d4d1 100644
--- a/code-of-conduct.md
+++ b/code-of-conduct.md
@@ -52,7 +52,7 @@ If you're raising concerns about something or someone, there must be demonstrabl
This is not a big project and so there is no division of labor or special enforcement committee or bureaucratic process.
-Complaints should be either reported in the Matrix chat room **#epicyon:matrix.freedombone.net** or sent to bob@freedombone.net, preferably via XMPP/Conversations with OMEMO enabled but you can also use the same address for email correspondence.
+Complaints should be either reported in the Matrix chat room **#epicyon:matrix.libreserver.org** or sent to bob@libreserver.org, preferably via XMPP/Conversations with OMEMO enabled but you can also use the same address for email correspondence.
## In case of violations
@@ -60,6 +60,5 @@ Violators of this code of conduct will:
* Be removed from any associated Matrix and/or XMPP chat rooms
* Will not have pending or future patches or pull requests merged
- * If they have a user account on *code.freedombone.net* it will be removed
This applies regardless of past levels of commitment or technical abilities.
diff --git a/content.py b/content.py
index e7af2e76f..b9f07c69e 100644
--- a/content.py
+++ b/content.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/context.py b/context.py
index 20efb02d9..c7e51d6da 100644
--- a/context.py
+++ b/context.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Security"
diff --git a/conversation.py b/conversation.py
index 32ecd0e08..ba35c5ba8 100644
--- a/conversation.py
+++ b/conversation.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/cwtch.py b/cwtch.py
index 9619067f1..0d145d402 100644
--- a/cwtch.py
+++ b/cwtch.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/daemon.py b/daemon.py
index 6a926acb1..eac622b3a 100644
--- a/daemon.py
+++ b/daemon.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/delete.py b/delete.py
index 2b7536542..1d16637bd 100644
--- a/delete.py
+++ b/delete.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/desktop_client.py b/desktop_client.py
index 62aa3acc8..67c94ef1d 100644
--- a/desktop_client.py
+++ b/desktop_client.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Client"
diff --git a/devices.py b/devices.py
index 1075f8582..242a0a75a 100644
--- a/devices.py
+++ b/devices.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Security"
diff --git a/donate.py b/donate.py
index a8f4e26d3..f15d5cb80 100644
--- a/donate.py
+++ b/donate.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/epicyon.py b/epicyon.py
index 5711f324f..b1640a308 100644
--- a/epicyon.py
+++ b/epicyon.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Commandline Interface"
diff --git a/feeds.py b/feeds.py
index 310243981..794f327c9 100644
--- a/feeds.py
+++ b/feeds.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "RSS Feeds"
diff --git a/filters.py b/filters.py
index 875fb5503..20efe8a5b 100644
--- a/filters.py
+++ b/filters.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Moderation"
diff --git a/follow.py b/follow.py
index 211960e96..adec4858e 100644
--- a/follow.py
+++ b/follow.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/followingCalendar.py b/followingCalendar.py
index e5453eb2b..5782f801e 100644
--- a/followingCalendar.py
+++ b/followingCalendar.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Calendar"
diff --git a/git.py b/git.py
index 7556d4840..fa51a39bd 100644
--- a/git.py
+++ b/git.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/happening.py b/happening.py
index a3d877528..fa8d85a98 100644
--- a/happening.py
+++ b/happening.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/httpsig.py b/httpsig.py
index 1b08cfa0c..e4ee2964a 100644
--- a/httpsig.py
+++ b/httpsig.py
@@ -4,7 +4,7 @@ __credits__ = ['lamia']
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Security"
diff --git a/i2pdomain b/i2pdomain
index 9eeea2e5d..f4c7d9f74 100755
--- a/i2pdomain
+++ b/i2pdomain
@@ -5,7 +5,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
import base64, hashlib, sys
diff --git a/inbox.py b/inbox.py
index 002721d7d..a3c856555 100644
--- a/inbox.py
+++ b/inbox.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/jami.py b/jami.py
index d0a20008b..9f9e8c76a 100644
--- a/jami.py
+++ b/jami.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/languages.py b/languages.py
index 9d0d4022b..0bd5996d7 100644
--- a/languages.py
+++ b/languages.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/like.py b/like.py
index 6765d6c1b..0fee375d1 100644
--- a/like.py
+++ b/like.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/linked_data_sig.py b/linked_data_sig.py
index b1acbc7da..f6234fcc2 100644
--- a/linked_data_sig.py
+++ b/linked_data_sig.py
@@ -5,7 +5,7 @@ __credits__ = ['Based on ' +
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Security"
diff --git a/manualapprove.py b/manualapprove.py
index 0d85bd1e6..e4180d100 100644
--- a/manualapprove.py
+++ b/manualapprove.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/markdown.py b/markdown.py
index eb0cbf2d7..8996cf408 100644
--- a/markdown.py
+++ b/markdown.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/mastoapiv1.py b/mastoapiv1.py
index b8b85c04f..b099685f9 100644
--- a/mastoapiv1.py
+++ b/mastoapiv1.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "API"
diff --git a/matrix.py b/matrix.py
index 0d493b0dc..39310288c 100644
--- a/matrix.py
+++ b/matrix.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/media.py b/media.py
index b7382f4b2..7d9f3c9ba 100644
--- a/media.py
+++ b/media.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/metadata.py b/metadata.py
index 4f55f1cc7..bfbf76d62 100644
--- a/metadata.py
+++ b/metadata.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Metadata"
diff --git a/migrate.py b/migrate.py
index 33c5143b8..5298200e3 100644
--- a/migrate.py
+++ b/migrate.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/newsdaemon.py b/newsdaemon.py
index 63de2e116..8824b9615 100644
--- a/newsdaemon.py
+++ b/newsdaemon.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface Columns"
diff --git a/newswire.py b/newswire.py
index 607f9cd2e..3d886dbeb 100644
--- a/newswire.py
+++ b/newswire.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface Columns"
diff --git a/notifyOnPost.py b/notifyOnPost.py
index e967e5d93..fdbe879f3 100644
--- a/notifyOnPost.py
+++ b/notifyOnPost.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Calendar"
diff --git a/outbox.py b/outbox.py
index 45b11d35e..62863a86e 100644
--- a/outbox.py
+++ b/outbox.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/person.py b/person.py
index 8fa404ec8..f38abee09 100644
--- a/person.py
+++ b/person.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/petnames.py b/petnames.py
index 6346c22f5..e0230c1be 100644
--- a/petnames.py
+++ b/petnames.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/pgp.py b/pgp.py
index c1178e19e..cab1f6c64 100644
--- a/pgp.py
+++ b/pgp.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/posts.py b/posts.py
index 408490da9..41b4f92e0 100644
--- a/posts.py
+++ b/posts.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/question.py b/question.py
index 9157aeea6..54f8f4485 100644
--- a/question.py
+++ b/question.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/roles.py b/roles.py
index 1dccf2073..8dc8466f9 100644
--- a/roles.py
+++ b/roles.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/schedule.py b/schedule.py
index 48d4bd035..83f29c27d 100644
--- a/schedule.py
+++ b/schedule.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Calendar"
diff --git a/scripts/epicyon-notification b/scripts/epicyon-notification
index 4b6205d63..a92db2b01 100755
--- a/scripts/epicyon-notification
+++ b/scripts/epicyon-notification
@@ -11,7 +11,7 @@
# License
# =======
#
-# Copyright (C) 2020-2021 Bob Mottram
+# Copyright (C) 2020-2021 Bob Mottram
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
diff --git a/session.py b/session.py
index 317042a42..ed0c1e5d6 100644
--- a/session.py
+++ b/session.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/setup.cfg b/setup.cfg
index 2a7d568f6..bf4669a6b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,9 +2,9 @@
name = epicyon
version = 1.3.0
author = Bob Mottram
-author_email = bob@freedombone.net
+author_email = bob@libreserver.org
maintainer = Bob Mottram
-maintainer_email = bob@freedombone.net
+maintainer_email = bob@libreserver.org
description = A modern ActivityPub compliant server implementing both S2S and C2S protocols.
long_description = file: README.md
long_description_content_type = text/markdown
diff --git a/shares.py b/shares.py
index 60096d2d2..47e99ef23 100644
--- a/shares.py
+++ b/shares.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/siteactive.py b/siteactive.py
index 3fb7173e9..171e43f90 100644
--- a/siteactive.py
+++ b/siteactive.py
@@ -4,7 +4,7 @@ __credits__ = ["webchk"]
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/skills.py b/skills.py
index 7f99acd9a..30cdae33b 100644
--- a/skills.py
+++ b/skills.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/socnet.py b/socnet.py
index 62b357b25..6df85be28 100644
--- a/socnet.py
+++ b/socnet.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Moderation"
diff --git a/speaker.py b/speaker.py
index 7d81a48e9..a63bfe581 100644
--- a/speaker.py
+++ b/speaker.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Accessibility"
diff --git a/ssb.py b/ssb.py
index 2c2a3af7a..44a43a2aa 100644
--- a/ssb.py
+++ b/ssb.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/tests.py b/tests.py
index 8ce77d41c..e544578b6 100644
--- a/tests.py
+++ b/tests.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Testing"
@@ -4469,7 +4469,7 @@ def _testLinksWithinPost() -> None:
port = 443
httpPrefix = 'https'
content = 'This is a test post with links.\n\n' + \
- 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://freedombone.net'
+ 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://libreserver.org'
followersOnly = False
saveToFile = False
clientToServer = False
@@ -4508,10 +4508,10 @@ def _testLinksWithinPost() -> None:
'ftp:// ' + \
'' + \
'ftp.ncdc.noaa.gov/pub/data/ghcn/v4/ ' + \
- '' + \
'https:// ' + \
- 'freedombone.net
'
+ 'libreserver.org '
assert postJsonObject['object']['content'] == \
postJsonObject['object']['contentMap'][systemLanguage]
@@ -5457,7 +5457,7 @@ def _testCanReplyTo() -> None:
port = 443
httpPrefix = 'https'
content = 'This is a test post with links.\n\n' + \
- 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://freedombone.net'
+ 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://libreserver.org'
followersOnly = False
saveToFile = False
clientToServer = False
diff --git a/theme.py b/theme.py
index 232898a9f..393206d5f 100644
--- a/theme.py
+++ b/theme.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/threads.py b/threads.py
index d49981f90..a8979b71c 100644
--- a/threads.py
+++ b/threads.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/tox.py b/tox.py
index ecb058673..4268991b4 100644
--- a/tox.py
+++ b/tox.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Profile Metadata"
diff --git a/utils.py b/utils.py
index 2042b248b..f81aafea1 100644
--- a/utils.py
+++ b/utils.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Core"
diff --git a/webapp_about.py b/webapp_about.py
index 607e08a6d..ad794d631 100644
--- a/webapp_about.py
+++ b/webapp_about.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_accesskeys.py b/webapp_accesskeys.py
index a8eba7c16..ebf26dae8 100644
--- a/webapp_accesskeys.py
+++ b/webapp_accesskeys.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Accessibility"
diff --git a/webapp_calendar.py b/webapp_calendar.py
index 9fbe1eaed..38d0743e2 100644
--- a/webapp_calendar.py
+++ b/webapp_calendar.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Calendar"
diff --git a/webapp_column_left.py b/webapp_column_left.py
index 33cd04a51..f6068e7d0 100644
--- a/webapp_column_left.py
+++ b/webapp_column_left.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface Columns"
diff --git a/webapp_column_right.py b/webapp_column_right.py
index b296027ee..74079928f 100644
--- a/webapp_column_right.py
+++ b/webapp_column_right.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface Columns"
diff --git a/webapp_confirm.py b/webapp_confirm.py
index 2bc63a939..3f22c4331 100644
--- a/webapp_confirm.py
+++ b/webapp_confirm.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_create_post.py b/webapp_create_post.py
index e7fa0e1cd..2ac46dff3 100644
--- a/webapp_create_post.py
+++ b/webapp_create_post.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_frontscreen.py b/webapp_frontscreen.py
index 3569d76a2..104a27361 100644
--- a/webapp_frontscreen.py
+++ b/webapp_frontscreen.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/webapp_hashtagswarm.py b/webapp_hashtagswarm.py
index 8cb784d8b..6eca2a7d7 100644
--- a/webapp_hashtagswarm.py
+++ b/webapp_hashtagswarm.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_headerbuttons.py b/webapp_headerbuttons.py
index 8b9213e50..507835bbf 100644
--- a/webapp_headerbuttons.py
+++ b/webapp_headerbuttons.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/webapp_login.py b/webapp_login.py
index c8d6e50a0..66b0ec192 100644
--- a/webapp_login.py
+++ b/webapp_login.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_media.py b/webapp_media.py
index b83b298d1..aa01b74b4 100644
--- a/webapp_media.py
+++ b/webapp_media.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/webapp_minimalbutton.py b/webapp_minimalbutton.py
index 716f3ba55..8b6e6f6e4 100644
--- a/webapp_minimalbutton.py
+++ b/webapp_minimalbutton.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/webapp_moderation.py b/webapp_moderation.py
index c1a195060..3064dc643 100644
--- a/webapp_moderation.py
+++ b/webapp_moderation.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Moderation"
diff --git a/webapp_person_options.py b/webapp_person_options.py
index ae5bdbc56..f5ea2b3b5 100644
--- a/webapp_person_options.py
+++ b/webapp_person_options.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_post.py b/webapp_post.py
index d2c88f5af..26e8e020c 100644
--- a/webapp_post.py
+++ b/webapp_post.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_profile.py b/webapp_profile.py
index 0ee01a0da..a58661cb6 100644
--- a/webapp_profile.py
+++ b/webapp_profile.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_question.py b/webapp_question.py
index bf198642e..51dd69af8 100644
--- a/webapp_question.py
+++ b/webapp_question.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_search.py b/webapp_search.py
index 9ec76b1af..9336bb27a 100644
--- a/webapp_search.py
+++ b/webapp_search.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_suspended.py b/webapp_suspended.py
index 40f8f4699..bcb69d277 100644
--- a/webapp_suspended.py
+++ b/webapp_suspended.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_timeline.py b/webapp_timeline.py
index a4c158c77..bf3a07ec2 100644
--- a/webapp_timeline.py
+++ b/webapp_timeline.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Timeline"
diff --git a/webapp_tos.py b/webapp_tos.py
index 6f9a6ba14..e96575e9b 100644
--- a/webapp_tos.py
+++ b/webapp_tos.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_utils.py b/webapp_utils.py
index cae7cfd54..1ed02e6e1 100644
--- a/webapp_utils.py
+++ b/webapp_utils.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
diff --git a/webapp_welcome.py b/webapp_welcome.py
index 11bca121c..24f687094 100644
--- a/webapp_welcome.py
+++ b/webapp_welcome.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Onboarding"
diff --git a/webapp_welcome_final.py b/webapp_welcome_final.py
index de5940199..088151840 100644
--- a/webapp_welcome_final.py
+++ b/webapp_welcome_final.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Onboarding"
diff --git a/webapp_welcome_profile.py b/webapp_welcome_profile.py
index e6a4d7ce8..d708df833 100644
--- a/webapp_welcome_profile.py
+++ b/webapp_welcome_profile.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Onboarding"
diff --git a/webfinger.py b/webfinger.py
index bf87c4c6d..1b50e1f12 100644
--- a/webfinger.py
+++ b/webfinger.py
@@ -3,7 +3,7 @@ __author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
-__email__ = "bob@freedombone.net"
+__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "ActivityPub"
diff --git a/website/EN/index.html b/website/EN/index.html
index 456348e37..a30abfcdd 100644
--- a/website/EN/index.html
+++ b/website/EN/index.html
@@ -11,8 +11,8 @@
"author" : {
"@type" : "Person",
"name" : "Bob Mottram",
- "email": "bob@freedombone.net",
- "url": "https://epicyon.freedombone.net/users/bob"
+ "email": "bob@libreserver.org",
+ "url": "https://epicyon.libreserver.org/users/bob"
},
"applicationCategory" : ["server", "software", "bash", "debian", "linux", "self-hosting"],
"downloadUrl" : "https://libreserver.org/epicyon/epicyon.tar.gz"
@@ -1467,7 +1467,7 @@
If you need to use fail2ban then failed login attempts can be found in accounts/loginfailures.log .
- If you are using the Caddy web server then see caddy.example.conf
+ If you are using the Caddy web server then see caddy.example.conf
Now you can navigate to your domain and register an account. The first account becomes the administrator.
@@ -1495,6 +1495,6 @@ Please be aware that such installations will not federate with ordinary fedivers