forked from indymedia/epicyon
Media timeline
parent
160f272b5d
commit
8ed3f132be
73
daemon.py
73
daemon.py
|
@ -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, \
|
||||||
|
|
|
@ -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;
|
||||||
|
|
45
posts.py
45
posts.py
|
@ -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:
|
||||||
|
|
|
@ -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: {}, \
|
||||||
|
|
Loading…
Reference in New Issue