epicyon/blog.py

830 lines
31 KiB
Python
Raw Normal View History

2020-04-01 21:29:04 +00:00
__filename__ = "blog.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2020-02-25 13:35:41 +00:00
import os
from datetime import datetime
from content import replaceEmojiFromTags
2020-02-25 18:21:32 +00:00
from webinterface import getIconsDir
2020-02-25 16:57:25 +00:00
from webinterface import getPostAttachmentsAsHtml
2020-02-25 13:35:41 +00:00
from webinterface import htmlHeader
from webinterface import htmlFooter
from webinterface import addEmbeddedElements
from utils import getNicknameFromActor
from utils import getDomainFromActor
2020-03-01 12:33:20 +00:00
from utils import locatePost
from utils import loadJson
2020-02-25 13:35:41 +00:00
from posts import createBlogsTimeline
2020-04-01 21:29:04 +00:00
def noOfBlogReplies(baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
postId: str, depth=0) -> int:
2020-02-25 20:31:37 +00:00
"""Returns the number of replies on the post
2020-02-26 10:03:09 +00:00
This is recursive, so can handle replies to replies
2020-02-25 20:31:37 +00:00
"""
2020-04-01 21:29:04 +00:00
if depth > 4:
2020-02-26 10:03:09 +00:00
return 0
if not postId:
2020-02-25 20:31:37 +00:00
return 0
2020-02-26 10:10:16 +00:00
2020-04-01 21:29:04 +00:00
tryPostBox = ('tlblogs', 'inbox', 'outbox')
boxFound = False
2020-02-26 10:10:16 +00:00
for postBox in tryPostBox:
2020-04-01 21:29:04 +00:00
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + postBox + '/' + \
postId.replace('/', '#') + '.replies'
2020-02-26 10:10:16 +00:00
if os.path.isfile(postFilename):
2020-04-01 21:29:04 +00:00
boxFound = True
2020-02-26 10:10:16 +00:00
break
if not boxFound:
2020-02-26 10:26:04 +00:00
# post may exist but has no replies
for postBox in tryPostBox:
2020-04-01 21:29:04 +00:00
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + postBox + '/' + \
postId.replace('/', '#')
2020-02-26 10:26:04 +00:00
if os.path.isfile(postFilename):
return 1
2020-02-25 20:31:37 +00:00
return 0
2020-02-26 10:10:16 +00:00
removals = []
2020-04-01 21:29:04 +00:00
replies = 0
lines = []
2020-02-25 20:31:37 +00:00
with open(postFilename, "r") as f:
2020-04-01 21:29:04 +00:00
lines = f.readlines()
2020-02-26 10:03:09 +00:00
for replyPostId in lines:
2020-05-22 11:32:38 +00:00
replyPostId = replyPostId.replace('\n', '').replace('\r', '')
replyPostId = replyPostId.replace('.json', '')
if locatePost(baseDir, nickname, domain, replyPostId):
replyPostId = replyPostId.replace('.replies', '')
replies += 1 + noOfBlogReplies(baseDir, httpPrefix, translate,
nickname, domain, domainFull,
replyPostId, depth+1)
else:
# remove post which no longer exists
removals.append(replyPostId)
# remove posts from .replies file if they don't exist
if lines and removals:
print('Rewriting ' + postFilename + ' to remove ' +
str(len(removals)) + ' entries')
2020-08-29 11:14:19 +00:00
with open(postFilename, 'w+') as f:
for replyPostId in lines:
2020-05-22 11:32:38 +00:00
replyPostId = replyPostId.replace('\n', '').replace('\r', '')
if replyPostId not in removals:
f.write(replyPostId + '\n')
2020-02-26 10:03:09 +00:00
return replies
2020-02-25 20:31:37 +00:00
2020-04-01 21:29:04 +00:00
def getBlogReplies(baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
postId: str, depth=0) -> str:
2020-05-18 14:00:47 +00:00
"""Returns a string containing html blog posts
2020-02-25 20:53:02 +00:00
"""
2020-04-01 21:29:04 +00:00
if depth > 4:
2020-02-26 10:03:09 +00:00
return ''
if not postId:
2020-02-25 20:53:02 +00:00
return ''
2020-02-26 10:10:16 +00:00
2020-04-01 21:29:04 +00:00
tryPostBox = ('tlblogs', 'inbox', 'outbox')
boxFound = False
2020-02-26 10:10:16 +00:00
for postBox in tryPostBox:
2020-04-01 21:29:04 +00:00
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + postBox + '/' + \
postId.replace('/', '#') + '.replies'
2020-02-26 10:10:16 +00:00
if os.path.isfile(postFilename):
2020-04-01 21:29:04 +00:00
boxFound = True
2020-02-26 10:10:16 +00:00
break
if not boxFound:
2020-02-26 10:26:04 +00:00
# post may exist but has no replies
for postBox in tryPostBox:
2020-04-01 21:29:04 +00:00
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + postBox + '/' + \
2020-05-18 14:00:47 +00:00
postId.replace('/', '#') + '.json'
2020-02-26 10:26:04 +00:00
if os.path.isfile(postFilename):
2020-04-01 21:29:04 +00:00
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + \
'/postcache/' + \
postId.replace('/', '#') + '.html'
2020-02-26 10:26:04 +00:00
if os.path.isfile(postFilename):
with open(postFilename, "r") as postFile:
2020-04-01 21:29:04 +00:00
return postFile.read() + '\n'
2020-02-25 20:53:02 +00:00
return ''
2020-02-26 10:10:16 +00:00
2020-02-25 20:53:02 +00:00
with open(postFilename, "r") as f:
2020-04-01 21:29:04 +00:00
lines = f.readlines()
repliesStr = ''
2020-02-26 10:04:38 +00:00
for replyPostId in lines:
2020-05-22 11:32:38 +00:00
replyPostId = replyPostId.replace('\n', '').replace('\r', '')
replyPostId = replyPostId.replace('.json', '')
2020-04-01 21:29:04 +00:00
replyPostId = replyPostId.replace('.replies', '')
postFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + \
'/postcache/' + \
2020-05-22 11:32:38 +00:00
replyPostId.replace('/', '#') + '.html'
2020-02-25 20:53:02 +00:00
if not os.path.isfile(postFilename):
continue
with open(postFilename, "r") as postFile:
2020-04-01 21:29:04 +00:00
repliesStr += postFile.read() + '\n'
2020-05-18 16:12:57 +00:00
rply = getBlogReplies(baseDir, httpPrefix, translate,
nickname, domain, domainFull,
replyPostId, depth+1)
if rply not in repliesStr:
repliesStr += rply
2020-02-26 10:03:09 +00:00
# indicate the reply indentation level
2020-04-01 21:29:04 +00:00
indentStr = '>'
2020-02-26 10:03:09 +00:00
for indentLevel in range(depth):
2020-04-01 21:29:04 +00:00
indentStr += ' >'
2020-02-26 10:03:09 +00:00
2020-04-01 21:29:04 +00:00
repliesStr = repliesStr.replace(translate['SHOW MORE'], indentStr)
return repliesStr.replace('?tl=outbox', '?tl=tlblogs')
2020-02-25 20:53:02 +00:00
return ''
2020-04-01 21:29:04 +00:00
def htmlBlogPostContent(authorized: bool,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
postJsonObject: {},
2020-05-18 11:32:28 +00:00
handle: str, restrictToDomain: bool,
blogSeparator='<hr>') -> str:
2020-02-25 13:35:41 +00:00
"""Returns the content for a single blog post
"""
2020-04-01 21:29:04 +00:00
linkedAuthor = False
actor = ''
blogStr = ''
messageLink = ''
2020-02-25 15:32:43 +00:00
if postJsonObject['object'].get('id'):
2020-04-01 21:29:04 +00:00
messageLink = postJsonObject['object']['id'].replace('/statuses/', '/')
titleStr = ''
2020-02-25 13:35:41 +00:00
if postJsonObject['object'].get('summary'):
2020-04-01 21:29:04 +00:00
titleStr = postJsonObject['object']['summary']
blogStr += '<h1><a href="' + messageLink + '">' + \
titleStr + '</a></h1>\n'
2020-02-25 13:35:41 +00:00
# get the handle of the author
if postJsonObject['object'].get('attributedTo'):
2020-08-06 16:21:46 +00:00
authorNickname = None
if isinstance(postJsonObject['object']['attributedTo'], str):
actor = postJsonObject['object']['attributedTo']
authorNickname = getNicknameFromActor(actor)
2020-02-25 13:35:41 +00:00
if authorNickname:
2020-04-01 21:29:04 +00:00
authorDomain, authorPort = getDomainFromActor(actor)
2020-02-25 13:35:41 +00:00
if authorDomain:
# author must be from the given domain
if restrictToDomain and authorDomain != domain:
return ''
2020-04-01 21:29:04 +00:00
handle = authorNickname + '@' + authorDomain
2020-02-25 13:35:41 +00:00
else:
# posts from the domain are expected to have an attributedTo field
if restrictToDomain:
return ''
2020-03-22 21:16:02 +00:00
2020-02-25 13:35:41 +00:00
if postJsonObject['object'].get('published'):
if 'T' in postJsonObject['object']['published']:
2020-04-01 21:29:04 +00:00
blogStr += '<h3>' + \
postJsonObject['object']['published'].split('T')[0]
2020-02-25 13:35:41 +00:00
if handle:
2020-04-01 21:29:04 +00:00
if handle.startswith(nickname + '@' + domain):
blogStr += ' <a href="' + httpPrefix + '://' + \
domainFull + \
'/users/' + nickname + '">' + handle + '</a>'
linkedAuthor = True
2020-02-25 13:35:41 +00:00
else:
2020-04-01 21:29:04 +00:00
if actor:
blogStr += ' <a href="' + actor + '">' + \
handle + '</a>'
linkedAuthor = True
2020-02-25 13:35:41 +00:00
else:
2020-04-01 21:29:04 +00:00
blogStr += ' ' + handle
blogStr += '</h3>\n'
avatarLink = ''
replyStr = ''
announceStr = ''
likeStr = ''
bookmarkStr = ''
deleteStr = ''
muteStr = ''
isMuted = False
attachmentStr, galleryStr = getPostAttachmentsAsHtml(postJsonObject,
'tlblogs', translate,
isMuted, avatarLink,
replyStr, announceStr,
likeStr, bookmarkStr,
deleteStr, muteStr)
2020-02-25 16:57:25 +00:00
if attachmentStr:
2020-04-01 21:29:04 +00:00
blogStr += '<br><center>' + attachmentStr + '</center>'
2020-02-25 16:57:25 +00:00
2020-02-25 13:35:41 +00:00
if postJsonObject['object'].get('content'):
2020-04-01 21:29:04 +00:00
contentStr = addEmbeddedElements(translate,
postJsonObject['object']['content'])
2020-02-25 13:35:41 +00:00
if postJsonObject['object'].get('tag'):
2020-04-01 21:29:04 +00:00
contentStr = replaceEmojiFromTags(contentStr,
postJsonObject['object']['tag'],
'content')
blogStr += '<br>' + contentStr + '\n'
2020-02-25 13:35:41 +00:00
2020-05-18 11:17:13 +00:00
blogStr += '<br>\n'
2020-02-25 20:31:37 +00:00
2020-02-25 20:57:16 +00:00
if not linkedAuthor:
2020-04-01 21:29:04 +00:00
blogStr += '<p class="about"><a class="about" href="' + \
httpPrefix + '://' + domainFull + \
'/users/' + nickname + '">' + translate['About the author'] + \
'</a></p>\n'
replies = noOfBlogReplies(baseDir, httpPrefix, translate,
nickname, domain, domainFull,
postJsonObject['object']['id'])
2020-05-18 11:32:28 +00:00
# separator between blogs should be centered
if '<center>' not in blogSeparator:
blogSeparator = '<center>' + blogSeparator + '</center>'
if replies == 0:
blogStr += blogSeparator + '\n'
return blogStr
if not authorized:
blogStr += '<p class="blogreplies">' + \
translate['Replies'].lower() + ': ' + str(replies) + '</p>'
blogStr += '<br><br><br>' + blogSeparator + '\n'
else:
blogStr += blogSeparator + '<h1>' + translate['Replies'] + '</h1>\n'
if not titleStr:
blogStr += getBlogReplies(baseDir, httpPrefix, translate,
nickname, domain, domainFull,
postJsonObject['object']['id'])
2020-02-25 21:40:13 +00:00
else:
2020-05-18 11:32:28 +00:00
blogRepliesStr = getBlogReplies(baseDir, httpPrefix, translate,
nickname, domain, domainFull,
postJsonObject['object']['id'])
blogStr += blogRepliesStr.replace('>' + titleStr + '<', '')
2020-02-25 13:35:41 +00:00
return blogStr
def htmlBlogPostRSS2(authorized: bool,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
postJsonObject: {},
handle: str, restrictToDomain: bool) -> str:
"""Returns the RSS version 2 feed for a single blog post
2020-02-27 20:23:27 +00:00
"""
2020-04-01 21:29:04 +00:00
messageLink = ''
2020-02-27 20:23:27 +00:00
if postJsonObject['object'].get('id'):
2020-04-01 21:29:04 +00:00
messageLink = postJsonObject['object']['id'].replace('/statuses/', '/')
2020-02-27 20:23:27 +00:00
if not restrictToDomain or \
2020-04-01 21:29:04 +00:00
(restrictToDomain and '/' + domain in messageLink):
2020-09-26 18:23:43 +00:00
if postJsonObject['object'].get('summary') and \
postJsonObject['object'].get('published'):
2020-04-01 21:29:04 +00:00
published = postJsonObject['object']['published']
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
titleStr = postJsonObject['object']['summary']
rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT")
rssStr = ' <item>'
rssStr += ' <title>' + titleStr + '</title>'
rssStr += ' <link>' + messageLink + '</link>'
rssStr += ' <pubDate>' + rssDateStr + '</pubDate>'
rssStr += ' </item>'
2020-02-27 20:23:27 +00:00
return rssStr
2020-05-23 09:41:50 +00:00
def htmlBlogPostRSS3(authorized: bool,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
postJsonObject: {},
handle: str, restrictToDomain: bool) -> str:
"""Returns the RSS version 3 feed for a single blog post
"""
messageLink = ''
if postJsonObject['object'].get('id'):
messageLink = postJsonObject['object']['id'].replace('/statuses/', '/')
if not restrictToDomain or \
(restrictToDomain and '/' + domain in messageLink):
2020-09-26 18:23:43 +00:00
if postJsonObject['object'].get('summary') and \
postJsonObject['object'].get('published'):
2020-05-23 09:41:50 +00:00
published = postJsonObject['object']['published']
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
titleStr = postJsonObject['object']['summary']
rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT")
rssStr = 'title: ' + titleStr + '\n'
rssStr += 'link: ' + messageLink + '\n'
rssStr += 'created: ' + rssDateStr + '\n\n'
return rssStr
2020-07-03 10:07:11 +00:00
def htmlBlogRemoveCwButton(blogStr: str, translate: {}) -> str:
"""Removes the CW button from blog posts, where the
summary field is instead used as the blog title
"""
blogStr = blogStr.replace('<details>', '<b>')
blogStr = blogStr.replace('</details>', '</b>')
blogStr = blogStr.replace('<summary>', '')
blogStr = blogStr.replace('</summary>', '')
blogStr = blogStr.replace(translate['SHOW MORE'], '')
return blogStr
2020-04-01 21:29:04 +00:00
def htmlBlogPost(authorized: bool,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, domainFull: str,
2020-02-25 13:44:10 +00:00
postJsonObject: {}) -> str:
2020-02-25 13:35:41 +00:00
"""Returns a html blog post
"""
2020-04-01 21:29:04 +00:00
blogStr = ''
2020-02-25 13:35:41 +00:00
2020-05-20 12:21:46 +00:00
cssFilename = baseDir + '/epicyon-blog.css'
if os.path.isfile(baseDir + '/blog.css'):
cssFilename = baseDir + '/blog.css'
2020-02-25 13:35:41 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-01 21:29:04 +00:00
blogCSS = cssFile.read()
blogStr = htmlHeader(cssFilename, blogCSS)
2020-07-03 10:07:11 +00:00
htmlBlogRemoveCwButton(blogStr, translate)
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
blogStr += htmlBlogPostContent(authorized, baseDir,
httpPrefix, translate,
nickname, domain,
domainFull, postJsonObject,
None, False)
2020-05-23 09:41:50 +00:00
# show rss links
2020-04-01 21:29:04 +00:00
iconsDir = getIconsDir(baseDir)
blogStr += '<p class="rssfeed">'
2020-05-23 09:41:50 +00:00
2020-04-01 21:29:04 +00:00
blogStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + nickname + '/rss.xml">'
2020-09-26 19:51:22 +00:00
blogStr += '<img style="width:3%;min-width:50px" ' + \
'loading="lazy" alt="RSS 2.0" ' + \
2020-05-23 09:46:40 +00:00
'title="RSS 2.0" src="/' + \
2020-05-23 09:41:50 +00:00
iconsDir + '/rss.png" /></a>'
blogStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + nickname + '/rss.txt">'
2020-09-26 19:51:22 +00:00
blogStr += '<img style="width:3%;min-width:50px" ' + \
'loading="lazy" alt="RSS 3.0" ' + \
2020-05-23 09:46:40 +00:00
'title="RSS 3.0" src="/' + \
2020-05-23 09:41:50 +00:00
iconsDir + '/rss3.png" /></a>'
blogStr += '</p>'
2020-04-01 21:29:04 +00:00
return blogStr + htmlFooter()
2020-02-25 13:35:41 +00:00
return None
2020-04-01 21:29:04 +00:00
def htmlBlogPage(authorized: bool, session,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, port: int,
noOfItems: int, pageNumber: int) -> str:
2020-02-25 13:35:41 +00:00
"""Returns a html blog page containing posts
"""
2020-05-22 11:32:38 +00:00
if ' ' in nickname or '@' in nickname or \
'\n' in nickname or '\r' in nickname:
2020-02-25 13:35:41 +00:00
return None
2020-04-01 21:29:04 +00:00
blogStr = ''
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2020-02-25 13:35:41 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-01 21:29:04 +00:00
blogCSS = cssFile.read()
blogStr = htmlHeader(cssFilename, blogCSS)
2020-07-03 10:07:11 +00:00
htmlBlogRemoveCwButton(blogStr, translate)
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
blogsIndex = baseDir + '/accounts/' + \
nickname + '@' + domain + '/tlblogs.index'
2020-02-25 13:35:41 +00:00
if not os.path.isfile(blogsIndex):
2020-04-01 21:29:04 +00:00
return blogStr + htmlFooter()
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
timelineJson = createBlogsTimeline(session, baseDir,
nickname, domain, port,
httpPrefix,
noOfItems, False, False,
pageNumber)
2020-02-25 13:35:41 +00:00
if not timelineJson:
2020-04-01 21:29:04 +00:00
return blogStr + htmlFooter()
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
domainFull = domain
2020-02-25 13:35:41 +00:00
if port:
2020-04-01 21:29:04 +00:00
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2020-02-25 13:35:41 +00:00
2020-02-27 20:23:27 +00:00
# show previous and next buttons
2020-04-01 21:29:04 +00:00
if pageNumber is not None:
iconsDir = getIconsDir(baseDir)
navigateStr = '<p>'
if pageNumber > 1:
2020-02-25 18:29:38 +00:00
# show previous button
2020-04-01 21:29:04 +00:00
navigateStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + \
nickname + '?page=' + str(pageNumber-1) + '">' + \
'<img loading="lazy" alt="<" title="<" ' + \
'src="/' + iconsDir + \
2020-02-25 18:29:38 +00:00
'/prev.png" class="buttonprev"/></a>\n'
2020-04-01 21:29:04 +00:00
if len(timelineJson['orderedItems']) >= noOfItems:
2020-02-25 18:29:38 +00:00
# show next button
2020-04-01 21:29:04 +00:00
navigateStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + nickname + \
'?page=' + str(pageNumber + 1) + '">' + \
'<img loading="lazy" alt=">" title=">" ' + \
'src="/' + iconsDir + \
2020-02-25 18:29:38 +00:00
'/prev.png" class="buttonnext"/></a>\n'
2020-04-01 21:29:04 +00:00
navigateStr += '</p>'
blogStr += navigateStr
2020-02-27 20:23:27 +00:00
2020-02-25 13:35:41 +00:00
for item in timelineJson['orderedItems']:
2020-04-01 21:29:04 +00:00
if item['type'] != 'Create':
2020-02-25 13:35:41 +00:00
continue
2020-04-01 21:29:04 +00:00
blogStr += htmlBlogPostContent(authorized, baseDir,
httpPrefix, translate,
nickname, domain,
domainFull, item,
None, True)
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
if len(timelineJson['orderedItems']) >= noOfItems:
blogStr += navigateStr
2020-02-25 18:19:43 +00:00
2020-02-27 20:27:44 +00:00
# show rss link
2020-04-01 21:29:04 +00:00
blogStr += '<p class="rssfeed">'
2020-05-23 09:50:10 +00:00
2020-04-01 21:29:04 +00:00
blogStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + nickname + '/rss.xml">'
2020-05-23 09:50:10 +00:00
blogStr += '<img loading="lazy" alt="RSS 2.0" ' + \
'title="RSS 2.0" src="/' + \
iconsDir + '/rss.png" /></a>'
blogStr += '<a href="' + httpPrefix + '://' + \
domainFull + '/blog/' + nickname + '/rss.txt">'
blogStr += '<img loading="lazy" alt="RSS 3.0" ' + \
'title="RSS 3.0" src="/' + \
iconsDir + '/rss3.png" /></a>'
blogStr += '</p>'
2020-04-01 21:29:04 +00:00
return blogStr + htmlFooter()
2020-02-25 13:35:41 +00:00
return None
2020-04-01 21:29:04 +00:00
def rss2Header(httpPrefix: str,
nickname: str, domainFull: str, translate: {}) -> str:
2020-04-01 21:29:04 +00:00
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
rssStr += "<rss version=\"2.0\">"
rssStr += '<channel>'
rssStr += ' <title>' + translate['Blog'] + '</title>'
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
'/users/' + nickname + '/rss.xml' + '</link>'
2020-02-27 20:23:27 +00:00
return rssStr
2020-04-01 21:29:04 +00:00
def rss2Footer() -> str:
2020-04-01 21:29:04 +00:00
rssStr = '</channel>'
rssStr += '</rss>'
2020-02-27 20:23:27 +00:00
return rssStr
2020-04-01 21:29:04 +00:00
def htmlBlogPageRSS2(authorized: bool, session,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, port: int,
noOfItems: int, pageNumber: int) -> str:
"""Returns an RSS version 2 feed containing posts
2020-02-27 20:23:27 +00:00
"""
2020-05-22 11:32:38 +00:00
if ' ' in nickname or '@' in nickname or \
'\n' in nickname or '\r' in nickname:
2020-02-27 20:23:27 +00:00
return None
2020-04-01 21:29:04 +00:00
domainFull = domain
2020-02-27 20:23:27 +00:00
if port:
2020-04-01 21:29:04 +00:00
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2020-02-27 20:23:27 +00:00
blogRSS2 = rss2Header(httpPrefix, nickname, domainFull, translate)
2020-02-27 20:23:27 +00:00
2020-04-01 21:29:04 +00:00
blogsIndex = baseDir + '/accounts/' + \
nickname + '@' + domain + '/tlblogs.index'
2020-02-27 20:23:27 +00:00
if not os.path.isfile(blogsIndex):
return blogRSS2 + rss2Footer()
2020-02-27 20:23:27 +00:00
2020-04-01 21:29:04 +00:00
timelineJson = createBlogsTimeline(session, baseDir,
nickname, domain, port,
httpPrefix,
noOfItems, False, False,
pageNumber)
2020-02-27 20:23:27 +00:00
if not timelineJson:
return blogRSS2 + rss2Footer()
2020-02-27 20:23:27 +00:00
2020-04-01 21:29:04 +00:00
if pageNumber is not None:
2020-02-27 20:23:27 +00:00
for item in timelineJson['orderedItems']:
2020-04-01 21:29:04 +00:00
if item['type'] != 'Create':
2020-02-27 20:23:27 +00:00
continue
blogRSS2 += \
htmlBlogPostRSS2(authorized, baseDir,
httpPrefix, translate,
nickname, domain,
domainFull, item,
None, True)
2020-02-27 20:23:27 +00:00
return blogRSS2 + rss2Footer()
2020-02-27 20:23:27 +00:00
2020-02-25 13:35:41 +00:00
2020-05-23 09:41:50 +00:00
def htmlBlogPageRSS3(authorized: bool, session,
baseDir: str, httpPrefix: str, translate: {},
nickname: str, domain: str, port: int,
noOfItems: int, pageNumber: int) -> str:
"""Returns an RSS version 3 feed containing posts
"""
if ' ' in nickname or '@' in nickname or \
'\n' in nickname or '\r' in nickname:
return None
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
blogRSS3 = ''
blogsIndex = baseDir + '/accounts/' + \
nickname + '@' + domain + '/tlblogs.index'
if not os.path.isfile(blogsIndex):
return blogRSS3
timelineJson = createBlogsTimeline(session, baseDir,
nickname, domain, port,
httpPrefix,
noOfItems, False, False,
pageNumber)
if not timelineJson:
return blogRSS3
if pageNumber is not None:
for item in timelineJson['orderedItems']:
if item['type'] != 'Create':
continue
blogRSS3 += \
htmlBlogPostRSS3(authorized, baseDir,
httpPrefix, translate,
nickname, domain,
domainFull, item,
None, True)
return blogRSS3
2020-02-25 13:35:41 +00:00
def getBlogIndexesForAccounts(baseDir: str) -> {}:
""" Get the index files for blogs for each account
and add them to a dict
"""
2020-04-01 21:29:04 +00:00
blogIndexes = {}
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2020-02-25 13:35:41 +00:00
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
continue
2020-04-01 21:29:04 +00:00
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
2020-02-25 13:35:41 +00:00
if os.path.isfile(blogsIndex):
2020-04-01 21:29:04 +00:00
blogIndexes[acct] = blogsIndex
2020-02-25 13:35:41 +00:00
return blogIndexes
2020-04-01 21:29:04 +00:00
2020-02-25 13:35:41 +00:00
def noOfBlogAccounts(baseDir: str) -> int:
"""Returns the number of blog accounts
"""
2020-04-01 21:29:04 +00:00
ctr = 0
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2020-02-25 13:35:41 +00:00
for acct in dirs:
if '@' not in acct:
continue
2020-02-25 13:47:04 +00:00
if 'inbox@' in acct:
2020-02-25 13:35:41 +00:00
continue
2020-04-01 21:29:04 +00:00
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
2020-02-25 13:35:41 +00:00
if os.path.isfile(blogsIndex):
2020-04-01 21:29:04 +00:00
ctr += 1
2020-02-25 13:35:41 +00:00
return ctr
2020-04-01 21:29:04 +00:00
2020-02-25 13:35:41 +00:00
def singleBlogAccountNickname(baseDir: str) -> str:
"""Returns the nickname of a single blog account
"""
2020-04-01 21:29:04 +00:00
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2020-02-25 13:35:41 +00:00
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
continue
2020-04-01 21:29:04 +00:00
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
2020-02-25 13:35:41 +00:00
if os.path.isfile(blogsIndex):
return acct.split('@')[0]
return None
2020-04-01 21:29:04 +00:00
def htmlBlogView(authorized: bool,
session, baseDir: str, httpPrefix: str,
translate: {}, domain: str, port: int,
2020-02-25 13:35:41 +00:00
noOfItems: int) -> str:
"""Show the blog main page
"""
2020-04-01 21:29:04 +00:00
blogStr = ''
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2020-02-25 13:35:41 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-01 21:29:04 +00:00
blogCSS = cssFile.read()
blogStr = htmlHeader(cssFilename, blogCSS)
2020-02-25 13:35:41 +00:00
if noOfBlogAccounts(baseDir) <= 1:
2020-04-01 21:29:04 +00:00
nickname = singleBlogAccountNickname(baseDir)
2020-02-25 13:35:41 +00:00
if nickname:
2020-04-01 21:29:04 +00:00
return htmlBlogPage(authorized, session,
baseDir, httpPrefix, translate,
nickname, domain, port,
noOfItems, 1)
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
domainFull = domain
2020-02-25 13:35:41 +00:00
if port:
2020-04-01 21:29:04 +00:00
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2020-02-25 13:35:41 +00:00
2020-04-01 21:29:04 +00:00
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2020-02-25 13:35:41 +00:00
for acct in dirs:
if '@' not in acct:
continue
if 'inbox@' in acct:
continue
2020-04-01 21:29:04 +00:00
accountDir = os.path.join(baseDir + '/accounts', acct)
blogsIndex = accountDir + '/tlblogs.index'
2020-02-25 13:35:41 +00:00
if os.path.isfile(blogsIndex):
2020-04-01 21:29:04 +00:00
blogStr += '<p class="blogaccount">'
blogStr += '<a href="' + \
httpPrefix + '://' + domainFull + '/blog/' + \
acct.split('@')[0] + '">' + acct + '</a>'
blogStr += '</p>'
return blogStr + htmlFooter()
2020-02-25 13:35:41 +00:00
return None
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
def htmlEditBlog(mediaInstance: bool, translate: {},
baseDir: str, httpPrefix: str,
path: str,
pageNumber: int,
nickname: str, domain: str,
2020-03-01 12:33:20 +00:00
postUrl: str) -> str:
2020-02-29 20:34:44 +00:00
"""Edit a blog post after it was created
"""
2020-04-01 21:29:04 +00:00
postFilename = locatePost(baseDir, nickname, domain, postUrl)
2020-03-01 12:33:20 +00:00
if not postFilename:
2020-04-01 21:29:04 +00:00
print('Edit blog: Filename not found for ' + postUrl)
2020-03-01 12:33:20 +00:00
return None
2020-04-01 21:29:04 +00:00
postJsonObject = loadJson(postFilename)
2020-03-01 12:33:20 +00:00
if not postJsonObject:
2020-04-01 21:29:04 +00:00
print('Edit blog: json not loaded for ' + postFilename)
2020-03-01 12:33:20 +00:00
return None
2020-04-01 21:29:04 +00:00
iconsDir = getIconsDir(baseDir)
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
editBlogText = '<p class="new-post-text">' + \
translate['Write your post text below.'] + '</p>'
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
if os.path.isfile(baseDir + '/accounts/newpost.txt'):
with open(baseDir + '/accounts/newpost.txt', 'r') as file:
editBlogText = '<p class="new-post-text">' + file.read() + '</p>'
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2020-02-29 20:34:44 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-01 21:29:04 +00:00
editBlogCSS = cssFile.read()
if httpPrefix != 'https':
editBlogCSS = editBlogCSS.replace('https://', httpPrefix+'://')
2020-02-29 20:34:44 +00:00
if '?' in path:
2020-04-01 21:29:04 +00:00
path = path.split('?')[0]
pathBase = path
editBlogImageSection = ' <div class="container">'
editBlogImageSection += ' <label class="labels">' + \
translate['Image description'] + '</label>'
editBlogImageSection += ' <input type="text" name="imageDescription">'
2020-04-05 09:33:23 +00:00
editBlogImageSection += \
' <input type="file" id="attachpic" name="attachpic"'
editBlogImageSection += \
' accept=".png, .jpg, .jpeg, .gif, .webp, .avif, ' + \
2020-04-05 09:33:23 +00:00
'.mp4, .webm, .ogv, .mp3, .ogg">'
2020-04-01 21:29:04 +00:00
editBlogImageSection += ' </div>'
placeholderMessage = translate['Write something'] + '...'
endpoint = 'editblogpost'
placeholderSubject = translate['Title']
scopeIcon = 'scope_blog.png'
scopeDescription = translate['Blog']
dateAndLocation = ''
dateAndLocation = '<div class="container">'
2020-04-05 09:33:23 +00:00
dateAndLocation += \
'<p><input type="checkbox" class="profilecheckbox" ' + \
'name="schedulePost"><label class="labels">' + \
2020-04-01 21:29:04 +00:00
translate['This is a scheduled post.'] + '</label></p>'
2020-04-05 09:33:23 +00:00
dateAndLocation += \
'<p><img loading="lazy" alt="" title="" ' + \
'class="emojicalendar" src="/' + \
2020-04-01 21:29:04 +00:00
iconsDir + '/calendar.png"/>'
2020-04-05 09:33:23 +00:00
dateAndLocation += \
'<label class="labels">' + translate['Date'] + ': </label>'
2020-04-01 21:29:04 +00:00
dateAndLocation += '<input type="date" name="eventDate">'
dateAndLocation += '<label class="labelsright">' + translate['Time'] + ':'
dateAndLocation += '<input type="time" name="eventTime"></label></p>'
dateAndLocation += '</div>'
dateAndLocation += '<div class="container">'
2020-04-05 09:33:23 +00:00
dateAndLocation += \
'<br><label class="labels">' + translate['Location'] + ': </label>'
2020-04-01 21:29:04 +00:00
dateAndLocation += '<input type="text" name="location">'
dateAndLocation += '</div>'
editBlogForm = htmlHeader(cssFilename, editBlogCSS)
2020-04-05 09:33:23 +00:00
editBlogForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + \
2020-04-01 21:29:04 +00:00
pathBase + '?' + endpoint + '?page=' + str(pageNumber) + '">'
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <input type="hidden" name="postUrl" value="' + postUrl + '">'
editBlogForm += \
' <input type="hidden" name="pageNumber" value="' + \
str(pageNumber) + '">'
2020-04-01 21:29:04 +00:00
editBlogForm += ' <div class="vertical-center">'
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <label for="nickname"><b>' + editBlogText + '</b></label>'
2020-04-01 21:29:04 +00:00
editBlogForm += ' <div class="container">'
editBlogForm += ' <div class="dropbtn">'
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <img loading="lazy" alt="" title="" src="/' + iconsDir + \
'/' + scopeIcon + '"/><b class="scope-desc">' + \
scopeDescription + '</b>'
2020-04-01 21:29:04 +00:00
editBlogForm += ' </div>'
editBlogForm += ' <a href="' + pathBase + \
'/searchemoji"><img loading="lazy" ' + \
'class="emojisearch" src="/emoji/1F601.png" title="' + \
translate['Search for emoji'] + '" alt="' + \
translate['Search for emoji'] + '"/></a>'
editBlogForm += ' </div>'
editBlogForm += ' <div class="container"><center>'
editBlogForm += ' <a href="' + pathBase + \
'/inbox"><button class="cancelbtn">' + \
translate['Cancel'] + '</button></a>'
editBlogForm += ' <input type="submit" name="submitPost" value="' + \
translate['Submit'] + '">'
editBlogForm += ' </center></div>'
2020-02-29 20:34:44 +00:00
if mediaInstance:
2020-04-01 21:29:04 +00:00
editBlogForm += editBlogImageSection
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <label class="labels">' + placeholderSubject + '</label><br>'
2020-04-01 21:29:04 +00:00
titleStr = ''
2020-03-01 12:33:20 +00:00
if postJsonObject['object'].get('summary'):
2020-04-01 21:29:04 +00:00
titleStr = postJsonObject['object']['summary']
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <input type="text" name="subject" value="' + titleStr + '">'
2020-04-01 21:29:04 +00:00
editBlogForm += ''
editBlogForm += ' <br><label class="labels">' + \
placeholderMessage + '</label>'
messageBoxHeight = 800
2020-04-05 09:33:23 +00:00
contentStr = postJsonObject['object']['content']
contentStr = contentStr.replace('<p>', '').replace('</p>', '\n')
2020-04-01 21:29:04 +00:00
2020-04-05 09:33:23 +00:00
editBlogForm += \
' <textarea id="message" name="message" style="height:' + \
2020-04-01 21:29:04 +00:00
str(messageBoxHeight) + 'px">' + contentStr + '</textarea>'
editBlogForm += dateAndLocation
2020-02-29 20:34:44 +00:00
if not mediaInstance:
2020-04-01 21:29:04 +00:00
editBlogForm += editBlogImageSection
editBlogForm += ' </div>'
editBlogForm += '</form>'
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
editBlogForm = editBlogForm.replace('<body>',
'<body onload="focusOnMessage()">')
2020-02-29 20:34:44 +00:00
2020-04-01 21:29:04 +00:00
editBlogForm += htmlFooter()
2020-02-29 20:34:44 +00:00
return editBlogForm