Media timeline

main2
Bob Mottram 2019-09-28 12:29:42 +01:00
parent 160f272b5d
commit 8ed3f132be
4 changed files with 183 additions and 12 deletions

View File

@ -85,6 +85,7 @@ from webinterface import htmlAbout
from webinterface import htmlRemoveSharedItem from webinterface import htmlRemoveSharedItem
from webinterface import htmlInboxDMs from webinterface import htmlInboxDMs
from webinterface import htmlInboxReplies from webinterface import htmlInboxReplies
from webinterface import htmlInboxMedia
from webinterface import htmlUnblockConfirm from webinterface import htmlUnblockConfirm
from webinterface import htmlPersonOptions from webinterface import htmlPersonOptions
from webinterface import htmlIndividualPost from webinterface import htmlIndividualPost
@ -1478,6 +1479,7 @@ class PubServer(BaseHTTPRequestHandler):
self._404() self._404()
self.server.GETbusy=False self.server.GETbusy=False
return return
# get replies to a post /users/nickname/statuses/number/replies # get replies to a post /users/nickname/statuses/number/replies
if self.path.endswith('/replies') or '/replies?page=' in self.path: if self.path.endswith('/replies') or '/replies?page=' in self.path:
if '/statuses/' in self.path and '/users/' in self.path: if '/statuses/' in self.path and '/users/' in self.path:
@ -1941,6 +1943,77 @@ class PubServer(BaseHTTPRequestHandler):
self.server.GETbusy=False self.server.GETbusy=False
return return
# get the media for a given person
if self.path.endswith('/tlmedia') or '/tlmedia?page=' in self.path:
if '/users/' in self.path:
if authorized:
inboxMediaFeed= \
personBoxJson(self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path, \
self.server.httpPrefix, \
maxPostsInFeed, 'tlmedia', \
True,self.server.ocapAlways)
if not inboxMediaFeed:
inboxMediaFeed=[]
if self._requestHTTP():
nickname=self.path.replace('/users/','').replace('/tlmedia','')
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
inboxMediaFeed= \
personBoxJson(self.server.baseDir, \
self.server.domain, \
self.server.port, \
self.path+'?page=1', \
self.server.httpPrefix, \
maxPostsInFeed, 'tlmedia', \
True,self.server.ocapAlways)
msg=htmlInboxMedia(self.server.translate, \
pageNumber,maxPostsInFeed, \
self.server.session, \
self.server.baseDir, \
self.server.cachedWebfingers, \
self.server.personCache, \
nickname, \
self.server.domain, \
self.server.port, \
inboxMediaFeed, \
self.server.allowDeletion, \
self.server.httpPrefix, \
self.server.projectVersion).encode('utf-8')
self._set_headers('text/html',len(msg),cookie)
self.wfile.write(msg)
else:
# don't need authenticated fetch here because there is
# already the authorization check
msg=json.dumps(inboxMediaFeed).encode('utf-8')
self._set_headers('application/json',len(msg),None)
self.wfile.write(msg)
self.server.GETbusy=False
return
else:
if self.server.debug:
nickname=self.path.replace('/users/','').replace('/tlmedia','')
print('DEBUG: '+nickname+ \
' was not authorized to access '+self.path)
if self.path!='/tlmedia':
# not the media inbox
if self.server.debug:
print('DEBUG: GET access to inbox is unauthorized')
self.send_response(405)
self.end_headers()
self.server.GETbusy=False
return
# get outbox feed for a person # get outbox feed for a person
outboxFeed=personBoxJson(self.server.baseDir,self.server.domain, \ outboxFeed=personBoxJson(self.server.baseDir,self.server.domain, \
self.server.port,self.path, \ self.server.port,self.path, \

View File

@ -28,6 +28,8 @@
--button-height: 10px; --button-height: 10px;
--button-height-padding-mobile: 20px; --button-height-padding-mobile: 20px;
--button-height-padding: 10px; --button-height-padding: 10px;
--gallery-border: #ccc;
--gallery-hover: #777;
} }
body, html { body, html {
@ -760,10 +762,35 @@ input[type=checkbox]
transform: translateY(-10%); transform: translateY(-10%);
} }
div.gallery {
margin: 5px;
border: 1px solid var(--gallery-border);
float: left;
width: 180px;
}
div.gallery:hover {
border: 1px solid var(--gallery-hover);
}
div.gallery img {
width: 100%;
height: auto;
}
div.imagedesc {
padding: 22px;
text-align: center;
}
@media screen and (min-width: 400px) { @media screen and (min-width: 400px) {
body, html { body, html {
font-size: 22px; font-size: 22px;
} }
div.imagedesc {
padding: 22px;
text-align: center;
}
.container img { .container img {
float: left; float: left;
max-width: 400px; max-width: 400px;
@ -989,6 +1016,10 @@ input[type=checkbox]
body, html { body, html {
font-size: 35px; font-size: 35px;
} }
div.imagedesc {
padding: 35px;
text-align: center;
}
.container img { .container img {
float: left; float: left;
max-width: 400px; max-width: 400px;

View File

@ -1585,6 +1585,11 @@ def createRepliesTimeline(baseDir: str,nickname: str,domain: str,port: int,httpP
return createBoxBase(baseDir,'tlreplies',nickname,domain,port,httpPrefix, \ return createBoxBase(baseDir,'tlreplies',nickname,domain,port,httpPrefix, \
itemsPerPage,headerOnly,True,ocapAlways,pageNumber) itemsPerPage,headerOnly,True,ocapAlways,pageNumber)
def createMediaTimeline(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \
itemsPerPage: int,headerOnly: bool,ocapAlways: bool,pageNumber=None) -> {}:
return createBoxBase(baseDir,'tlmedia',nickname,domain,port,httpPrefix, \
itemsPerPage,headerOnly,True,ocapAlways,pageNumber)
def createOutbox(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: str, \
itemsPerPage: int,headerOnly: bool,authorized: bool,pageNumber=None) -> {}: itemsPerPage: int,headerOnly: bool,authorized: bool,pageNumber=None) -> {}:
return createBoxBase(baseDir,'outbox',nickname,domain,port,httpPrefix, \ return createBoxBase(baseDir,'outbox',nickname,domain,port,httpPrefix, \
@ -1684,6 +1689,30 @@ def isDM(postJsonObject: {}) -> bool:
return False return False
return True return True
def isImageMedia(postJsonObject: {}) -> bool:
"""Returns true if the given post has attached image media
"""
if postJsonObject['type']!='Create':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if postJsonObject['object']['type']!='Note':
return False
if not postJsonObject['object'].get('attachment'):
return False
if not isinstance(postJsonObject['object']['attachment'], list):
return False
if len(postJsonObject['object']['attachment'])==0:
return False
for attach in postJsonObject['object']['attachment']:
if attach.get('mediaType') and attach.get('url'):
mediaType=attach['mediaType']
if mediaType.startswith('image/'):
return True
return False
def isReply(postJsonObject: {},actor: str) -> bool: def isReply(postJsonObject: {},actor: str) -> bool:
"""Returns true if the given post is a reply to the given actor """Returns true if the given post is a reply to the given actor
""" """
@ -1707,15 +1736,16 @@ def createBoxBase(baseDir: str,boxname: str, \
ocapAlways: bool,pageNumber=None) -> {}: ocapAlways: bool,pageNumber=None) -> {}:
"""Constructs the box feed for a person with the given nickname """Constructs the box feed for a person with the given nickname
""" """
if boxname!='inbox' and boxname!='dm' and boxname!='tlreplies' and boxname!='outbox': if boxname!='inbox' and boxname!='dm' and \
boxname!='tlreplies' and boxname!='tlmedia' and boxname!='outbox':
return None return None
if boxname!='dm' and boxname!='tlreplies': if boxname!='dm' and boxname!='tlreplies' and boxname!='tlmedia':
boxDir = createPersonDir(nickname,domain,baseDir,boxname) boxDir = createPersonDir(nickname,domain,baseDir,boxname)
else: else:
# extract DMs or replies from the inbox # extract DMs or replies from the inbox
boxDir = createPersonDir(nickname,domain,baseDir,'inbox') boxDir = createPersonDir(nickname,domain,baseDir,'inbox')
sharedBoxDir=None sharedBoxDir=None
if boxname=='inbox' or boxname=='tlreplies': if boxname=='inbox' or boxname=='tlreplies' or boxname=='tlmedia':
sharedBoxDir = createPersonDir('inbox',domain,baseDir,boxname) sharedBoxDir = createPersonDir('inbox',domain,baseDir,boxname)
if port: if port:
@ -1865,7 +1895,7 @@ def createBoxBase(baseDir: str,boxname: str, \
# get the post as json # get the post as json
p = json.loads(postStr) p = json.loads(postStr)
if (boxname!='dm' and boxname!='tlreplies'): if (boxname!='dm' and boxname!='tlreplies' and boxname!='tlmedia'):
isTimelinePost=True isTimelinePost=True
else: else:
if boxname=='dm': if boxname=='dm':
@ -1874,6 +1904,9 @@ def createBoxBase(baseDir: str,boxname: str, \
elif boxname=='tlreplies': elif boxname=='tlreplies':
if isDM(p) or isReply(p,boxActor): if isDM(p) or isReply(p,boxActor):
isTimelinePost=True isTimelinePost=True
elif boxname=='tlmedia':
if isImageMedia(p):
isTimelinePost=True
if isTimelinePost and currPage == pageNumber: if isTimelinePost and currPage == pageNumber:
# remove any capability so that it's not displayed # remove any capability so that it's not displayed
@ -2084,7 +2117,9 @@ def sendCapabilitiesUpdate(session,baseDir: str,httpPrefix: str, \
sendThreads,postLog,cachedWebfingers, \ sendThreads,postLog,cachedWebfingers, \
personCache,debug,projectVersion) personCache,debug,projectVersion)
def populateRepliesJson(baseDir: str,nickname: str,domain: str,postRepliesFilename: str,authorized: bool,repliesJson: {}) -> None: def populateRepliesJson(baseDir: str,nickname: str,domain: str, \
postRepliesFilename: str,authorized: bool, \
repliesJson: {}) -> None:
# populate the items list with replies # populate the items list with replies
repliesBoxes=['outbox','inbox'] repliesBoxes=['outbox','inbox']
with open(postRepliesFilename,'r') as repliesFile: with open(postRepliesFilename,'r') as repliesFile:

View File

@ -1666,6 +1666,7 @@ def individualPostAsHtml(iconsDir: str,translate: {}, \
showDMicon=True showDMicon=True
titleStr='' titleStr=''
galleryStr=''
isAnnounced=False isAnnounced=False
if postJsonObject['type']=='Announce': if postJsonObject['type']=='Announce':
if postJsonObject.get('object'): if postJsonObject.get('object'):
@ -1875,6 +1876,17 @@ def individualPostAsHtml(iconsDir: str,translate: {}, \
attach['url'].endswith('.gif'): attach['url'].endswith('.gif'):
if attachmentCtr>0: if attachmentCtr>0:
attachmentStr+='<br>' attachmentStr+='<br>'
if boxName=='tlmedia':
galleryStr+= \
'<div class="gallery">\n' \
' <a target="_blank" href="'+attach['url']+'">\n' \
' <img src="'+attach['url']+'" alt="'+imageDescription+'" title="'+imageDescription+'" width="600" height="400">\n' \
' </a>\n'
if postJsonObject['object'].get('content'):
galleryStr+= \
' <div class="imagedesc">'+postJsonObject['object']['content']+'</div>\n'
galleryStr+= \
'</div>\n' \
attachmentStr+= \ attachmentStr+= \
'<a href="'+attach['url']+'">' \ '<a href="'+attach['url']+'">' \
'<img src="'+attach['url']+'" alt="'+imageDescription+'" title="'+imageDescription+'" class="attachment"></a>\n' '<img src="'+attach['url']+'" alt="'+imageDescription+'" title="'+imageDescription+'" class="attachment"></a>\n'
@ -2064,12 +2076,15 @@ def individualPostAsHtml(iconsDir: str,translate: {}, \
contentStr='<div class="message">'+contentStr+'</div>' contentStr='<div class="message">'+contentStr+'</div>'
return \ if boxName!='tlmedia':
'<div class="'+containerClass+'">\n'+ \ return \
avatarImageInPost+ \ '<div class="'+containerClass+'">\n'+ \
'<p class="post-title">'+titleStr+replyAvatarImageInPost+'</p>'+ \ avatarImageInPost+ \
contentStr+footerStr+ \ '<p class="post-title">'+titleStr+replyAvatarImageInPost+'</p>'+ \
'</div>\n' contentStr+footerStr+ \
'</div>\n'
else:
return galleryStr
def isQuestion(postObjectJson: {}) -> bool: def isQuestion(postObjectJson: {}) -> bool:
""" is the given post a question? """ is the given post a question?
@ -2106,6 +2121,7 @@ def htmlTimeline(translate: {},pageNumber: int, \
inboxButton='button' inboxButton='button'
dmButton='button' dmButton='button'
repliesButton='button' repliesButton='button'
mediaButton='button'
sentButton='button' sentButton='button'
moderationButton='button' moderationButton='button'
if boxName=='inbox': if boxName=='inbox':
@ -2114,6 +2130,8 @@ def htmlTimeline(translate: {},pageNumber: int, \
dmButton='buttonselected' dmButton='buttonselected'
elif boxName=='tlreplies': elif boxName=='tlreplies':
repliesButton='buttonselected' repliesButton='buttonselected'
elif boxName=='tlmedia':
mediaButton='buttonselected'
elif boxName=='outbox': elif boxName=='outbox':
sentButton='buttonselected' sentButton='buttonselected'
elif boxName=='moderation': elif boxName=='moderation':
@ -2125,7 +2143,8 @@ def htmlTimeline(translate: {},pageNumber: int, \
showIndividualPostIcons=True showIndividualPostIcons=True
followApprovals='' followApprovals=''
followRequestsFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/followrequests.txt' followRequestsFilename= \
baseDir+'/accounts/'+nickname+'@'+domain+'/followrequests.txt'
if os.path.isfile(followRequestsFilename): if os.path.isfile(followRequestsFilename):
with open(followRequestsFilename,'r') as f: with open(followRequestsFilename,'r') as f:
for line in f: for line in f:
@ -2160,6 +2179,7 @@ def htmlTimeline(translate: {},pageNumber: int, \
' <a href="'+actor+'/inbox"><button class="'+inboxButton+'"><span>'+translate['Inbox']+'</span></button></a>' \ ' <a href="'+actor+'/inbox"><button class="'+inboxButton+'"><span>'+translate['Inbox']+'</span></button></a>' \
' <a href="'+actor+'/dm"><button class="'+dmButton+'"><span>'+translate['DM']+'</span></button></a>' \ ' <a href="'+actor+'/dm"><button class="'+dmButton+'"><span>'+translate['DM']+'</span></button></a>' \
' <a href="'+actor+'/tlreplies"><button class="'+repliesButton+'"><span>'+translate['Replies']+'</span></button></a>' \ ' <a href="'+actor+'/tlreplies"><button class="'+repliesButton+'"><span>'+translate['Replies']+'</span></button></a>' \
' <a href="'+actor+'/tlmedia"><button class="'+mediaButton+'"><span>'+translate['Media']+'</span></button></a>' \
' <a href="'+actor+'/outbox"><button class="'+sentButton+'"><span>'+translate['Outbox']+'</span></button></a>'+ \ ' <a href="'+actor+'/outbox"><button class="'+sentButton+'"><span>'+translate['Outbox']+'</span></button></a>'+ \
moderationButtonStr+newPostButtonStr+ \ moderationButtonStr+newPostButtonStr+ \
' <a href="'+actor+'/search"><img src="/'+iconsDir+'/search.png" title="'+translate['Search and follow']+'" alt="'+translate['Search and follow']+'" class="right"/></a>'+ \ ' <a href="'+actor+'/search"><img src="/'+iconsDir+'/search.png" title="'+translate['Search and follow']+'" alt="'+translate['Search and follow']+'" class="right"/></a>'+ \
@ -2250,6 +2270,18 @@ def htmlInboxReplies(translate: {},pageNumber: int,itemsPerPage: int, \
nickname,domain,port,inboxJson,'tlreplies',allowDeletion, \ nickname,domain,port,inboxJson,'tlreplies',allowDeletion, \
httpPrefix,projectVersion,False) httpPrefix,projectVersion,False)
def htmlInboxMedia(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 media timeline as html
"""
return htmlTimeline(translate,pageNumber, \
itemsPerPage,session,baseDir,wfRequest,personCache, \
nickname,domain,port,inboxJson,'tlmedia',allowDeletion, \
httpPrefix,projectVersion,False)
def htmlModeration(translate: {},pageNumber: int,itemsPerPage: int, \ def htmlModeration(translate: {},pageNumber: int,itemsPerPage: int, \
session,baseDir: str,wfRequest: {},personCache: {}, \ session,baseDir: str,wfRequest: {},personCache: {}, \
nickname: str,domain: str,port: int,inboxJson: {}, \ nickname: str,domain: str,port: int,inboxJson: {}, \