diff --git a/announce.py b/announce.py
index 00804d2b..5916f55a 100644
--- a/announce.py
+++ b/announce.py
@@ -129,7 +129,7 @@ def createAnnounce(session, baseDir: str, federationList: [],
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'atomUri': atomUriStr,
'cc': [],
- 'id': newAnnounceId+'/activity',
+ 'id': newAnnounceId + '/activity',
'object': objectUrl,
'published': published,
'to': [toUrl],
@@ -365,7 +365,7 @@ def sendAnnounceViaServer(baseDir: str, session,
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'atomUri': newAnnounceId,
'cc': [ccUrl],
- 'id': newAnnounceId+'/activity',
+ 'id': newAnnounceId + '/activity',
'object': repeatObjectUrl,
'published': published,
'to': [toUrl],
diff --git a/blocking.py b/blocking.py
index 0c57af78..8d6436f3 100644
--- a/blocking.py
+++ b/blocking.py
@@ -7,6 +7,7 @@ __email__ = "bob@freedombone.net"
__status__ = "Production"
import os
+from utils import removeIdEnding
from utils import isEvil
from utils import locatePost
from utils import evilIncarnate
@@ -214,7 +215,7 @@ def outboxBlock(baseDir: str, httpPrefix: str,
if debug:
print('DEBUG: c2s block request arrived in outbox')
- messageId = messageJson['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object'])
if '/statuses/' not in messageId:
if debug:
print('DEBUG: c2s block object is not a status')
@@ -293,7 +294,7 @@ def outboxUndoBlock(baseDir: str, httpPrefix: str,
if debug:
print('DEBUG: c2s undo block request arrived in outbox')
- messageId = messageJson['object']['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object']['object'])
if '/statuses/' not in messageId:
if debug:
print('DEBUG: c2s undo block object is not a status')
diff --git a/bookmarks.py b/bookmarks.py
index b7f1fa82..44b95197 100644
--- a/bookmarks.py
+++ b/bookmarks.py
@@ -8,6 +8,7 @@ __status__ = "Production"
import os
from pprint import pprint
+from utils import removeIdEnding
from utils import removePostFromCache
from utils import urlPermitted
from utils import getNicknameFromActor
@@ -607,7 +608,7 @@ def outboxBookmark(recentPostsCache: {},
if debug:
print('DEBUG: c2s bookmark request arrived in outbox')
- messageId = messageJson['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object'])
if ':' in domain:
domain = domain.split(':')[0]
postFilename = locatePost(baseDir, nickname, domain, messageId)
@@ -667,7 +668,7 @@ def outboxUndoBookmark(recentPostsCache: {},
if debug:
print('DEBUG: c2s undo bookmark request arrived in outbox')
- messageId = messageJson['object']['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object']['object'])
if ':' in domain:
domain = domain.split(':')[0]
postFilename = locatePost(baseDir, nickname, domain, messageId)
diff --git a/daemon.py b/daemon.py
index a4909021..dd45e6e2 100644
--- a/daemon.py
+++ b/daemon.py
@@ -127,6 +127,7 @@ from webinterface import htmlIndividualPost
from webinterface import htmlProfile
from webinterface import htmlInbox
from webinterface import htmlBookmarks
+from webinterface import htmlEvents
from webinterface import htmlShares
from webinterface import htmlOutbox
from webinterface import htmlModeration
@@ -154,6 +155,7 @@ from shares import getSharesFeedForPerson
from shares import addShare
from shares import removeShare
from shares import expireShares
+from utils import removeIdEnding
from utils import updateLikesCollection
from utils import undoLikesCollectionEntry
from utils import deletePost
@@ -2734,10 +2736,9 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime, GETtimings, 32)
-# unrepeatPrivate = False
if htmlGET and '?unrepeatprivate=' in self.path:
self.path = self.path.replace('?unrepeatprivate=', '?unrepeat=')
-# unrepeatPrivate = True
+
# undo an announce/repeat from the web interface
if htmlGET and '?unrepeat=' in self.path:
pageNumber = 1
@@ -4823,6 +4824,100 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy = False
return
+ # get the events for a given person
+ if self.path.endswith('/tlevents') or \
+ '/tlevents?page=' in self.path or \
+ self.path.endswith('/events') or \
+ '/events?page=' in self.path:
+ if '/users/' in self.path:
+ if authorized:
+ eventsFeed = \
+ personBoxJson(self.server.recentPostsCache,
+ self.server.session,
+ self.server.baseDir,
+ self.server.domain,
+ self.server.port,
+ self.path,
+ self.server.httpPrefix,
+ maxPostsInFeed, 'tlevents',
+ authorized, self.server.ocapAlways)
+ if eventsFeed:
+ if self._requestHTTP():
+ nickname = self.path.replace('/users/', '')
+ nickname = nickname.replace('/tlevents', '')
+ nickname = nickname.replace('/events', '')
+ pageNumber = 1
+ if '?page=' in nickname:
+ pageNumber = nickname.split('?page=')[1]
+ nickname = nickname.split('?page=')[0]
+ if pageNumber.isdigit():
+ pageNumber = int(pageNumber)
+ else:
+ pageNumber = 1
+ if 'page=' not in self.path:
+ # if no page was specified then show the first
+ eventsFeed = \
+ personBoxJson(self.server.recentPostsCache,
+ self.server.session,
+ self.server.baseDir,
+ self.server.domain,
+ self.server.port,
+ self.path + '?page=1',
+ self.server.httpPrefix,
+ maxPostsInFeed,
+ 'tlevents',
+ authorized,
+ self.server.ocapAlways)
+ msg = \
+ htmlEvents(self.server.defaultTimeline,
+ self.server.recentPostsCache,
+ self.server.maxRecentPosts,
+ self.server.translate,
+ pageNumber, maxPostsInFeed,
+ self.server.session,
+ self.server.baseDir,
+ self.server.cachedWebfingers,
+ self.server.personCache,
+ nickname,
+ self.server.domain,
+ self.server.port,
+ eventsFeed,
+ self.server.allowDeletion,
+ self.server.httpPrefix,
+ self.server.projectVersion,
+ self._isMinimal(nickname),
+ self.server.YTReplacementDomain)
+ msg = msg.encode('utf-8')
+ self._set_headers('text/html',
+ len(msg),
+ cookie, callingDomain)
+ self._write(msg)
+ else:
+ # don't need authenticated fetch here because
+ # there is already the authorization check
+ msg = json.dumps(inboxFeed,
+ ensure_ascii=False)
+ msg = msg.encode('utf-8')
+ self._set_headers('application/json',
+ len(msg),
+ None, callingDomain)
+ self._write(msg)
+ self.server.GETbusy = False
+ return
+ else:
+ if self.server.debug:
+ nickname = self.path.replace('/users/', '')
+ nickname = nickname.replace('/tlevents', '')
+ nickname = nickname.replace('/events', '')
+ print('DEBUG: ' + nickname +
+ ' was not authorized to access ' + self.path)
+ if self.server.debug:
+ print('DEBUG: GET access to events is unauthorized')
+ self.send_response(405)
+ self.end_headers()
+ self.server.GETbusy = False
+ return
+
self._benchmarkGETtimings(GETstartTime, GETtimings, 47)
# get outbox feed for a person
@@ -7892,7 +7987,7 @@ class PubServer(BaseHTTPRequestHandler):
followId = followActor + '/statuses/' + str(statusNumber)
unfollowJson = {
'@context': 'https://www.w3.org/ns/activitystreams',
- 'id': followId+'/undo',
+ 'id': followId + '/undo',
'type': 'Undo',
'actor': followActor,
'object': {
@@ -8682,8 +8777,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.outboxAuthenticated:
if self._postToOutbox(messageJson, __version__):
if messageJson.get('id'):
- locnStr = messageJson['id'].replace('/activity', '')
- locnStr = locnStr.replace('/undo', '')
+ locnStr = removeIdEnding(messageJson['id'])
self.headers['Location'] = locnStr
self.send_response(201)
self.end_headers()
diff --git a/delete.py b/delete.py
index f7758544..1b537d39 100644
--- a/delete.py
+++ b/delete.py
@@ -6,6 +6,7 @@ __maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
+from utils import removeIdEnding
from utils import getStatusNumber
from utils import urlPermitted
from utils import getNicknameFromActor
@@ -257,7 +258,7 @@ def outboxDelete(baseDir: str, httpPrefix: str,
if debug:
print('DEBUG: delete not permitted from other instances')
return
- messageId = messageJson['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object'])
if '/statuses/' not in messageId:
if debug:
print('DEBUG: c2s delete object is not a status')
diff --git a/inbox.py b/inbox.py
index 4250f8d7..25113f72 100644
--- a/inbox.py
+++ b/inbox.py
@@ -10,6 +10,7 @@ import json
import os
import datetime
import time
+from utils import removeIdEnding
from utils import getProtocolPrefixes
from utils import isBlogPost
from utils import removeAvatarFromCache
@@ -93,7 +94,7 @@ def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
continue
tagName = tag['name'].replace('#', '').strip()
tagsFilename = tagsDir + '/' + tagName + '.txt'
- postUrl = postJsonObject['id'].replace('/activity', '')
+ postUrl = removeIdEnding(postJsonObject['id'])
postUrl = postUrl.replace('/', '#')
daysDiff = datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)
daysSinceEpoch = daysDiff.days
@@ -354,15 +355,13 @@ def savePostToInboxQueue(baseDir: str, httpPrefix: str,
return None
originalPostId = None
if postJsonObject.get('id'):
- originalPostId = \
- postJsonObject['id'].replace('/activity', '').replace('/undo', '')
+ originalPostId = removeIdEnding(postJsonObject['id'])
currTime = datetime.datetime.utcnow()
postId = None
if postJsonObject.get('id'):
- postId = postJsonObject['id'].replace('/activity', '')
- postId = postId.replace('/undo', '')
+ postId = removeIdEnding(postJsonObject['id'])
published = currTime.strftime("%Y-%m-%dT%H:%M:%SZ")
if not postId:
statusNumber, published = getStatusNumber()
@@ -817,9 +816,7 @@ def receiveEventPost(recentPostsCache: {}, session, baseDir: str,
if port != 80 and port != 443:
handle += ':' + str(port)
- postId = \
- messageJson['id'].replace('/activity', '').replace('/', '#')
- postId = postId.replace('/event', '')
+ postId = removeIdEnding(messageJson['id']).replace('/', '#')
saveEventPost(baseDir, handle, postId, messageJson['object'])
@@ -910,7 +907,7 @@ def receiveUpdateToQuestion(recentPostsCache: {}, messageJson: {},
return
if not messageJson.get('actor'):
return
- messageId = messageJson['id'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['id'])
if '#' in messageId:
messageId = messageId.split('#', 1)[0]
# find the question post
@@ -1367,8 +1364,7 @@ def receiveDelete(session, handle: str, isGroup: bool, baseDir: str,
if not os.path.isdir(baseDir + '/accounts/' + handle):
print('DEBUG: unknown recipient of like - ' + handle)
# if this post in the outbox of the person?
- messageId = messageJson['object'].replace('/activity', '')
- messageId = messageId.replace('/undo', '')
+ messageId = removeIdEnding(messageJson['object'])
removeModerationPostFromIndex(baseDir, messageId, debug)
postFilename = locatePost(baseDir, handle.split('@')[0],
handle.split('@')[1], messageId)
@@ -1653,8 +1649,7 @@ def populateReplies(baseDir: str, httpPrefix: str, domain: str,
return False
# populate a text file containing the ids of replies
postRepliesFilename = postFilename.replace('.json', '.replies')
- messageId = messageJson['id'].replace('/activity', '')
- messageId = messageId.replace('/undo', '')
+ messageId = removeIdEnding(messageJson['id'])
if os.path.isfile(postRepliesFilename):
numLines = sum(1 for line in open(postRepliesFilename))
if numLines > maxReplies:
@@ -2070,8 +2065,7 @@ def inboxUpdateCalendar(baseDir: str, handle: str, postJsonObject: {}) -> None:
actorNickname, actorDomain):
return
- postId = \
- postJsonObject['id'].replace('/activity', '').replace('/', '#')
+ postId = removeIdEnding(postJsonObject['id']).replace('/', '#')
# look for events within the tags list
for tagDict in postJsonObject['object']['tag']:
@@ -2375,6 +2369,9 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int,
if isBlogPost(postJsonObject):
# blogs index will be updated
updateIndexList.append('tlblogs')
+ elif isEventPost(postJsonObject):
+ # events index will be updated
+ updateIndexList.append('tlevents')
# get the avatar for a reply/announce
obtainAvatarForReplyPost(session, baseDir,
diff --git a/like.py b/like.py
index f3d127dc..fb38d810 100644
--- a/like.py
+++ b/like.py
@@ -6,6 +6,7 @@ __maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
+from utils import removeIdEnding
from utils import urlPermitted
from utils import getNicknameFromActor
from utils import getDomainFromActor
@@ -411,7 +412,7 @@ def outboxLike(recentPostsCache: {},
if debug:
print('DEBUG: c2s like request arrived in outbox')
- messageId = messageJson['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object'])
if ':' in domain:
domain = domain.split(':')[0]
postFilename = locatePost(baseDir, nickname, domain, messageId)
@@ -462,7 +463,7 @@ def outboxUndoLike(recentPostsCache: {},
if debug:
print('DEBUG: c2s undo like request arrived in outbox')
- messageId = messageJson['object']['object'].replace('/activity', '')
+ messageId = removeIdEnding(messageJson['object']['object'])
if ':' in domain:
domain = domain.split(':')[0]
postFilename = locatePost(baseDir, nickname, domain, messageId)
diff --git a/outbox.py b/outbox.py
index 8b99ba70..805d24b3 100644
--- a/outbox.py
+++ b/outbox.py
@@ -13,6 +13,7 @@ from posts import outboxMessageCreateWrap
from posts import savePostToBox
from posts import sendToFollowersThread
from posts import sendToNamedAddresses
+from utils import removeIdEnding
from utils import getDomainFromActor
from blocking import isBlockedDomain
from blocking import outboxBlock
@@ -159,8 +160,7 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str,
' is not a permitted activity type')
return False
if messageJson.get('id'):
- postId = \
- messageJson['id'].replace('/activity', '').replace('/undo', '')
+ postId = removeIdEnding(messageJson['id'])
if debug:
print('DEBUG: id attribute exists within POST to outbox')
else:
@@ -172,13 +172,15 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str,
if messageJson['type'] != 'Upgrade':
outboxName = 'outbox'
- # if this is a blog post then save to its own box
+ # if this is a blog post or an event then save to its own box
if messageJson['type'] == 'Create':
if messageJson.get('object'):
if isinstance(messageJson['object'], dict):
if messageJson['object'].get('type'):
if messageJson['object']['type'] == 'Article':
outboxName = 'tlblogs'
+ elif messageJson['object']['type'] == 'Event':
+ outboxName = 'tlevents'
savedFilename = \
savePostToBox(baseDir,
@@ -196,7 +198,8 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str,
messageJson['type'] == 'Announce':
indexes = [outboxName, "inbox"]
for boxNameIndex in indexes:
- if boxNameIndex == 'inbox' and outboxName == 'tlblogs':
+ if ((boxNameIndex == 'inbox') and
+ (outboxName == 'tlblogs' or outboxName == 'tlevents')):
continue
selfActor = \
httpPrefix + '://' + domainFull + \
diff --git a/person.py b/person.py
index 2519a426..4f0477bc 100644
--- a/person.py
+++ b/person.py
@@ -25,6 +25,7 @@ from posts import createRepliesTimeline
from posts import createMediaTimeline
from posts import createBlogsTimeline
from posts import createBookmarksTimeline
+from posts import createEventsTimeline
from posts import createInbox
from posts import createOutbox
from posts import createModeration
@@ -598,7 +599,8 @@ def personBoxJson(recentPostsCache: {},
boxname != 'tlreplies' and boxname != 'tlmedia' and \
boxname != 'tlblogs' and \
boxname != 'outbox' and boxname != 'moderation' and \
- boxname != 'tlbookmarks' and boxname != 'bookmarks':
+ boxname != 'tlbookmarks' and boxname != 'bookmarks' and \
+ boxname != 'tlevents' and boxname != 'events':
return None
if not '/' + boxname in path:
@@ -646,6 +648,11 @@ def personBoxJson(recentPostsCache: {},
port, httpPrefix,
noOfItems, headerOnly, ocapAlways,
pageNumber)
+ elif boxname == 'tlevents' or boxname == 'events':
+ return createEventsTimeline(session, baseDir, nickname, domain,
+ port, httpPrefix,
+ noOfItems, headerOnly, ocapAlways,
+ pageNumber)
elif boxname == 'tlreplies':
return createRepliesTimeline(session, baseDir, nickname, domain,
port, httpPrefix,
diff --git a/posts.py b/posts.py
index 1f802341..fbd956f9 100644
--- a/posts.py
+++ b/posts.py
@@ -29,6 +29,7 @@ from session import postJsonString
from session import postImage
from webfinger import webfingerHandle
from httpsig import createSignedHeader
+from utils import removeIdEnding
from utils import siteIsActive
from utils import removePostFromCache
from utils import getCachedPostFilename
@@ -502,7 +503,8 @@ def deleteAllPosts(baseDir: str,
nickname: str, domain: str, boxname: str) -> None:
"""Deletes all posts for a person from inbox or outbox
"""
- if boxname != 'inbox' and boxname != 'outbox' and boxname != 'tlblogs':
+ if boxname != 'inbox' and boxname != 'outbox' and \
+ boxname != 'tlblogs' and boxname != 'tlevents':
return
boxDir = createPersonDir(nickname, domain, baseDir, boxname)
for deleteFilename in os.scandir(boxDir):
@@ -524,7 +526,8 @@ def savePostToBox(baseDir: str, httpPrefix: str, postId: str,
Returns the filename
"""
if boxname != 'inbox' and boxname != 'outbox' and \
- boxname != 'tlblogs' and boxname != 'scheduled':
+ boxname != 'tlblogs' and boxname != 'tlevents' and \
+ boxname != 'scheduled':
return None
originalDomain = domain
if ':' in domain:
@@ -845,7 +848,7 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
'/statuses/' + statusNumber + '/replies'
newPost = {
'@context': postContext,
- 'id': newPostId+'/activity',
+ 'id': newPostId + '/activity',
'capability': capabilityIdList,
'type': 'Create',
'actor': actorUrl,
@@ -977,12 +980,15 @@ def createPostBase(baseDir: str, nickname: str, domain: str, port: int,
'date and time values')
return newPost
elif saveToFile:
- if not isArticle:
- savePostToBox(baseDir, httpPrefix, newPostId,
- nickname, domain, newPost, 'outbox')
- else:
+ if isArticle:
savePostToBox(baseDir, httpPrefix, newPostId,
nickname, domain, newPost, 'tlblogs')
+ elif eventUUID:
+ savePostToBox(baseDir, httpPrefix, newPostId,
+ nickname, domain, newPost, 'tlevents')
+ else:
+ savePostToBox(baseDir, httpPrefix, newPostId,
+ nickname, domain, newPost, 'outbox')
return newPost
@@ -1009,10 +1015,10 @@ def outboxMessageCreateWrap(httpPrefix: str,
capabilityUrl = []
newPost = {
"@context": "https://www.w3.org/ns/activitystreams",
- 'id': newPostId+'/activity',
+ 'id': newPostId + '/activity',
'capability': capabilityUrl,
'type': 'Create',
- 'actor': httpPrefix+'://'+domain+'/users/'+nickname,
+ 'actor': httpPrefix + '://' + domain + '/users/' + nickname,
'published': published,
'to': messageJson['to'],
'cc': cc,
@@ -1473,8 +1479,9 @@ def createReportPost(baseDir: str,
continue
# update the inbox index with the report filename
- # indexFilename=baseDir+'/accounts/'+handle+'/inbox.index'
- # indexEntry=postJsonObject['id'].replace('/activity','').replace('/','#')+'.json'
+ # indexFilename = baseDir+'/accounts/'+handle+'/inbox.index'
+ # indexEntry = \
+ # removeIdEnding(postJsonObject['id']).replace('/','#') + '.json'
# if indexEntry not in open(indexFilename).read():
# try:
# with open(indexFilename, 'a+') as fp:
@@ -2419,6 +2426,16 @@ def createBookmarksTimeline(session, baseDir: str, nickname: str, domain: str,
True, ocapAlways, pageNumber)
+def createEventsTimeline(session, baseDir: str, nickname: str, domain: str,
+ port: int, httpPrefix: str, itemsPerPage: int,
+ headerOnly: bool, ocapAlways: bool,
+ pageNumber=None) -> {}:
+ return createBoxIndexed({}, session, baseDir, 'tlevents',
+ nickname, domain,
+ port, httpPrefix, itemsPerPage, headerOnly,
+ True, ocapAlways, pageNumber)
+
+
def createDMTimeline(session, baseDir: str, nickname: str, domain: str,
port: int, httpPrefix: str, itemsPerPage: int,
headerOnly: bool, ocapAlways: bool,
@@ -2791,14 +2808,19 @@ def createBoxIndexed(recentPostsCache: {},
boxname != 'tlreplies' and boxname != 'tlmedia' and \
boxname != 'tlblogs' and \
boxname != 'outbox' and boxname != 'tlbookmarks' and \
- boxname != 'bookmarks':
+ boxname != 'bookmarks' and \
+ boxname != 'tlevents' and boxname != 'events':
return None
- # bookmarks timeline is like the inbox but has its own separate index
+ # bookmarks and events timelines are like the inbox
+ # but have their own separate index
indexBoxName = boxname
if boxname == "tlbookmarks":
boxname = "bookmarks"
indexBoxName = boxname
+ elif boxname == "tlevents":
+ boxname = "events"
+ indexBoxName = boxname
if port:
if port != 80 and port != 443:
@@ -3489,7 +3511,7 @@ def mutePost(baseDir: str, nickname: str, domain: str, postId: str,
# if the post is in the recent posts cache then mark it as muted
if recentPostsCache.get('index'):
postId = \
- postJsonObject['id'].replace('/activity', '').replace('/', '#')
+ removeIdEnding(postJsonObject['id']).replace('/', '#')
if postId in recentPostsCache['index']:
print('MUTE: ' + postId + ' is in recent posts cache')
if recentPostsCache['json'].get(postId):
diff --git a/tests.py b/tests.py
index 69095aa4..6109dead 100644
--- a/tests.py
+++ b/tests.py
@@ -31,6 +31,7 @@ from follow import clearFollows
from follow import clearFollowers
from follow import sendFollowRequestViaServer
from follow import sendUnfollowRequestViaServer
+from utils import removeIdEnding
from utils import siteIsActive
from utils import updateRecentPostsCache
from utils import followPerson
@@ -1364,7 +1365,7 @@ def testClientToServer():
outboxPostFilename = outboxPath + '/' + name
postJsonObject = loadJson(outboxPostFilename, 0)
if postJsonObject:
- outboxPostId = postJsonObject['id'].replace('/activity', '')
+ outboxPostId = removeIdEnding(postJsonObject['id'])
assert outboxPostId
print('message id obtained: ' + outboxPostId)
assert validInbox(bobDir, 'bob', bobDomain)
@@ -2014,8 +2015,35 @@ def testJsonPostAllowsComments():
assert not jsonPostAllowsComments(postJsonObject)
+def testRemoveIdEnding():
+ print('testRemoveIdEnding')
+ testStr = 'https://activitypub.somedomain.net'
+ resultStr = removeIdEnding(testStr)
+ assert resultStr == 'https://activitypub.somedomain.net'
+
+ testStr = \
+ 'https://activitypub.somedomain.net/users/foo/' + \
+ 'statuses/34544814814/activity'
+ resultStr = removeIdEnding(testStr)
+ assert resultStr == \
+ 'https://activitypub.somedomain.net/users/foo/statuses/34544814814'
+
+ testStr = \
+ 'https://undo.somedomain.net/users/foo/statuses/34544814814/undo'
+ resultStr = removeIdEnding(testStr)
+ assert resultStr == \
+ 'https://undo.somedomain.net/users/foo/statuses/34544814814'
+
+ testStr = \
+ 'https://event.somedomain.net/users/foo/statuses/34544814814/event'
+ resultStr = removeIdEnding(testStr)
+ assert resultStr == \
+ 'https://event.somedomain.net/users/foo/statuses/34544814814'
+
+
def runAllTests():
print('Running tests...')
+ testRemoveIdEnding()
testJsonPostAllowsComments()
runHtmlReplaceQuoteMarks()
testDangerousMarkup()
diff --git a/translations/ar.json b/translations/ar.json
index 38afd2b6..56c889fa 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -258,6 +258,7 @@
"Notes": "ملاحظات",
"Allow replies.": "السماح بالردود.",
"Event": "حدث",
+ "Events": "الأحداث",
"Create an event": "أنشئ حدثًا",
"Describe the event": "صف الحدث",
"Start Date": "تاريخ البدء",
diff --git a/translations/ca.json b/translations/ca.json
index aea20a99..3e420c60 100644
--- a/translations/ca.json
+++ b/translations/ca.json
@@ -258,6 +258,7 @@
"Notes": "Notes",
"Allow replies.": "Permetre respostes.",
"Event": "Esdeveniment",
+ "Events": "Esdeveniments",
"Create an event": "Crea un esdeveniment",
"Describe the event": "Descriviu l’esdeveniment",
"Start Date": "Data d'inici",
diff --git a/translations/cy.json b/translations/cy.json
index e84bd97e..e7bd2c8b 100644
--- a/translations/cy.json
+++ b/translations/cy.json
@@ -258,6 +258,7 @@
"Notes": "Nodiadau",
"Allow replies.": "Caniatáu atebion.",
"Event": "Digwyddiad",
+ "Events": "Digwyddiadau",
"Create an event": "Creu digwyddiad",
"Describe the event": "Disgrifiwch y digwyddiad",
"Start Date": "Dyddiad cychwyn",
diff --git a/translations/de.json b/translations/de.json
index a3e57336..a1029eb3 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -258,6 +258,7 @@
"Notes": "Anmerkungen",
"Allow replies.": "Antworten zulassen.",
"Event": "Veranstaltung",
+ "Events": "Veranstaltungen",
"Create an event": "Erstellen Sie ein Ereignis",
"Describe the event": "Beschreiben Sie das Ereignis",
"Start Date": "Anfangsdatum",
diff --git a/translations/en.json b/translations/en.json
index e920d2d3..d5f957fd 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -258,6 +258,7 @@
"Notes": "Notes",
"Allow replies.": "Allow replies.",
"Event": "Event",
+ "Events": "Events",
"Create an event": "Create an event",
"Describe the event": "Describe the event",
"Start Date": "Start Date",
diff --git a/translations/es.json b/translations/es.json
index 9e3096dc..907814a6 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -258,6 +258,7 @@
"Notes": "Notas",
"Allow replies.": "Permitir respuestas.",
"Event": "Evento",
+ "Events": "Eventos",
"Create an event": "Crea un evento",
"Describe the event": "Describe el evento",
"Start Date": "Fecha de inicio",
diff --git a/translations/fr.json b/translations/fr.json
index 4bccf438..3cd15ff3 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -258,6 +258,7 @@
"Notes": "Remarques",
"Allow replies.": "Autoriser les réponses.",
"Event": "un événement",
+ "Events": "Événements",
"Create an event": "Créer un événement",
"Describe the event": "Décrivez l'événement",
"Start Date": "Date de début",
diff --git a/translations/ga.json b/translations/ga.json
index 35d6463f..bc7237e4 100644
--- a/translations/ga.json
+++ b/translations/ga.json
@@ -258,6 +258,7 @@
"Notes": "Nótaí",
"Allow replies.": "Ceadaigh freagraí.",
"Event": "Imeacht",
+ "Events": "Imeachtaí",
"Create an event": "Cruthaigh imeacht",
"Describe the event": "Déan cur síos ar an ócáid",
"Start Date": "Dáta tosaigh",
diff --git a/translations/hi.json b/translations/hi.json
index 4e1be87b..fea37de5 100644
--- a/translations/hi.json
+++ b/translations/hi.json
@@ -258,6 +258,7 @@
"Notes": "टिप्पणियाँ",
"Allow replies.": "जवाब दें।",
"Event": "प्रतिस्पर्धा",
+ "Events": "आयोजन",
"Create an event": "एक घटना बनाएँ",
"Describe the event": "घटना का वर्णन करें",
"Start Date": "आरंभ करने की तिथि",
diff --git a/translations/it.json b/translations/it.json
index b73d94cc..cf22239e 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -258,6 +258,7 @@
"Notes": "Appunti",
"Allow replies.": "Consenti risposte.",
"Event": "Evento",
+ "Events": "Eventi",
"Create an event": "Crea un evento",
"Describe the event": "Descrivi l'evento",
"Start Date": "Data d'inizio",
diff --git a/translations/ja.json b/translations/ja.json
index 01c8dc3b..6a024bcf 100644
--- a/translations/ja.json
+++ b/translations/ja.json
@@ -258,6 +258,7 @@
"Notes": "ノート",
"Allow replies.": "返信を許可します。",
"Event": "イベント",
+ "Events": "イベント",
"Create an event": "イベントを作成する",
"Describe the event": "イベントについて説明する",
"Start Date": "開始日",
diff --git a/translations/oc.json b/translations/oc.json
index 96371bec..b38fdf8d 100644
--- a/translations/oc.json
+++ b/translations/oc.json
@@ -254,6 +254,7 @@
"Notes": "Notes",
"Allow replies.": "Allow replies.",
"Event": "Event",
+ "Events": "Events",
"Create an event": "Create an event",
"Describe the event": "Describe the event",
"Start Date": "Start Date",
diff --git a/translations/pt.json b/translations/pt.json
index cac28c6c..86cef14a 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -258,6 +258,7 @@
"Notes": "Notas",
"Allow replies.": "Permitir respostas.",
"Event": "Evento",
+ "Events": "Eventos",
"Create an event": "Crie um evento",
"Describe the event": "Descreva o evento",
"Start Date": "Data de início",
diff --git a/translations/ru.json b/translations/ru.json
index 2be8bfc5..0bdedc6a 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -258,6 +258,7 @@
"Notes": "Ноты",
"Allow replies.": "Разрешить ответы.",
"Event": "Мероприятие",
+ "Events": "События",
"Create an event": "Создать мероприятие",
"Describe the event": "Опишите событие",
"Start Date": "Дата начала",
diff --git a/translations/zh.json b/translations/zh.json
index 0fb87c97..d01c60b9 100644
--- a/translations/zh.json
+++ b/translations/zh.json
@@ -258,6 +258,7 @@
"Notes": "笔记",
"Allow replies.": "允许回复。",
"Event": "事件",
+ "Events": "大事记",
"Create an event": "建立活动",
"Describe the event": "描述事件",
"Start Date": "开始日期",
diff --git a/utils.py b/utils.py
index 3d9720fb..3f36d25b 100644
--- a/utils.py
+++ b/utils.py
@@ -19,6 +19,18 @@ from calendar import monthrange
from followingCalendar import addPersonToCalendar
+def removeIdEnding(idStr: str) -> str:
+ """Removes endings such as /activity and /undo
+ """
+ if idStr.endswith('/activity'):
+ idStr = idStr[:-len('/activity')]
+ elif idStr.endswith('/undo'):
+ idStr = idStr[:-len('/undo')]
+ elif idStr.endswith('/event'):
+ idStr = idStr[:-len('/event')]
+ return idStr
+
+
def getProtocolPrefixes() -> []:
"""Returns a list of valid prefixes
"""
@@ -384,13 +396,13 @@ def locatePost(baseDir: str, nickname: str, domain: str,
extension = 'replies'
# if this post in the shared inbox?
- postUrl = postUrl.replace('/', '#').replace('/activity', '').strip()
+ postUrl = removeIdEnding(postUrl.strip()).replace('/', '#')
# add the extension
postUrl = postUrl + '.' + extension
# search boxes
- boxes = ('inbox', 'outbox', 'tlblogs')
+ boxes = ('inbox', 'outbox', 'tlblogs', 'tlevents')
accountDir = baseDir + '/accounts/' + nickname + '@' + domain + '/'
for boxName in boxes:
postFilename = accountDir + boxName + '/' + postUrl
@@ -435,7 +447,7 @@ def removeModerationPostFromIndex(baseDir: str, postUrl: str,
moderationIndexFile = baseDir + '/accounts/moderation.txt'
if not os.path.isfile(moderationIndexFile):
return
- postId = postUrl.replace('/activity', '')
+ postId = removeIdEnding(postUrl)
if postId in open(moderationIndexFile).read():
with open(moderationIndexFile, "r") as f:
lines = f.readlines()
@@ -463,7 +475,7 @@ def isReplyToBlogPost(baseDir: str, nickname: str, domain: str,
nickname + '@' + domain + '/tlblogs.index'
if not os.path.isfile(blogsIndexFilename):
return False
- postId = postJsonObject['object']['inReplyTo'].replace('/activity', '')
+ postId = removeIdEnding(postJsonObject['object']['inReplyTo'])
postId = postId.replace('/', '#')
if postId in open(blogsIndexFilename).read():
return True
@@ -494,7 +506,7 @@ def deletePost(baseDir: str, httpPrefix: str,
# remove from recent posts cache in memory
if recentPostsCache:
postId = \
- postJsonObject['id'].replace('/activity', '').replace('/', '#')
+ removeIdEnding(postJsonObject['id']).replace('/', '#')
if recentPostsCache.get('index'):
if postId in recentPostsCache['index']:
recentPostsCache['index'].remove(postId)
@@ -526,7 +538,7 @@ def deletePost(baseDir: str, httpPrefix: str,
if isinstance(postJsonObject['object'], dict):
if postJsonObject['object'].get('moderationStatus'):
if postJsonObject.get('id'):
- postId = postJsonObject['id'].replace('/activity', '')
+ postId = removeIdEnding(postJsonObject['id'])
removeModerationPostFromIndex(baseDir, postId, debug)
# remove any hashtags index entries
@@ -540,8 +552,7 @@ def deletePost(baseDir: str, httpPrefix: str,
if postJsonObject['object'].get('id') and \
postJsonObject['object'].get('tag'):
# get the id of the post
- postId = \
- postJsonObject['object']['id'].replace('/activity', '')
+ postId = removeIdEnding(postJsonObject['object']['id'])
for tag in postJsonObject['object']['tag']:
if tag['type'] != 'Hashtag':
continue
@@ -710,7 +721,7 @@ def getCachedPostFilename(baseDir: str, nickname: str, domain: str,
return None
cachedPostFilename = \
cachedPostDir + \
- '/' + postJsonObject['id'].replace('/activity', '').replace('/', '#')
+ '/' + removeIdEnding(postJsonObject['id']).replace('/', '#')
cachedPostFilename = cachedPostFilename + '.html'
return cachedPostFilename
@@ -727,7 +738,7 @@ def removePostFromCache(postJsonObject: {}, recentPostsCache: {}):
postId = postJsonObject['id']
if '#' in postId:
postId = postId.split('#', 1)[0]
- postId = postId.replace('/activity', '').replace('/', '#')
+ postId = removeIdEnding(postId).replace('/', '#')
if postId not in recentPostsCache['index']:
return
@@ -747,7 +758,7 @@ def updateRecentPostsCache(recentPostsCache: {}, maxRecentPosts: int,
postId = postJsonObject['id']
if '#' in postId:
postId = postId.split('#', 1)[0]
- postId = postId.replace('/activity', '').replace('/', '#')
+ postId = removeIdEnding(postId).replace('/', '#')
if recentPostsCache.get('index'):
if postId in recentPostsCache['index']:
return
@@ -1071,7 +1082,7 @@ def updateAnnounceCollection(recentPostsCache: {},
return
if not isinstance(postJsonObject['object'], dict):
return
- postUrl = postJsonObject['id'].replace('/activity', '') + '/shares'
+ postUrl = removeIdEnding(postJsonObject['id']) + '/shares'
if not postJsonObject['object'].get('shares'):
if debug:
print('DEBUG: Adding initial shares (announcements) to ' +
diff --git a/webinterface.py b/webinterface.py
index 4f6c8ce7..1eebed49 100644
--- a/webinterface.py
+++ b/webinterface.py
@@ -25,6 +25,7 @@ from ssb import getSSBAddress
from tox import getToxAddress
from matrix import getMatrixAddress
from donate import getDonationUrl
+from utils import removeIdEnding
from utils import getProtocolPrefixes
from utils import getFileCaseInsensitive
from utils import searchBoxPosts
@@ -3313,7 +3314,7 @@ def insertQuestion(baseDir: str, translate: {},
return content
if len(postJsonObject['object']['oneOf']) == 0:
return content
- messageId = postJsonObject['id'].replace('/activity', '')
+ messageId = removeIdEnding(postJsonObject['id'])
if '#' in messageId:
messageId = messageId.split('#', 1)[0]
pageNumberStr = ''
@@ -3781,7 +3782,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
avatarPosition = ''
messageId = ''
if postJsonObject.get('id'):
- messageId = postJsonObject['id'].replace('/activity', '')
+ messageId = removeIdEnding(postJsonObject['id'])
messageIdStr = ''
if messageId:
@@ -3870,7 +3871,7 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
if boxName == 'tlbookmarks' or boxName == 'bookmarks':
return ''
- timelinePostBookmark = postJsonObject['id'].replace('/activity', '')
+ timelinePostBookmark = removeIdEnding(postJsonObject['id'])
timelinePostBookmark = timelinePostBookmark.replace('://', '-')
timelinePostBookmark = timelinePostBookmark.replace('/', '-')
@@ -4007,8 +4008,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int,
editStr = ''
if fullDomain + '/users/' + nickname in postJsonObject['actor']:
- if isBlogPost(postJsonObject):
- if '/statuses/' in postJsonObject['object']['id']:
+ if '/statuses/' in postJsonObject['object']['id']:
+ if isBlogPost(postJsonObject):
editStr += \
'\n'
+ eventsButtonStr = \
+ '\n'
+
tlStr = htmlHeader(cssFilename, profileStyle)
if boxName != 'dm':
@@ -4835,7 +4845,7 @@ def htmlTimeline(defaultTimeline: str,
sentButton+'">' + translate['Outbox'] + \
'\n'
tlStr += \
- sharesButtonStr + bookmarksButtonStr + \
+ sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
moderationButtonStr + newPostButtonStr
tlStr += \
'