Adding bookmarks

main
Bob Mottram 2019-11-17 14:01:49 +00:00
parent 1a5677b118
commit 52e1d44021
21 changed files with 1126 additions and 26 deletions

667
bookmarks.py 100644
View File

@ -0,0 +1,667 @@
__filename__ = "bookmarks.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.0.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import os
import json
import time
import commentjson
from pprint import pprint
from utils import urlPermitted
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import locatePost
from utils import getCachedPostFilename
from utils import loadJson
from utils import saveJson
from posts import sendSignedJson
from session import postJson
from webfinger import webfingerHandle
from auth import createBasicAuthHeader
from posts import getPersonBox
def undoBookmarksCollectionEntry(baseDir: str,postFilename: str,objectUrl: str, \
actor: str,domain: str,debug: bool) -> None:
"""Undoes a bookmark for a particular actor
"""
postJsonObject=loadJson(postFilename)
if postJsonObject:
# remove any cached version of this post so that the bookmark icon is changed
nickname=getNicknameFromActor(actor)
cachedPostFilename= \
getCachedPostFilename(baseDir,nickname,domain,postJsonObject)
if os.path.isfile(cachedPostFilename):
os.remove(cachedPostFilename)
if not postJsonObject.get('type'):
return
if postJsonObject['type']!='Create':
return
if not postJsonObject.get('object'):
if debug:
pprint(postJsonObject)
print('DEBUG: post '+objectUrl+' has no object')
return
if not isinstance(postJsonObject['object'], dict):
return
if not postJsonObject['object'].get('bookmarks'):
return
if not isinstance(postJsonObject['object']['bookmarks'], dict):
return
if not postJsonObject['object']['bookmarks'].get('items'):
return
totalItems=0
if postJsonObject['object']['bookmarks'].get('totalItems'):
totalItems=postJsonObject['object']['bookmarks']['totalItems']
itemFound=False
for bookmarkItem in postJsonObject['object']['bookmarks']['items']:
if bookmarkItem.get('actor'):
if bookmarkItem['actor']==actor:
if debug:
print('DEBUG: bookmark was removed for '+actor)
postJsonObject['object']['bookmarks']['items'].remove(bookmarkItem)
itemFound=True
break
if itemFound:
if totalItems==1:
if debug:
print('DEBUG: bookmarks was removed from post')
del postJsonObject['object']['bookmarks']
else:
postJsonObject['object']['bookmarks']['totalItems']= \
len(postJsonObject['bookmarks']['items'])
saveJson(postJsonObject,postFilename)
# remove from the index
bookmarksIndexFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/bookmarks.index'
if os.path.isfile(bookmarksIndexFilename):
bookmarkIndex=postFilename.split('/')[-1]+'\n'
if bookmarkIndex in open(bookmarksIndexFilename).read():
indexStr=''
indexStrChanged=False
with open(bookmarksIndexFilename, 'r') as indexFile:
indexStr=indexFile.read().replace(bookmarkIndex,'')
indexStrChanged=True
if indexStrChanged:
bookmarksIndexFile=open(bookmarksIndexFilename,'w')
if bookmarksIndexFile:
bookmarksIndexFile.write(indexStr)
bookmarksIndexFile.close()
def bookmarkedByPerson(postJsonObject: {}, nickname: str,domain: str) -> bool:
"""Returns True if the given post is bookmarked by the given person
"""
if noOfBookmarks(postJsonObject)==0:
return False
actorMatch=domain+'/users/'+nickname
for item in postJsonObject['object']['bookmarks']['items']:
if item['actor'].endswith(actorMatch):
return True
return False
def noOfBookmarks(postJsonObject: {}) -> int:
"""Returns the number of bookmarks ona given post
"""
if not postJsonObject.get('object'):
return 0
if not isinstance(postJsonObject['object'], dict):
return 0
if not postJsonObject['object'].get('bookmarks'):
return 0
if not isinstance(postJsonObject['object']['bookmarks'], dict):
return 0
if not postJsonObject['object']['bookmarks'].get('items'):
postJsonObject['object']['bookmarks']['items']=[]
postJsonObject['object']['bookmarks']['totalItems']=0
return len(postJsonObject['object']['bookmarks']['items'])
def updateBookmarksCollection(baseDir: str,postFilename: str, \
objectUrl: str, \
actor: str,domain: str,debug: bool) -> None:
"""Updates the bookmarks collection within a post
"""
postJsonObject=loadJson(postFilename)
if postJsonObject:
# remove any cached version of this post so that the bookmark icon is changed
nickname=getNicknameFromActor(actor)
cachedPostFilename= \
getCachedPostFilename(baseDir,nickname,domain,postJsonObject)
if os.path.isfile(cachedPostFilename):
os.remove(cachedPostFilename)
if not postJsonObject.get('object'):
if debug:
pprint(postJsonObject)
print('DEBUG: post '+objectUrl+' has no object')
return
if not objectUrl.endswith('/bookmarks'):
objectUrl=objectUrl+'/bookmarks'
if not postJsonObject['object'].get('bookmarks'):
if debug:
print('DEBUG: Adding initial bookmarks to '+objectUrl)
bookmarksJson = {
"@context": "https://www.w3.org/ns/activitystreams",
'id': objectUrl,
'type': 'Collection',
"totalItems": 1,
'items': [{
'type': 'Bookmark',
'actor': actor
}]
}
postJsonObject['object']['bookmarks']=bookmarksJson
else:
if not postJsonObject['object']['bookmarks'].get('items'):
postJsonObject['object']['bookmarks']['items']=[]
for bookmarkItem in postJsonObject['object']['bookmarks']['items']:
if bookmarkItem.get('actor'):
if bookmarkItem['actor']==actor:
return
newBookmark={
'type': 'Bookmark',
'actor': actor
}
postJsonObject['object']['bookmarks']['items'].append(newBookmark)
postJsonObject['object']['bookmarks']['totalItems']= \
len(postJsonObject['object']['bookmarks']['items'])
if debug:
print('DEBUG: saving post with bookmarks added')
pprint(postJsonObject)
saveJson(postJsonObject,postFilename)
# prepend to the index
bookmarksIndexFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/bookmarks.index'
bookmarkIndex=postFilename.split('/')[-1]
if os.path.isfile(bookmarksIndexFilename):
if bookmarkIndex not in open(bookmarksIndexFilename).read():
try:
with open(bookmarksIndexFilename, 'r+') as bookmarksIndexFile:
content = bookmarksIndexFile.read()
bookmarksIndexFile.seek(0, 0)
bookmarksIndexFile.write(bookmarkIndex+'\n'+content)
if debug:
print('DEBUG: bookmark added to index')
except Exception as e:
print('WARN: Failed to write entry to bookmarks index '+ \
bookmarksIndexFilename+' '+str(e))
else:
bookmarksIndexFile=open(bookmarksIndexFilename,'w')
if bookmarksIndexFile:
bookmarksIndexFile.write(bookmarkIndex+'\n')
bookmarksIndexFile.close()
def bookmark(session,baseDir: str,federationList: [], \
nickname: str,domain: str,port: int, \
ccList: [],httpPrefix: str, \
objectUrl: str,actorBookmarked: str, \
clientToServer: bool, \
sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}, \
debug: bool,projectVersion: str) -> {}:
"""Creates a bookmark
actor is the person doing the bookmarking
'to' might be a specific person (actor) whose post was bookmarked
object is typically the url of the message which was bookmarked
"""
if not urlPermitted(objectUrl,federationList,"inbox:write"):
return None
fullDomain=domain
if port:
if port!=80 and port!=443:
if ':' not in domain:
fullDomain=domain+':'+str(port)
bookmarkTo=[]
if '/statuses/' in objectUrl:
bookmarkTo=[objectUrl.split('/statuses/')[0]]
newBookmarkJson = {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'object': objectUrl
}
if ccList:
if len(ccList)>0:
newBookmarkJson['cc']=ccList
# Extract the domain and nickname from a statuses link
bookmarkedPostNickname=None
bookmarkedPostDomain=None
bookmarkedPostPort=None
if actorBookmarked:
bookmarkedPostNickname=getNicknameFromActor(actorBookmarked)
bookmarkedPostDomain,bookmarkedPostPort=getDomainFromActor(actorBookmarked)
else:
if '/users/' in objectUrl or \
'/channel/' in objectUrl or \
'/profile/' in objectUrl:
bookmarkedPostNickname=getNicknameFromActor(objectUrl)
bookmarkedPostDomain,bookmarkedPostPort=getDomainFromActor(objectUrl)
if bookmarkedPostNickname:
postFilename=locatePost(baseDir,nickname,domain,objectUrl)
if not postFilename:
print('DEBUG: bookmark baseDir: '+baseDir)
print('DEBUG: bookmark nickname: '+nickname)
print('DEBUG: bookmark domain: '+domain)
print('DEBUG: bookmark objectUrl: '+objectUrl)
return None
updateBookmarksCollection(baseDir,postFilename,objectUrl, \
newBookmarkJson['actor'],domain,debug)
sendSignedJson(newBookmarkJson,session,baseDir, \
nickname,domain,port, \
bookmarkedPostNickname,bookmarkedPostDomain,bookmarkedPostPort, \
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix,True,clientToServer,federationList, \
sendThreads,postLog,cachedWebfingers,personCache, \
debug,projectVersion)
return newBookmarkJson
def bookmarkPost(session,baseDir: str,federationList: [], \
nickname: str,domain: str,port: int,httpPrefix: str, \
bookmarkNickname: str,bookmarkedomain: str,bookmarkPort: int, \
ccList: [], \
bookmarkStatusNumber: int,clientToServer: bool, \
sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}, \
debug: bool,projectVersion: str) -> {}:
"""Bookmarks a given status post. This is only used by unit tests
"""
bookmarkedomain=bookmarkedomain
if bookmarkPort:
if bookmarkPort!=80 and bookmarkPort!=443:
if ':' not in bookmarkedomain:
bookmarkedomain=bookmarkedomain+':'+str(bookmarkPort)
actorBookmarked= \
httpPrefix + '://'+bookmarkedomain+'/users/'+bookmarkNickname
objectUrl=actorBookmarked+'/statuses/'+str(bookmarkStatusNumber)
ccUrl=httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname
if bookmarkPort:
if bookmarkPort!=80 and bookmarkPort!=443:
if ':' not in bookmarkedomain:
ccUrl= \
httpPrefix+'://'+bookmarkedomain+':'+ \
str(bookmarkPort)+'/users/'+bookmarkNickname
return bookmark(session,baseDir,federationList,nickname,domain,port, \
ccList,httpPrefix,objectUrl,actorBookmarked,clientToServer, \
sendThreads,postLog,personCache,cachedWebfingers, \
debug,projectVersion)
def undoBookmark(session,baseDir: str,federationList: [], \
nickname: str,domain: str,port: int, \
ccList: [],httpPrefix: str, \
objectUrl: str,actorBookmarked: str, \
clientToServer: bool, \
sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}, \
debug: bool,projectVersion: str) -> {}:
"""Removes a bookmark
actor is the person doing the bookmarking
'to' might be a specific person (actor) whose post was bookmarked
object is typically the url of the message which was bookmarked
"""
if not urlPermitted(objectUrl,federationList,"inbox:write"):
return None
fullDomain=domain
if port:
if port!=80 and port!=443:
if ':' not in domain:
fullDomain=domain+':'+str(port)
bookmarkTo=[]
if '/statuses/' in objectUrl:
bookmarkTo=[objectUrl.split('/statuses/')[0]]
newUndoBookmarkJson = {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'object': {
'type': 'Bookmark',
'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
'object': objectUrl
}
}
if ccList:
if len(ccList)>0:
newUndoBookmarkJson['cc']=ccList
newUndoBookmarkJson['object']['cc']=ccList
# Extract the domain and nickname from a statuses link
bookmarkedPostNickname=None
bookmarkedPostDomain=None
bookmarkedPostPort=None
if actorBookmarked:
bookmarkedPostNickname=getNicknameFromActor(actorBookmarked)
bookmarkedPostDomain,bookmarkedPostPort=getDomainFromActor(actorBookmarked)
else:
if '/users/' in objectUrl or \
'/channel/' in objectUrl or \
'/profile/' in objectUrl:
bookmarkedPostNickname=getNicknameFromActor(objectUrl)
bookmarkedPostDomain,bookmarkedPostPort=getDomainFromActor(objectUrl)
if bookmarkedPostNickname:
postFilename=locatePost(baseDir,nickname,domain,objectUrl)
if not postFilename:
return None
undoBookmarksCollectionEntry(baseDir,postFilename,objectUrl, \
newBookmarkJson['actor'],domain,debug)
sendSignedJson(newUndoBookmarkJson,session,baseDir, \
nickname,domain,port, \
bookmarkedPostNickname,bookmarkedPostDomain,bookmarkedPostPort, \
'https://www.w3.org/ns/activitystreams#Public', \
httpPrefix,True,clientToServer,federationList, \
sendThreads,postLog,cachedWebfingers,personCache, \
debug,projectVersion)
else:
return None
return newUndoBookmarkJson
def undoBookmarkPost(session,baseDir: str,federationList: [], \
nickname: str,domain: str,port: int,httpPrefix: str, \
bookmarkNickname: str,bookmarkedomain: str,bookmarkPort: int, \
ccList: [], \
bookmarkStatusNumber: int,clientToServer: bool, \
sendThreads: [],postLog: [], \
personCache: {},cachedWebfingers: {}, \
debug: bool) -> {}:
"""Removes a bookmarked post
"""
bookmarkedomain=bookmarkedomain
if bookmarkPort:
if bookmarkPort!=80 and bookmarkPort!=443:
if ':' not in bookmarkedomain:
bookmarkedomain=bookmarkedomain+':'+str(bookmarkPort)
objectUrl = \
httpPrefix + '://'+bookmarkedomain+'/users/'+bookmarkNickname+ \
'/statuses/'+str(bookmarkStatusNumber)
ccUrl=httpPrefix+'://'+bookmarkedomain+'/users/'+bookmarkNickname
if bookmarkPort:
if bookmarkPort!=80 and bookmarkPort!=443:
if ':' not in bookmarkedomain:
ccUrl= \
httpPrefix+'://'+bookmarkedomain+':'+ \
str(bookmarkPort)+'/users/'+bookmarkNickname
return undoBookmark(session,baseDir,federationList,nickname,domain,port, \
ccList,httpPrefix,objectUrl,clientToServer, \
sendThreads,postLog,personCache,cachedWebfingers,debug)
def sendBookmarkViaServer(baseDir: str,session, \
fromNickname: str,password: str,
fromDomain: str,fromPort: int, \
httpPrefix: str,bookmarkUrl: str, \
cachedWebfingers: {},personCache: {}, \
debug: bool,projectVersion: str) -> {}:
"""Creates a bookmark via c2s
"""
if not session:
print('WARN: No session for sendBookmarkViaServer')
return 6
fromDomainFull=fromDomain
if fromPort:
if fromPort!=80 and fromPort!=443:
if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort)
toUrl=['https://www.w3.org/ns/activitystreams#Public']
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
if '/statuses/' in bookmarkUrl:
toUrl=[bookmarkUrl.split('/statuses/')[0]]
newBookmarkJson = {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'object': bookmarkUrl
}
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle
wfRequest=webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest:
if debug:
print('DEBUG: announce webfinger failed for '+handle)
return 1
postToBox='outbox'
# get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox)
if not inboxUrl:
if debug:
print('DEBUG: No '+postToBox+' was found for '+handle)
return 3
if not fromPersonId:
if debug:
print('DEBUG: No actor was found for '+handle)
return 4
authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \
'Content-type': 'application/json', \
'Authorization': authHeader}
postResult = \
postJson(session,newBookmarkJson,[],inboxUrl,headers,"inbox:write")
#if not postResult:
# if debug:
# print('DEBUG: POST announce failed for c2s to '+inboxUrl)
# return 5
if debug:
print('DEBUG: c2s POST bookmark success')
return newBookmarkJson
def sendUndoBookmarkViaServer(baseDir: str,session, \
fromNickname: str,password: str, \
fromDomain: str,fromPort: int, \
httpPrefix: str,bookmarkUrl: str, \
cachedWebfingers: {},personCache: {}, \
debug: bool,projectVersion: str) -> {}:
"""Undo a bookmark via c2s
"""
if not session:
print('WARN: No session for sendUndoBookmarkViaServer')
return 6
fromDomainFull=fromDomain
if fromPort:
if fromPort!=80 and fromPort!=443:
if ':' not in fromDomain:
fromDomainFull=fromDomain+':'+str(fromPort)
toUrl=['https://www.w3.org/ns/activitystreams#Public']
ccUrl=httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname+'/followers'
if '/statuses/' in bookmarkUrl:
toUrl=[bookmarkUrl.split('/statuses/')[0]]
newUndoBookmarkJson = {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'object': {
'type': 'Bookmark',
'actor': httpPrefix+'://'+fromDomainFull+'/users/'+fromNickname,
'object': bookmarkUrl
}
}
handle=httpPrefix+'://'+fromDomainFull+'/@'+fromNickname
# lookup the inbox for the To handle
wfRequest = webfingerHandle(session,handle,httpPrefix,cachedWebfingers, \
fromDomain,projectVersion)
if not wfRequest:
if debug:
print('DEBUG: announce webfinger failed for '+handle)
return 1
postToBox='outbox'
# get the actor inbox for the To handle
inboxUrl,pubKeyId,pubKey,fromPersonId,sharedInbox,capabilityAcquisition,avatarUrl,displayName = \
getPersonBox(baseDir,session,wfRequest,personCache, \
projectVersion,httpPrefix,fromNickname, \
fromDomain,postToBox)
if not inboxUrl:
if debug:
print('DEBUG: No '+postToBox+' was found for '+handle)
return 3
if not fromPersonId:
if debug:
print('DEBUG: No actor was found for '+handle)
return 4
authHeader=createBasicAuthHeader(fromNickname,password)
headers = {'host': fromDomain, \
'Content-type': 'application/json', \
'Authorization': authHeader}
postResult = \
postJson(session,newUndoBookmarkJson,[],inboxUrl,headers,"inbox:write")
#if not postResult:
# if debug:
# print('DEBUG: POST announce failed for c2s to '+inboxUrl)
# return 5
if debug:
print('DEBUG: c2s POST undo bookmark success')
return newUndoBookmarkJson
def outboxBookmark(baseDir: str,httpPrefix: str, \
nickname: str,domain: str,port: int, \
messageJson: {},debug: bool) -> None:
""" When a bookmark request is received by the outbox from c2s
"""
if not messageJson.get('type'):
if debug:
print('DEBUG: bookmark - no type')
return
if not messageJson['type']=='Bookmark':
if debug:
print('DEBUG: not a bookmark')
return
if not messageJson.get('object'):
if debug:
print('DEBUG: no object in bookmark')
return
if not isinstance(messageJson['object'], str):
if debug:
print('DEBUG: bookmark object is not string')
return
if messageJson['object'].get('to'):
if not isinstance(messageJson['object']['to'], list):
return
if len(messageJson['object']['to'])!=1:
print('WARN: Bookmark should only be sent to one recipient')
return
if messageJson['object']['to'][0]!=messageJson['actor']:
print('WARN: Bookmark should be addressed to the same actor')
return
if debug:
print('DEBUG: c2s bookmark request arrived in outbox')
messageId=messageJson['object'].replace('/activity','')
if ':' in domain:
domain=domain.split(':')[0]
postFilename=locatePost(baseDir,nickname,domain,messageId)
if not postFilename:
if debug:
print('DEBUG: c2s bookmark post not found in inbox or outbox')
print(messageId)
return True
updateBookmarksCollection(baseDir,postFilename,messageId, \
messageJson['actor'],domain,debug)
if debug:
print('DEBUG: post bookmarked via c2s - '+postFilename)
def outboxUndoBookmark(baseDir: str,httpPrefix: str, \
nickname: str,domain: str,port: int, \
messageJson: {},debug: bool) -> None:
""" When an undo bookmark request is received by the outbox from c2s
"""
if not messageJson.get('type'):
return
if not messageJson['type']=='Undo':
return
if not messageJson.get('object'):
return
if not isinstance(messageJson['object'], dict):
if debug:
print('DEBUG: undo bookmark object is not dict')
return
if not messageJson['object'].get('type'):
if debug:
print('DEBUG: undo bookmark - no type')
return
if not messageJson['object']['type']=='Bookmark':
if debug:
print('DEBUG: not a undo bookmark')
return
if not messageJson['object'].get('object'):
if debug:
print('DEBUG: no object in undo bookmark')
return
if not isinstance(messageJson['object']['object'], str):
if debug:
print('DEBUG: undo bookmark object is not string')
return
if messageJson['object'].get('to'):
if not isinstance(messageJson['object']['to'], list):
return
if len(messageJson['object']['to'])!=1:
print('WARN: Bookmark should only be sent to one recipient')
return
if messageJson['object']['to'][0]!=messageJson['actor']:
print('WARN: Bookmark should be addressed to the same actor')
return
if debug:
print('DEBUG: c2s undo bookmark request arrived in outbox')
messageId=messageJson['object']['object'].replace('/activity','')
if ':' in domain:
domain=domain.split(':')[0]
postFilename=locatePost(baseDir,nickname,domain,messageId)
if not postFilename:
if debug:
print('DEBUG: c2s undo bookmark post not found in inbox or outbox')
print(messageId)
return True
undoBookmarksCollectionEntry(baseDir,postFilename,messageId, \
messageJson['actor'],domain,debug)
if debug:
print('DEBUG: post undo bookmarked via c2s - '+postFilename)

186
daemon.py
View File

@ -75,6 +75,8 @@ from media import createMediaDirs
from delete import outboxDelete
from like import outboxLike
from like import outboxUndoLike
from bookmarks import outboxBookmark
from bookmarks import outboxUndoBookmark
from blocking import outboxBlock
from blocking import outboxUndoBlock
from blocking import addBlock
@ -101,6 +103,7 @@ from webinterface import htmlPersonOptions
from webinterface import htmlIndividualPost
from webinterface import htmlProfile
from webinterface import htmlInbox
from webinterface import htmlBookmarks
from webinterface import htmlShares
from webinterface import htmlOutbox
from webinterface import htmlModeration
@ -566,7 +569,7 @@ class PubServer(BaseHTTPRequestHandler):
permittedOutboxTypes=[
'Create','Announce','Like','Follow','Undo', \
'Update','Add','Remove','Block','Delete', \
'Delegate','Skill'
'Delegate','Skill','Bookmark'
]
if messageJson['type'] not in permittedOutboxTypes:
if self.server.debug:
@ -643,6 +646,7 @@ class PubServer(BaseHTTPRequestHandler):
if self.server.debug:
print('DEBUG: handle availability changes requests')
outboxAvailability(self.server.baseDir,self.postToNickname,messageJson,self.server.debug)
if self.server.debug:
print('DEBUG: handle any like requests')
outboxLike(self.server.baseDir,self.server.httpPrefix, \
@ -653,6 +657,18 @@ class PubServer(BaseHTTPRequestHandler):
outboxUndoLike(self.server.baseDir,self.server.httpPrefix, \
self.postToNickname,self.server.domain,self.server.port, \
messageJson,self.server.debug)
if self.server.debug:
print('DEBUG: handle any bookmark requests')
outboxBookmark(self.server.baseDir,self.server.httpPrefix, \
self.postToNickname,self.server.domain,self.server.port, \
messageJson,self.server.debug)
if self.server.debug:
print('DEBUG: handle any undo bookmark requests')
outboxUndoBookmark(self.server.baseDir,self.server.httpPrefix, \
self.postToNickname,self.server.domain,self.server.port, \
messageJson,self.server.debug)
if self.server.debug:
print('DEBUG: handle delete requests')
outboxDelete(self.server.baseDir,self.server.httpPrefix, \
@ -1752,6 +1768,102 @@ class PubServer(BaseHTTPRequestHandler):
'?page='+str(pageNumber),cookie)
return
self._benchmarkGETtimings(GETstartTime,GETtimings,36)
# bookmark from the web interface icon
if htmlGET and '?bookmark=' in self.path:
pageNumber=1
bookmarkUrl=self.path.split('?bookmark=')[1]
if '?' in bookmarkUrl:
bookmarkUrl=bookmarkUrl.split('?')[0]
actor=self.path.split('?bookmark=')[0]
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
self.postToNickname=getNicknameFromActor(actor)
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
self._redirect_headers(actor+'/'+timelineStr+ \
'?page='+str(pageNumber),cookie)
return
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
bookmarkActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/users/'+self.postToNickname
bookmarkJson= {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Bookmark',
'actor': bookmarkActor,
'to': [bookmarkActor],
'object': bookmarkUrl
}
self._postToOutbox(bookmarkJson,self.server.projectVersion)
self.server.GETbusy=False
self._redirect_headers(actor+'/'+timelineStr+ \
'?page='+str(pageNumber),cookie)
return
# undo a bookmark from the web interface icon
if htmlGET and '?unbookmark=' in self.path:
pageNumber=1
bookmarkUrl=self.path.split('?unbookmark=')[1]
if '?' in bookmarkUrl:
bookmarkUrl=bookmarkUrl.split('?')[0]
if '?page=' in self.path:
pageNumberStr=self.path.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr=pageNumberStr.split('?')[0]
if pageNumberStr.isdigit():
pageNumber=int(pageNumberStr)
timelineStr='inbox'
if '?tl=' in self.path:
timelineStr=self.path.split('?tl=')[1]
if '?' in timelineStr:
timelineStr=timelineStr.split('?')[0]
actor=self.path.split('?unbookmark=')[0]
self.postToNickname=getNicknameFromActor(actor)
if not self.postToNickname:
print('WARN: unable to find nickname in '+actor)
self.server.GETbusy=False
self._redirect_headers(actor+'/'+timelineStr+ \
'?page='+str(pageNumber),cookie)
return
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
undoActor= \
self.server.httpPrefix+'://'+ \
self.server.domainFull+'/users/'+self.postToNickname
undoBookmarkJson= {
"@context": "https://www.w3.org/ns/activitystreams",
'type': 'Undo',
'actor': undoActor,
'to': [undoActor],
'object': {
'type': 'Bookmark',
'actor': undoActor,
'to': [undoActor],
'object': bookmarkUrl
}
}
self._postToOutbox(undoBookmarkJson,self.server.projectVersion)
self.server.GETbusy=False
self._redirect_headers(actor+'/'+timelineStr+ \
'?page='+str(pageNumber),cookie)
return
self._benchmarkGETtimings(GETstartTime,GETtimings,37)
# delete a post from the web interface icon
@ -2559,7 +2671,77 @@ class PubServer(BaseHTTPRequestHandler):
self.end_headers()
self.server.GETbusy=False
return
# get the bookmarks for a given person
if self.path.endswith('/tlbookmarks') or '/tlbookmarks?page=' in self.path:
if '/users/' in self.path:
if authorized:
bookmarksFeed= \
personBoxJson(self.server.session, \
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'tlbookmarks', \
True,self.server.ocapAlways)
if bookmarksFeed:
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/inbox','')
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
bookmarksFeed= \
personBoxJson(self.server.session, \
self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'tlbookmarks', \
True,self.server.ocapAlways)
msg=htmlBookmarks(self.server.translate, \
pageNumber,maxPostsInFeed, \
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
bookmarksFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
else:
# don't need authenticated fetch here because there is
# already the authorization check
msg=json.dumps(inboxFeed,ensure_ascii=False).encode('utf-8')
self._set_headers('application/json',len(msg),None)
self._write(msg)
self.server.GETbusy=False
return
else:
if self.server.debug:
nickname=self.path.replace('/users/','').replace('/inbox','')
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.server.debug:
print('DEBUG: GET access to bookmarks 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

151
inbox.py
View File

@ -40,6 +40,8 @@ from capabilities import CapablePost
from capabilities import capabilitiesReceiveUpdate
from like import updateLikesCollection
from like import undoLikesCollectionEntry
from bookmarks import updateBookmarkssCollection
from bookmarks import undoBookmarksCollectionEntry
from blocking import isBlocked
from blocking import isBlockedDomain
from filters import isFiltered
@ -900,6 +902,129 @@ def receiveUndoLike(session,handle: str,isGroup: bool,baseDir: str, \
undoLikesCollectionEntry(baseDir,postFilename,messageJson['object'],messageJson['actor'],domain,debug)
return True
def receiveBookmark(session,handle: str,isGroup: bool,baseDir: str, \
httpPrefix: str,domain :str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},messageJson: {},federationList: [], \
debug : bool) -> bool:
"""Receives a bookmark activity within the POST section of HTTPServer
"""
if messageJson['type']!='Bookmark':
return False
if not messageJson.get('actor'):
if debug:
print('DEBUG: '+messageJson['type']+' has no actor')
return False
if not messageJson.get('object'):
if debug:
print('DEBUG: '+messageJson['type']+' has no object')
return False
if not isinstance(messageJson['object'], str):
if debug:
print('DEBUG: '+messageJson['type']+' object is not a string')
return False
if not messageJson.get('to'):
if debug:
print('DEBUG: '+messageJson['type']+' has no "to" list')
return False
if '/users/' not in messageJson['actor']:
if debug:
print('DEBUG: "users" missing from actor in '+messageJson['type'])
return False
if '/statuses/' not in messageJson['object']:
if debug:
print('DEBUG: "statuses" missing from object in '+messageJson['type'])
return False
if domain not in handle.split('@')[1]:
if debug:
print('DEBUG: unrecognized domain '+handle)
return False
domainFull=domain
if port:
if port!=80 and port!=443:
domainFull=domain+':'+str(port)
nickname=handle.split('@')[0]
if not messageJson['actor'].endswith(domainFull+'/users/'+nickname):
if debug:
print('DEBUG: bookmark actor should be the same as the handle sent to '+handle+' != '+messageJson['actor'])
return False
if not os.path.isdir(baseDir+'/accounts/'+handle):
print('DEBUG: unknown recipient of bookmark - '+handle)
# if this post in the outbox of the person?
postFilename=locatePost(baseDir,nickname,domain,messageJson['object'])
if not postFilename:
if debug:
print('DEBUG: post not found in inbox or outbox')
print(messageJson['object'])
return True
if debug:
print('DEBUG: bookmarked post was found')
updateBookmarksCollection(baseDir,postFilename,messageJson['object'],messageJson['actor'],domain,debug)
return True
def receiveUndoBookmark(session,handle: str,isGroup: bool,baseDir: str, \
httpPrefix: str,domain :str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
personCache: {},messageJson: {},federationList: [], \
debug : bool) -> bool:
"""Receives an undo bookmark activity within the POST section of HTTPServer
"""
if messageJson['type']!='Undo':
return False
if not messageJson.get('actor'):
return False
if not messageJson.get('object'):
return False
if not isinstance(messageJson['object'], dict):
return False
if not messageJson['object'].get('type'):
return False
if messageJson['object']['type']!='Bookmark':
return False
if not messageJson['object'].get('object'):
if debug:
print('DEBUG: '+messageJson['type']+' like has no object')
return False
if not isinstance(messageJson['object']['object'], str):
if debug:
print('DEBUG: '+messageJson['type']+' like object is not a string')
return False
if '/users/' not in messageJson['actor']:
if debug:
print('DEBUG: "users" missing from actor in '+messageJson['type']+' like')
return False
if '/statuses/' not in messageJson['object']['object']:
if debug:
print('DEBUG: "statuses" missing from like object in '+messageJson['type'])
return False
domainFull=domain
if port:
if port!=80 and port!=443:
domainFull=domain+':'+str(port)
nickname=handle.split('@')[0]
if domain not in handle.split('@')[1]:
if debug:
print('DEBUG: unrecognized bookmark domain '+handle)
return False
if not messageJson['actor'].endswith(domainFull+'/users/'+nickname):
if debug:
print('DEBUG: bookmark actor should be the same as the handle sent to '+handle+' != '+messageJson['actor'])
return False
if not os.path.isdir(baseDir+'/accounts/'+handle):
print('DEBUG: unknown recipient of bookmark undo - '+handle)
# if this post in the outbox of the person?
postFilename=locatePost(baseDir,nickname,domain,messageJson['object']['object'])
if not postFilename:
if debug:
print('DEBUG: unbookmarked post not found in inbox or outbox')
print(messageJson['object']['object'])
return True
if debug:
print('DEBUG: bookmarked post found. Now undoing.')
undoBookmarksCollectionEntry(baseDir,postFilename,messageJson['object'],messageJson['actor'],domain,debug)
return True
def receiveDelete(session,handle: str,isGroup: bool,baseDir: str, \
httpPrefix: str,domain :str,port: int, \
sendThreads: [],postLog: [],cachedWebfingers: {}, \
@ -1517,6 +1642,32 @@ def inboxAfterCapabilities(session,keyId: str,handle: str,messageJson: {}, \
print('DEBUG: Undo like accepted from '+actor)
return False
if receiveBookmark(session,handle,isGroup, \
baseDir,httpPrefix, \
domain,port, \
sendThreads,postLog, \
cachedWebfingers, \
personCache, \
messageJson, \
federationList, \
debug):
if debug:
print('DEBUG: Bookmark accepted from '+actor)
return False
if receiveUndoBookmark(session,handle,isGroup, \
baseDir,httpPrefix, \
domain,port, \
sendThreads,postLog, \
cachedWebfingers, \
personCache, \
messageJson, \
federationList, \
debug):
if debug:
print('DEBUG: Undo bookmark accepted from '+actor)
return False
if receiveAnnounce(session,handle,isGroup, \
baseDir,httpPrefix, \
domain,port, \

View File

@ -21,6 +21,7 @@ from webfinger import storeWebfingerEndpoint
from posts import createDMTimeline
from posts import createRepliesTimeline
from posts import createMediaTimeline
from posts import createBookmarksTimeline
from posts import createInbox
from posts import createOutbox
from posts import createModeration
@ -411,7 +412,8 @@ def personBoxJson(session,baseDir: str,domain: str,port: int,path: str, \
"""
if boxname!='inbox' and boxname!='dm' and \
boxname!='tlreplies' and boxname!='tlmedia' and \
boxname!='outbox' and boxname!='moderation':
boxname!='outbox' and boxname!='moderation' and \
boxname!='tlbookmarks':
return None
if not '/'+boxname in path:
@ -448,9 +450,12 @@ def personBoxJson(session,baseDir: str,domain: str,port: int,path: str, \
if boxname=='inbox':
return createInbox(session,baseDir,nickname,domain,port,httpPrefix, \
noOfItems,headerOnly,ocapAlways,pageNumber)
if boxname=='dm':
elif boxname=='dm':
return createDMTimeline(session,baseDir,nickname,domain,port,httpPrefix, \
noOfItems,headerOnly,ocapAlways,pageNumber)
elif boxname=='tlbookmarks':
return createBookmarksTimeline(session,baseDir,nickname,domain,port,httpPrefix, \
noOfItems,headerOnly,ocapAlways,pageNumber)
elif boxname=='tlreplies':
return createRepliesTimeline(session,baseDir,nickname,domain,port,httpPrefix, \
noOfItems,headerOnly,ocapAlways,pageNumber)

View File

@ -1788,6 +1788,11 @@ def createInbox(session,baseDir: str,nickname: str,domain: str,port: int,httpPre
return createBoxBase(session,baseDir,'inbox',nickname,domain,port,httpPrefix, \
itemsPerPage,headerOnly,True,ocapAlways,pageNumber)
def createBookmarksTimeline(session,baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \
itemsPerPage: int,headerOnly: bool,ocapAlways: bool,pageNumber=None) -> {}:
return createBoxBase(session,baseDir,'tlbookmarks',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,pageNumber=None) -> {}:
return createBoxBase(session,baseDir,'dm',nickname,domain,port,httpPrefix, \
@ -2058,17 +2063,25 @@ def createBoxBase(session,baseDir: str,boxname: str, \
pageNumber=1
if boxname!='inbox' and boxname!='dm' and \
boxname!='tlreplies' and boxname!='tlmedia' and boxname!='outbox':
boxname!='tlreplies' and boxname!='tlmedia' and \
boxname!='outbox' and boxname!='tlbookmarks':
return None
if boxname!='dm' and boxname!='tlreplies' and boxname!='tlmedia':
if boxname!='dm' and boxname!='tlreplies' and \
boxname!='tlmedia' and boxname!='tlbookmarks':
boxDir = createPersonDir(nickname,domain,baseDir,boxname)
else:
# extract DMs or replies or media from the inbox
boxDir = createPersonDir(nickname,domain,baseDir,'inbox')
sharedBoxDir=None
if boxname=='inbox' or boxname=='tlreplies' or boxname=='tlmedia':
if boxname=='inbox' or boxname=='tlreplies' or \
boxname=='tlmedia' or boxname=='tlbookmarks':
sharedBoxDir = createPersonDir('inbox',domain,baseDir,boxname)
# bookmarks timeline is like the inbox but has its own separate index
indexBoxName=boxname
if boxname=='tlbookmarks':
indexBoxName='bookmarks'
if port:
if port!=80 and port!=443:
if ':' not in domain:
@ -2102,7 +2115,7 @@ def createBoxBase(session,baseDir: str,boxname: str, \
postsInBoxDict={}
postsInBox={}
indexFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+boxname+'.index'
indexFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/'+indexBoxName+'.index'
lookedUpFromIndex=False
if os.path.isfile(indexFilename):
print('DEBUG: using index file to construct timeline')
@ -2125,7 +2138,7 @@ def createBoxBase(session,baseDir: str,boxname: str, \
postsCtr=createBoxIndex(boxDir,postsInBoxDict)
# combine the inbox for the account with the shared inbox
if sharedBoxDir:
if sharedBoxDir and boxname!='tlbookmarks':
postsCtr= \
createSharedInboxIndex(baseDir,sharedBoxDir, \
postsInBoxDict,postsCtr, \

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -173,5 +173,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -177,5 +177,8 @@
"Instance Title": "Instance Title",
"Instance Short Description": "Instance Short Description",
"Instance Description": "Instance Description",
"Instance Logo": "Instance Logo"
"Instance Logo": "Instance Logo",
"Bookmark this post": "Bookmark this post",
"Undo the bookmark": "Undo the bookmark",
"Bookmarks": "Bookmarks"
}

View File

@ -44,6 +44,7 @@ from session import getJson
from auth import createPassword
from like import likedByPerson
from like import noOfLikes
from bookmarks import bookmarkedByPerson
from announce import announcedByPerson
from blocking import isBlocked
from content import getMentionsFromHtml
@ -2213,6 +2214,22 @@ def individualPostAsHtml(iconsDir: str,translate: {}, \
'?tl='+boxName+'" title="'+likeTitle+'">'
likeStr+='<img loading="lazy" src="/'+iconsDir+'/'+likeIcon+'"/></a>'
bookmarkStr=''
if not isModerationPost:
bookmarkIcon='bookmark_inactive.png'
bookmarkLink='bookmark'
bookmarkTitle=translate['Bookmark this post']
if bookmarkedByPerson(postJsonObject,nickname,fullDomain):
bookmarkIcon='bookmark.png'
bookmarkLink='unbookmark'
bookmarkTitle=translate['Undo the bookmark']
bookmarkStr= \
'<a href="/users/' + nickname + '?' + \
bookmarkLink + '=' + postJsonObject['object']['id'] + pageNumberParam + \
'?actor='+postJsonObject['actor']+ \
'?tl='+boxName+'" title="'+bookmarkTitle+'">'
bookmarkStr+='<img loading="lazy" src="/'+iconsDir+'/'+bookmarkIcon+'"/></a>'
deleteStr=''
if allowDeletion or \
('/'+fullDomain+'/' in postActor and \
@ -2258,7 +2275,7 @@ def individualPostAsHtml(iconsDir: str,translate: {}, \
'?actor='+postJsonObject['actor']+ \
'" title="'+translate['Reply to this post']+'">'
footerStr+='<img loading="lazy" src="/'+iconsDir+'/reply.png"/></a>'
footerStr+=announceStr+likeStr+deleteStr
footerStr+=announceStr+likeStr+bookmarkStr+deleteStr
footerStr+='<span class="'+timeClass+'">'+publishedStr+'</span>'
footerStr+='</div>'
@ -2405,6 +2422,7 @@ def htmlTimeline(translate: {},pageNumber: int, \
if newReply:
repliesButton='buttonhighlighted'
mediaButton='button'
bookmarksButton='button'
sentButton='button'
sharesButton='button'
if newShare:
@ -2434,6 +2452,8 @@ def htmlTimeline(translate: {},pageNumber: int, \
sharesButton='buttonselected'
if newShare:
sharesButton='buttonselectedhighlighted'
elif boxName=='tlbookmarks':
bookmarksButton='buttonselected'
fullDomain=domain
if port!=80 and port!=443:
@ -2462,6 +2482,8 @@ def htmlTimeline(translate: {},pageNumber: int, \
sharesButtonStr='<a href="'+actor+'/tlshares"><button class="'+sharesButton+'"><span>'+translate['Shares']+' </span></button></a>'
bookmarksButtonStr='<a href="'+actor+'/tlbookmarks"><button class="'+bookmarksButton+'"><span>'+translate['Bookmarks']+' </span></button></a>'
tlStr=htmlHeader(cssFilename,profileStyle)
#if (boxName=='inbox' or boxName=='dm') and pageNumber==1:
# refresh if on the first page of the inbox and dm timeline
@ -2485,7 +2507,7 @@ def htmlTimeline(translate: {},pageNumber: int, \
tlStr+=' <a href="'+actor+'/tlreplies"><button class="'+repliesButton+'"><span>'+translate['Replies']+'</span></button></a>'
tlStr+=' <a href="'+actor+'/tlmedia"><button class="'+mediaButton+'"><span>'+translate['Media']+'</span></button></a>'
tlStr+=' <a href="'+actor+'/outbox"><button class="'+sentButton+'"><span>'+translate['Outbox']+'</span></button></a>'
tlStr+=sharesButtonStr+moderationButtonStr+newPostButtonStr
tlStr+=sharesButtonStr+bookmarksButtonStr+moderationButtonStr+newPostButtonStr
tlStr+=' <a href="'+actor+'/search"><img loading="lazy" src="/'+iconsDir+'/search.png" title="'+translate['Search and follow']+'" alt="'+translate['Search and follow']+'" class="timelineicon"/></a>'
tlStr+=' <a href="'+actor+calendarPath+'"><img loading="lazy" src="/'+iconsDir+'/'+calendarImage+'" title="'+translate['Calendar']+'" alt="'+translate['Calendar']+'" class="timelineicon"/></a>'
tlStr+=' <a href="'+actor+'/'+boxName+'"><img loading="lazy" src="/'+iconsDir+'/refresh.png" title="'+translate['Refresh']+'" alt="'+translate['Refresh']+'" class="timelineicon"/></a>'
@ -2582,6 +2604,21 @@ def htmlInbox(translate: {},pageNumber: int,itemsPerPage: int, \
nickname,domain,port,inboxJson,'inbox',allowDeletion, \
httpPrefix,projectVersion,manuallyApproveFollowers)
def htmlBookmarks(translate: {},pageNumber: int,itemsPerPage: int, \
session,baseDir: str,wfRequest: {},personCache: {}, \
nickname: str,domain: str,port: int,inboxJson: {}, \
allowDeletion: bool, \
httpPrefix: str,projectVersion: str) -> str:
"""Show the bookmarks as html
"""
manuallyApproveFollowers= \
followerApprovalActive(baseDir,nickname,domain)
return htmlTimeline(translate,pageNumber, \
itemsPerPage,session,baseDir,wfRequest,personCache, \
nickname,domain,port,inboxJson,'tlbookmarks',allowDeletion, \
httpPrefix,projectVersion,manuallyApproveFollowers)
def htmlInboxDMs(translate: {},pageNumber: int,itemsPerPage: int, \
session,baseDir: str,wfRequest: {},personCache: {}, \
nickname: str,domain: str,port: int,inboxJson: {}, \