forked from indymedia/epicyon
261 lines
8.8 KiB
Python
261 lines
8.8 KiB
Python
__filename__ = "blog.py"
|
|
__author__ = "Bob Mottram"
|
|
__license__ = "AGPL3+"
|
|
__version__ = "1.1.0"
|
|
__maintainer__ = "Bob Mottram"
|
|
__email__ = "bob@freedombone.net"
|
|
__status__ = "Production"
|
|
|
|
import json
|
|
import time
|
|
import os
|
|
from collections import OrderedDict
|
|
from datetime import datetime
|
|
from datetime import date
|
|
from dateutil.parser import parse
|
|
from shutil import copyfile
|
|
from shutil import copyfileobj
|
|
from pprint import pprint
|
|
|
|
from content import replaceEmojiFromTags
|
|
from webinterface import htmlHeader
|
|
from webinterface import htmlFooter
|
|
from webinterface import addEmbeddedElements
|
|
from utils import getNicknameFromActor
|
|
from utils import getDomainFromActor
|
|
from posts import createBlogsTimeline
|
|
|
|
|
|
def htmlBlogPostContent(baseDir: str,httpPrefix: str,translate: {}, \
|
|
nickname: str,domain: str,domainFull: str, \
|
|
postJsonObject: {}, \
|
|
handle: str,restrictToDomain: bool) -> str:
|
|
"""Returns the content for a single blog post
|
|
"""
|
|
linkedAuthor=False
|
|
actor=''
|
|
blogStr=''
|
|
if postJsonObject['object'].get('summary'):
|
|
blogStr+='<h1>'+postJsonObject['object']['summary']+'</h1>\n'
|
|
|
|
# get the handle of the author
|
|
if postJsonObject['object'].get('attributedTo'):
|
|
actor=postJsonObject['object']['attributedTo']
|
|
authorNickname=getNicknameFromActor(actor)
|
|
if authorNickname:
|
|
authorDomain,authorPort=getDomainFromActor(actor)
|
|
if authorDomain:
|
|
# author must be from the given domain
|
|
if restrictToDomain and authorDomain != domain:
|
|
return ''
|
|
handle=authorNickname+'@'+authorDomain
|
|
else:
|
|
# posts from the domain are expected to have an attributedTo field
|
|
if restrictToDomain:
|
|
return ''
|
|
|
|
if postJsonObject['object'].get('published'):
|
|
if 'T' in postJsonObject['object']['published']:
|
|
blogStr+='<h3>'+postJsonObject['object']['published'].split('T')[0]
|
|
if handle:
|
|
if handle.startswith(nickname+'@'+domain):
|
|
blogStr+= \
|
|
' <a href="'+httpPrefix+'://'+domainFull+ \
|
|
'/users/'+nickname+'">'+handle+'</a>'
|
|
linkedAuthor=True
|
|
else:
|
|
if author:
|
|
blogStr+= \
|
|
' <a href="'+author+'">'+handle+'</a>'
|
|
linkedAuthor=True
|
|
else:
|
|
blogStr+=' '+handle
|
|
blogStr+='</h3>\n'
|
|
|
|
if postJsonObject['object'].get('content'):
|
|
contentStr=addEmbeddedElements(translate,postJsonObject['object']['content'])
|
|
if postJsonObject['object'].get('tag'):
|
|
contentStr= \
|
|
replaceEmojiFromTags(contentStr, \
|
|
postJsonObject['object']['tag'],'content')
|
|
blogStr+='<br>'+contentStr+'\n'
|
|
|
|
blogStr+='<br><hr>\n'
|
|
if not linkedAuthor:
|
|
blogStr+= \
|
|
'<p class="about"><a href="'+httpPrefix+'://'+domainFull+ \
|
|
'/users/'+nickname+'">'+translate['About the author']+'</a></p>\n'
|
|
return blogStr
|
|
|
|
|
|
def htmlBlogPost(baseDir: str,httpPrefix: str,translate: {}, \
|
|
nickname: str,domainFull: str,postJsonObject: {}) -> str:
|
|
"""Returns a html blog post
|
|
"""
|
|
blogStr=''
|
|
|
|
cssFilename=baseDir+'/epicyon-blog.css'
|
|
if os.path.isfile(baseDir+'/blog.css'):
|
|
cssFilename=baseDir+'/blog.css'
|
|
with open(cssFilename, 'r') as cssFile:
|
|
blogCSS=cssFile.read()
|
|
blogStr=htmlHeader(cssFilename,blogCSS)
|
|
|
|
blogStr+= \
|
|
htmlBlogPostContent(baseDir,httpPrefix,translate, \
|
|
nickname,domainFull,postJsonObject, \
|
|
None,False)
|
|
|
|
return blogStr+htmlFooter()
|
|
return None
|
|
|
|
|
|
def htmlBlogPage(session, \
|
|
baseDir: str,httpPrefix: str,translate: {}, \
|
|
nickname: str,domain: str,port: int, \
|
|
noOfItems: int,pageNumber: int) -> str:
|
|
"""Returns a html blog page containing posts
|
|
"""
|
|
if ' ' in nickname or '@' in nickname or '\n' in nickname:
|
|
return None
|
|
blogStr=''
|
|
|
|
cssFilename=baseDir+'/epicyon-blog.css'
|
|
if os.path.isfile(baseDir+'/blog.css'):
|
|
cssFilename=baseDir+'/blog.css'
|
|
with open(cssFilename, 'r') as cssFile:
|
|
blogCSS=cssFile.read()
|
|
blogStr=htmlHeader(cssFilename,blogCSS)
|
|
|
|
blogsIndex= \
|
|
baseDir+'/accounts/'+nickname+'@'+domain+'/tlblogs.index'
|
|
if not os.path.isfile(blogsIndex):
|
|
return blogStr+htmlFooter()
|
|
|
|
if pageNumber:
|
|
if pageNumber>1:
|
|
# show previous button
|
|
print('TODO previous')
|
|
# show next button
|
|
print('TODO next')
|
|
|
|
timelineJson= \
|
|
createBlogsTimeline(session,baseDir, \
|
|
nickname,domain,port,httpPrefix, \
|
|
noOfItems,False,False,pageNumber)
|
|
|
|
if not timelineJson:
|
|
return blogStr+htmlFooter()
|
|
|
|
domainFull=domain
|
|
if port:
|
|
if port!=80 and port!=443:
|
|
domainFull=domain+':'+str(port)
|
|
|
|
for item in timelineJson['orderedItems']:
|
|
if item['type']!='Create':
|
|
continue
|
|
|
|
blogStr+= \
|
|
htmlBlogPostContent(baseDir,httpPrefix,translate, \
|
|
nickname,domainFull,item, \
|
|
None,True)
|
|
|
|
return blogStr+htmlFooter()
|
|
return None
|
|
|
|
|
|
def getBlogIndexesForAccounts(baseDir: str) -> {}:
|
|
""" Get the index files for blogs for each account
|
|
and add them to a dict
|
|
"""
|
|
blogIndexes={}
|
|
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
|
for acct in dirs:
|
|
if '@' not in acct:
|
|
continue
|
|
if 'inbox@' in acct:
|
|
continue
|
|
accountDir=os.path.join(baseDir+'/accounts', acct)
|
|
blogsIndex=accountDir+'/tlblogs.index'
|
|
if os.path.isfile(blogsIndex):
|
|
blogIndexes[acct]=blogsIndex
|
|
return blogIndexes
|
|
|
|
def noOfBlogAccounts(baseDir: str) -> int:
|
|
"""Returns the number of blog accounts
|
|
"""
|
|
ctr=0
|
|
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
|
for acct in dirs:
|
|
if '@' not in acct:
|
|
continue
|
|
if 'inbox@' not in acct:
|
|
continue
|
|
accountDir=os.path.join(baseDir+'/accounts', acct)
|
|
blogsIndex=accountDir+'/tlblogs.index'
|
|
if os.path.isfile(blogsIndex):
|
|
ctr+=1
|
|
return ctr
|
|
|
|
def singleBlogAccountNickname(baseDir: str) -> str:
|
|
"""Returns the nickname of a single blog account
|
|
"""
|
|
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
|
for acct in dirs:
|
|
if '@' not in acct:
|
|
continue
|
|
if 'inbox@' in acct:
|
|
continue
|
|
accountDir=os.path.join(baseDir+'/accounts', acct)
|
|
blogsIndex=accountDir+'/tlblogs.index'
|
|
if os.path.isfile(blogsIndex):
|
|
return acct.split('@')[0]
|
|
return None
|
|
|
|
def htmlBlogView(session,baseDir: str,httpPrefix: str, \
|
|
translate: {},domain: str,port: int, \
|
|
noOfItems: int) -> str:
|
|
"""Show the blog main page
|
|
"""
|
|
blogStr=''
|
|
|
|
cssFilename=baseDir+'/epicyon-blog.css'
|
|
if os.path.isfile(baseDir+'/blog.css'):
|
|
cssFilename=baseDir+'/blog.css'
|
|
with open(cssFilename, 'r') as cssFile:
|
|
blogCSS=cssFile.read()
|
|
blogStr=htmlHeader(cssFilename,blogCSS)
|
|
|
|
if noOfBlogAccounts(baseDir) <= 1:
|
|
nickname=singleBlogAccountNickname(baseDir)
|
|
if nickname:
|
|
return htmlBlogPage(session, \
|
|
baseDir,httpPrefix,translate, \
|
|
nickname,domain,port, \
|
|
noOfItems,1)
|
|
|
|
domainFull=domain
|
|
if port:
|
|
if port!=80 and port!=443:
|
|
domainFull=domain+':'+str(port)
|
|
|
|
for subdir, dirs, files in os.walk(baseDir+'/accounts'):
|
|
for acct in dirs:
|
|
if '@' not in acct:
|
|
continue
|
|
if 'inbox@' in acct:
|
|
continue
|
|
accountDir=os.path.join(baseDir+'/accounts', acct)
|
|
blogsIndex=accountDir+'/tlblogs.index'
|
|
if os.path.isfile(blogsIndex):
|
|
blogStr+='<p class="blogaccount">'
|
|
blogStr+= \
|
|
'<a href="'+ \
|
|
httpPrefix+'://'+domainFull+'/blog/'+ \
|
|
acct.split('@')[0]+'">'+acct+'</a>'
|
|
blogStr+='</p>'
|
|
|
|
return blogStr+htmlFooter()
|
|
return None
|