Functions for shared items

master
Bob Mottram 2019-07-23 13:33:09 +01:00
parent ac8e920bcf
commit 643ca568bc
5 changed files with 319 additions and 12 deletions

View File

@ -49,6 +49,7 @@ from webinterface import htmlProfile
from webinterface import htmlInbox from webinterface import htmlInbox
from webinterface import htmlOutbox from webinterface import htmlOutbox
from webinterface import htmlPostReplies from webinterface import htmlPostReplies
from shares import getSharesFeedForPerson
import os import os
import sys import sys
@ -58,6 +59,9 @@ maxPostsInFeed=20
# number of follows/followers per page # number of follows/followers per page
followsPerPage=12 followsPerPage=12
# number of item shares per page
sharesPerPage=12
def readFollowList(filename: str): def readFollowList(filename: str):
"""Returns a list of ActivityPub addresses to follow """Returns a list of ActivityPub addresses to follow
""" """
@ -354,6 +358,28 @@ class PubServer(BaseHTTPRequestHandler):
return return
self._404() self._404()
return return
# show shared item images
# Note that this comes before the busy flag to avoid conflicts
if '/sharefiles/' in self.path:
if self.path.endswith('.png') or \
self.path.endswith('.jpg') or \
self.path.endswith('.gif'):
mediaStr=self.path.split('/sharefiles/')[1]
mediaFilename= \
self.server.baseDir+'/sharefiles/'+mediaStr
if os.path.isfile(mediaFilename):
if mediaFilename.endswith('.png'):
self._set_headers('image/png')
elif mediaFilename.endswith('.jpg'):
self._set_headers('image/jpeg')
else:
self._set_headers('image/gif')
with open(mediaFilename, 'rb') as avFile:
mediaBinary = avFile.read()
self.wfile.write(mediaBinary)
return
self._404()
return
# show avatar or background image # show avatar or background image
# Note that this comes before the busy flag to avoid conflicts # Note that this comes before the busy flag to avoid conflicts
if '/users/' in self.path: if '/users/' in self.path:
@ -700,6 +726,45 @@ class PubServer(BaseHTTPRequestHandler):
return return
authorized=self._isAuthorized() authorized=self._isAuthorized()
shares=getSharesFeedForPerson(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \
self.server.httpPrefix, \
sharesPerPage)
if shares:
if 'text/html' in self.headers['Accept']:
if 'page=' not in self.path:
# get a page of shares, not the summary
shares=getSharesFeedForPerson(self.server.baseDir,self.server.domain, \
self.server.port,self.path+'?page=true', \
self.server.httpPrefix, \
sharesPerPage)
getPerson = personLookup(self.server.domain,self.path.replace('/shares',''), \
self.server.baseDir)
if getPerson:
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session')
self.server.session= \
createSession(self.server.domain,self.server.port,self.server.useTor)
self._set_headers('text/html')
self.wfile.write(htmlProfile(self.server.baseDir, \
self.server.httpPrefix, \
authorized, \
self.server.ocapAlways, \
getPerson,'shares', \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
shares).encode('utf-8'))
self.server.GETbusy=False
return
else:
self._set_headers('application/json')
self.wfile.write(json.dumps(shares).encode('utf-8'))
self.server.GETbusy=False
return
following=getFollowingFeed(self.server.baseDir,self.server.domain, \ following=getFollowingFeed(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \ self.server.port,self.path, \
self.server.httpPrefix, \ self.server.httpPrefix, \
@ -715,17 +780,12 @@ class PubServer(BaseHTTPRequestHandler):
getPerson = personLookup(self.server.domain,self.path.replace('/following',''), \ getPerson = personLookup(self.server.domain,self.path.replace('/following',''), \
self.server.baseDir) self.server.baseDir)
if getPerson: if getPerson:
if not self.server.session:
if self.server.debug:
print('DEBUG: creating new session for c2s')
self.server.session= \
createSession(self.server.domain,self.server.port,self.server.useTor)
if not self.server.session: if not self.server.session:
if self.server.debug: if self.server.debug:
print('DEBUG: creating new session') print('DEBUG: creating new session')
self.server.session= \ self.server.session= \
createSession(self.server.domain,self.server.port,self.server.useTor) createSession(self.server.domain,self.server.port,self.server.useTor)
self._set_headers('text/html') self._set_headers('text/html')
self.wfile.write(htmlProfile(self.server.baseDir, \ self.wfile.write(htmlProfile(self.server.baseDir, \
self.server.httpPrefix, \ self.server.httpPrefix, \

View File

@ -137,6 +137,7 @@ def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
'endpoints': { 'endpoints': {
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/endpoints', 'id': httpPrefix+'://'+domain+'/users/'+nickname+'/endpoints',
'sharedInbox': httpPrefix+'://'+domain+'/inbox', 'sharedInbox': httpPrefix+'://'+domain+'/inbox',
'shares': httpPrefix+'://'+domain+'/shares'
}, },
'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new', 'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
'followers': httpPrefix+'://'+domain+'/users/'+nickname+'/followers', 'followers': httpPrefix+'://'+domain+'/users/'+nickname+'/followers',
@ -144,7 +145,6 @@ def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
'orgSchema': None, 'orgSchema': None,
'skills': {}, 'skills': {},
'roles': {}, 'roles': {},
'shares': {},
'availability': None, 'availability': None,
'icon': {'mediaType': 'image/png', 'icon': {'mediaType': 'image/png',
'type': 'Image', 'type': 'Image',
@ -233,6 +233,10 @@ def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
setRole(baseDir,nickname,domain,'instance','admin') setRole(baseDir,nickname,domain,'instance','admin')
setRole(baseDir,nickname,domain,'instance','moderator') setRole(baseDir,nickname,domain,'instance','moderator')
setRole(baseDir,nickname,domain,'instance','delegator') setRole(baseDir,nickname,domain,'instance','delegator')
if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain):
os.mkdir(baseDir+'/accounts/'+nickname+'@'+domain)
if os.path.isfile(baseDir+'/img/default-avatar.png'): if os.path.isfile(baseDir+'/img/default-avatar.png'):
copyfile(baseDir+'/img/default-avatar.png',baseDir+'/accounts/'+nickname+'@'+domain+'/avatar.png') copyfile(baseDir+'/img/default-avatar.png',baseDir+'/accounts/'+nickname+'@'+domain+'/avatar.png')
if os.path.isfile(baseDir+'/img/image.png'): if os.path.isfile(baseDir+'/img/image.png'):

View File

@ -114,16 +114,16 @@ def parseUserFeed(session,feedUrl: str,asHeader: {}) -> None:
yield item yield item
def getPersonBox(session,wfRequest: {},personCache: {}, \ def getPersonBox(session,wfRequest: {},personCache: {}, \
boxName='inbox') -> (str,str,str,str,str,str): boxName='inbox') -> (str,str,str,str,str,str,str,str):
asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'} asHeader = {'Accept': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'}
personUrl = getUserUrl(wfRequest) personUrl = getUserUrl(wfRequest)
if not personUrl: if not personUrl:
return None,None,None,None,None,None,None return None,None,None,None,None,None,None,None
personJson = getPersonFromCache(personUrl,personCache) personJson = getPersonFromCache(personUrl,personCache)
if not personJson: if not personJson:
personJson = getJson(session,personUrl,asHeader,None) personJson = getJson(session,personUrl,asHeader,None)
if not personJson: if not personJson:
return None,None,None,None,None,None,None return None,None,None,None,None,None,None,None
boxJson=None boxJson=None
if not personJson.get(boxName): if not personJson.get(boxName):
if personJson.get('endpoints'): if personJson.get('endpoints'):
@ -133,7 +133,7 @@ def getPersonBox(session,wfRequest: {},personCache: {}, \
boxJson=personJson[boxName] boxJson=personJson[boxName]
if not boxJson: if not boxJson:
return None,None,None,None,None,None,None return None,None,None,None,None,None,None,None
personId=None personId=None
if personJson.get('id'): if personJson.get('id'):

228
shares.py 100644
View File

@ -0,0 +1,228 @@
__filename__ = "shares.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "0.0.1"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import json
import commentjson
import os
import time
from shutil import copyfile
from person import validNickname
from webfinger import webfingerHandle
from auth import createBasicAuthHeader
from posts import getPersonBox
from session import postJson
from utils import getNicknameFromActor
from utils import getDomainFromActor
def removeShare(baseDir: str,nickname: str,domain: str, \
displayName: str) -> None:
"""Removes a share for a person
"""
sharesFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/shares.json'
if os.path.isfile(sharesFilename):
with open(sharesFilename, 'r') as fp:
sharesJson=commentjson.load(fp)
itemID=displayName.replace(' ','')
if sharesJson.get(itemID):
# remove any image for the item
published=sharesJson[itemID]['published']
itemIDfile=baseDir+'/sharefiles/'+str(published)+itemID
if sharesJson[itemID]['imageUrl']:
if sharesJson[itemID]['imageUrl'].endswith('.png'):
os.remove(itemIDfile+'.png')
if sharesJson[itemID]['imageUrl'].endswith('.jpg'):
os.remove(itemIDfile+'.jpg')
if sharesJson[itemID]['imageUrl'].endswith('.gif'):
os.remove(itemIDfile+'.gif')
# remove the item itself
del sharesJson[itemID]
with open(sharesFilename, 'w') as fp:
commentjson.dump(sharesJson, fp, indent=4, sort_keys=True)
def addShare(baseDir: str,nickname: str,domain: str, \
displayName: str, \
summary: str, \
imageFilename: str, \
itemType: str, \
itemCategory: str, \
location: str, \
duration: str,
debug: bool) -> None:
"""Updates the likes collection within a post
"""
sharesFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/shares.json'
sharesJson={}
if os.path.isfile(sharesFilename):
with open(sharesFilename, 'r') as fp:
sharesJson=commentjson.load(fp)
duration=duration.lower()
durationSec=0
published=int(time.time())
if ' ' in duration:
durationList=duration.split(' ')
if durationList[0].isdigit():
if 'hour' in durationList[1]:
durationSec=published+(int(durationList[0])*60*60)
if 'day' in durationList[1]:
durationSec=published+(int(durationList[0])*60*60*24)
if 'week' in durationList[1]:
durationSec=published+(int(durationList[0])*60*60*24*7)
if 'month' in durationList[1]:
durationSec=published+(int(durationList[0])*60*60*24*30)
if 'year' in durationList[1]:
durationSec=published+(int(durationList[0])*60*60*24*365)
itemID=displayName.replace(' ','')
imageUrl=None
if imageFilename:
if os.path.isfile(imageFilename):
if not os.path.isdir(baseDir+'/sharefiles'):
os.mkdir(baseDir+'/sharefiles')
itemIDfile=baseDir+'/sharefiles/'+str(published)+itemID
if imageFilename.endswidth('.png'):
copyfile(imageFilename,itemIDfile+'.png')
imageUrl='/sharefiles/'+str(published)+itemID+'.png'
if imageFilename.endswidth('.jpg'):
copyfile(imageFilename,itemIDfile+'.jpg')
imageUrl='/sharefiles/'+str(published)+itemID+'.jpg'
if imageFilename.endswidth('.gif'):
copyfile(imageFilename,itemIDfile+'.gif')
imageUrl='/sharefiles/'+str(published)+itemID+'.gif'
sharesJson[itemID] = {
"displayName": displayName,
"summary": summary,
"imageUrl": imageUrl,
"type": itemType,
"category": category,
"location": location,
"published": published,
"expire": durationSec
}
with open(sharesFilename, 'w') as fp:
commentjson.dump(sharesJson, fp, indent=4, sort_keys=True)
def expireShares(baseDir: str,nickname: str,domain: str) -> None:
"""Removes expired items from shares
"""
handleDomain=domain
if ':' in handleDomain:
handleDomain=domain.split(':')[0]
handle=nickname+'@'+handleDomain
sharesFilename=baseDir+'/accounts/'+handle+'/shares.json'
if os.path.isfile(sharesFilename):
with open(sharesFilename, 'r') as fp:
sharesJson=commentjson.load(fp)
currTime=int(time.time())
deleteItemID=[]
for itemID,item in sharesJson.items():
if currTime>item['expire']:
deleteItemID.append(itemID)
if deleteItemID:
for itemID in deleteItemID:
del sharesJson[itemID]
with open(sharesFilename, 'w') as fp:
commentjson.dump(sharesJson, fp, indent=4, sort_keys=True)
def getSharesFeedForPerson(baseDir: str, \
nickname: str,domain: str,port: int, \
path: str,httpPrefix: str, \
sharesPerPage=12) -> {}:
"""Returns the shares for an account from GET requests
"""
if '/shares' not in path:
return None
# handle page numbers
headerOnly=True
pageNumber=None
if '?page=' in path:
pageNumber=path.split('?page=')[1]
if pageNumber=='true':
pageNumber=1
else:
try:
pageNumber=int(pageNumber)
except:
pass
path=path.split('?page=')[0]
headerOnly=False
if not path.endswith('/shares'):
return None
nickname=None
if path.startswith('/users/'):
nickname=path.replace('/users/','',1).replace('/shares','')
if path.startswith('/@'):
nickname=path.replace('/@','',1).replace('/shares','')
if not nickname:
return None
if not validNickname(nickname):
return None
if port!=80 and port!=443:
domain=domain+':'+str(port)
handleDomain=domain
if ':' in handleDomain:
handleDomain=domain.split(':')[0]
handle=nickname+'@'+handleDomain
sharesFilename=baseDir+'/accounts/'+handle+'/shares.json'
if headerOnly:
noOfShares=0
if os.path.isfile(sharesFilename):
with open(sharesFilename, 'r') as fp:
sharesJson=commentjson.load(fp)
noOfShares=len(sharesJson.items())
shares = {
'@context': 'https://www.w3.org/ns/activitystreams',
'first': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page=1',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
'totalItems': str(noOfShares),
'type': 'OrderedCollection'}
return shares
if not pageNumber:
pageNumber=1
nextPageNumber=int(pageNumber+1)
shares = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page='+str(pageNumber),
'orderedItems': [],
'partOf': httpPrefix+'://'+domain+'/users/'+nickname+'/shares',
'totalItems': 0,
'type': 'OrderedCollectionPage'}
if not os.path.isfile(sharesFilename):
return shares
currPage=1
pageCtr=0
totalCtr=0
with open(sharesFilename, 'r') as fp:
sharesJson=commentjson.load(fp)
for itemID,item in sharesJson.items():
pageCtr += 1
totalCtr += 1
if currPage==pageNumber:
shares['orderedItems'].append(item)
if pageCtr>=sharesPerPage:
pageCtr=0
currPage += 1
shares['totalItems']=totalCtr
lastPage=int(totalCtr/sharesPerPage)
if lastPage<1:
lastPage=1
if nextPageNumber>lastPage:
shares['next']=httpPrefix+'://'+domain+'/users/'+nickname+'/shares?page='+str(lastPage)
return shares

View File

@ -98,6 +98,18 @@ def htmlProfileSkills(nickname: str,domain: str,skillsJson: {}) -> str:
profileStr='<center><div class="skill-title">'+profileStr+'</div></center>' profileStr='<center><div class="skill-title">'+profileStr+'</div></center>'
return profileStr return profileStr
def htmlProfileShares(nickname: str,domain: str,sharesJson: {}) -> str:
"""Shows shares on the profile screen
"""
profileStr=''
for item in sharesJson['orderedItems']:
profileStr+='<div>TODO</div><br>'
if len(profileStr)==0:
profileStr+='<p>@'+nickname+'@'+domain+' is not sharing any items</p>'
else:
profileStr='<center><div class="share-title">'+profileStr+'</div></center>'
return profileStr
def htmlProfile(baseDir: str,httpPrefix: str,authorized: bool, \ def htmlProfile(baseDir: str,httpPrefix: str,authorized: bool, \
ocapAlways: bool,profileJson: {},selected: str, \ ocapAlways: bool,profileJson: {},selected: str, \
session,wfRequest: {},personCache: {}, \ session,wfRequest: {},personCache: {}, \
@ -175,6 +187,9 @@ def htmlProfile(baseDir: str,httpPrefix: str,authorized: bool, \
if selected=='skills': if selected=='skills':
profileStr+= \ profileStr+= \
htmlProfileSkills(nickname,domainFull,extraJson) htmlProfileSkills(nickname,domainFull,extraJson)
if selected=='shares':
profileStr+= \
htmlProfileShares(nickname,domainFull,extraJson)
profileStr=htmlHeader(profileStyle)+profileStr+htmlFooter() profileStr=htmlHeader(profileStyle)+profileStr+htmlFooter()
return profileStr return profileStr