forked from indymedia/epicyon
Start of public blog
parent
372d9c3328
commit
8e8261e2cf
|
@ -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
104
daemon.py
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue