forked from indymedia/epicyon
Adding bookmarks
parent
1a5677b118
commit
52e1d44021
|
@ -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
186
daemon.py
|
@ -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
151
inbox.py
|
@ -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, \
|
||||
|
|
|
@ -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)
|
||||
|
|
23
posts.py
23
posts.py
|
@ -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, \
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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: {}, \
|
||||
|
|
Loading…
Reference in New Issue