Start of public blog

main
Bob Mottram 2020-02-25 13:35:41 +00:00
parent 372d9c3328
commit 8e8261e2cf
4 changed files with 341 additions and 60 deletions

260
blog.py 100644
View File

@ -0,0 +1,260 @@
__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

104
daemon.py
View File

@ -108,7 +108,9 @@ from roles import setRole
from roles import clearModeratorStatus
from skills import outboxSkills
from availability import outboxAvailability
from webinterface import htmlBlogPost
from blog import htmlBlogView
from blog import htmlBlogPage
from blog import htmlBlogPost
from webinterface import htmlCalendarDeleteConfirm
from webinterface import htmlDeletePost
from webinterface import htmlAbout
@ -995,7 +997,61 @@ class PubServer(BaseHTTPRequestHandler):
self._benchmarkGETtimings(GETstartTime,GETtimings,8)
if htmlGET and '/users/' in self.path:
# show the main blog page
if htmlGET and (self.path=='/blog' or self.path=='/blogs'):
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
msg=htmlBlogView(self.server.session, \
self.server.baseDir, \
self.server.httpPrefix, \
self.server.translate, \
self.server.domain,self.server.port, \
maxPostsInBlogsFeed)
if msg!=None:
msg=msg.encode()
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
return
self._404()
return
if htmlGET and self.path.startswith('/blog/'):
pageNumber=1
nickname=self.path.split('/blog/')[1]
if '/' in nickname:
nickname=nickname.split('/')[0]
if '?' in nickname:
nickname=nickname.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)
if pageNumber<1:
pageNumber=1
elif pageNumber>10:
pageNumber=10
if not self.server.session:
self.server.session= \
createSession(self.server.useTor)
msg=htmlBlogPage(self.server.session, \
self.server.baseDir, \
self.server.httpPrefix, \
self.server.translate, \
nickname, \
self.server.domain,self.server.port, \
maxPostsInBlogsFeed,pageNumber)
if msg!=None:
msg=msg.encode()
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
return
self._404()
return
if htmlGET and '/users/' in self.path:
# show the person options screen with view/follow/block/report
if '?options=' in self.path:
optionsStr=self.path.split('?options=')[1]
@ -1040,28 +1096,28 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStrAbsolute,cookie)
return
# show blog post
blogFilename,nickname= \
self._pathContainsBlogLink(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
self.server.domainFull, \
self.path)
if blogFilename and nickname:
postJsonObject=loadJson(blogFilename)
if isBlogPost(postJsonObject):
msg=htmlBlogPost(self.server.baseDir, \
self.server.httpPrefix, \
self.server.translate, \
nickname,self.server.domain, \
postJsonObject)
if msg!=None:
msg=msg.encode()
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
return
self._404()
return
# show blog post
blogFilename,nickname= \
self._pathContainsBlogLink(self.server.baseDir, \
self.server.httpPrefix, \
self.server.domain, \
self.server.domainFull, \
self.path)
if blogFilename and nickname:
postJsonObject=loadJson(blogFilename)
if isBlogPost(postJsonObject):
msg=htmlBlogPost(self.server.baseDir, \
self.server.httpPrefix, \
self.server.translate, \
nickname,self.server.domain, \
postJsonObject)
if msg!=None:
msg=msg.encode()
self._set_headers('text/html',len(msg),cookie)
self._write(msg)
return
self._404()
return
self._benchmarkGETtimings(GETstartTime,GETtimings,9)

View File

@ -466,7 +466,7 @@ def personLookup(domain: str,path: str,baseDir: str) -> {}:
def personBoxJson(recentPostsCache: {}, \
session,baseDir: str,domain: str,port: int,path: str, \
httpPrefix: str,noOfItems: int,boxname: str, \
authorized: bool,ocapAlways: bool) -> []:
authorized: bool,ocapAlways: bool) -> {}:
"""Obtain the inbox/outbox/moderation feed for the given person
"""
if boxname!='inbox' and boxname!='dm' and \

View File

@ -5011,38 +5011,3 @@ def htmlProfileAfterSearch(recentPostsCache: {},maxRecentPosts: int, \
return htmlHeader(cssFilename,profileStyle)+profileStr+htmlFooter()
def htmlBlogPost(baseDir: str,httpPrefix: str,translate: {}, \
nickname: str,domain: 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)
if postJsonObject['object'].get('summary'):
blogStr+='<h1>'+postJsonObject['object']['summary']+'</h1>\n'
if postJsonObject['object'].get('published'):
if 'T' in postJsonObject['object']['published']:
blogStr+='<h3>'+postJsonObject['object']['published'].split('T')[0]+'</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'
blogStr+= \
'<p class="about"><a href="'+httpPrefix+'://'+domain+ \
'/users/'+nickname+'">'+translate['About the author']+'</a></p>\n'
return blogStr+htmlFooter()
return None