__filename__ = "webapp_timeline.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
import os
from datetime import datetime
import time
from utils import removeIdEnding
from follow import followerApprovalActive
from person import isPersonSnoozed
from happening import todaysEventsCheck
from happening import thisWeeksEventsCheck
from webapp_utils import getIconsWebPath
from webapp_utils import htmlPostSeparator
from webapp_utils import getBannerFile
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import sharesTimelineJson
from webapp_post import preparePostFromHtmlCache
from webapp_post import individualPostAsHtml
from webapp_column_left import getLeftColumnContent
from webapp_column_right import getRightColumnContent
from posts import isModerator
from posts import isEditor
def htmlTimeline(cssCache: {}, defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int,
itemsPerPage: int, session, baseDir: str,
wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, timelineJson: {},
boxName: str, allowDeletion: bool,
httpPrefix: str, projectVersion: str,
manuallyApproveFollowers: bool,
minimal: bool,
YTReplacementDomain: str,
showPublishedDateOnly: bool,
newswire: {}, moderator: bool,
editor: bool,
positiveVoting: bool,
showPublishAsIcon: bool,
fullWidthTimelineButtonHeader: bool,
iconsAsButtons: bool,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool) -> str:
"""Show the timeline as html
"""
timelineStartTime = time.time()
accountDir = baseDir + '/accounts/' + nickname + '@' + domain
# should the calendar icon be highlighted?
newCalendarEvent = False
calendarImage = 'calendar.png'
calendarPath = '/calendar'
calendarFile = accountDir + '/.newCalendar'
if os.path.isfile(calendarFile):
newCalendarEvent = True
calendarImage = 'calendar_notify.png'
with open(calendarFile, 'r') as calfile:
calendarPath = calfile.read().replace('##sent##', '')
calendarPath = calendarPath.replace('\n', '').replace('\r', '')
# should the DM button be highlighted?
newDM = False
dmFile = accountDir + '/.newDM'
if os.path.isfile(dmFile):
newDM = True
if boxName == 'dm':
os.remove(dmFile)
# should the Replies button be highlighted?
newReply = False
replyFile = accountDir + '/.newReply'
if os.path.isfile(replyFile):
newReply = True
if boxName == 'tlreplies':
os.remove(replyFile)
# should the Shares button be highlighted?
newShare = False
newShareFile = accountDir + '/.newShare'
if os.path.isfile(newShareFile):
newShare = True
if boxName == 'tlshares':
os.remove(newShareFile)
# should the Moderation/reports button be highlighted?
newReport = False
newReportFile = accountDir + '/.newReport'
if os.path.isfile(newReportFile):
newReport = True
if boxName == 'moderation':
os.remove(newReportFile)
# directory where icons are found
# This changes depending upon theme
iconsPath = getIconsWebPath(baseDir)
separatorStr = ''
if boxName != 'tlmedia':
separatorStr = htmlPostSeparator(baseDir, None)
# the css filename
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
# filename of the banner shown at the top
bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain)
# benchmark 1
timeDiff = int((time.time() - timelineStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff))
# is the user a moderator?
if not moderator:
moderator = isModerator(baseDir, nickname)
# is the user a site editor?
if not editor:
editor = isEditor(baseDir, nickname)
# benchmark 2
timeDiff = int((time.time() - timelineStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff))
# the appearance of buttons - highlighted or not
inboxButton = 'button'
blogsButton = 'button'
newsButton = 'button'
dmButton = 'button'
if newDM:
dmButton = 'buttonhighlighted'
repliesButton = 'button'
if newReply:
repliesButton = 'buttonhighlighted'
mediaButton = 'button'
bookmarksButton = 'button'
eventsButton = 'button'
sentButton = 'button'
sharesButton = 'button'
if newShare:
sharesButton = 'buttonhighlighted'
moderationButton = 'button'
if newReport:
moderationButton = 'buttonhighlighted'
if boxName == 'inbox':
inboxButton = 'buttonselected'
elif boxName == 'tlblogs':
blogsButton = 'buttonselected'
elif boxName == 'tlnews':
newsButton = 'buttonselected'
elif boxName == 'dm':
dmButton = 'buttonselected'
if newDM:
dmButton = 'buttonselectedhighlighted'
elif boxName == 'tlreplies':
repliesButton = 'buttonselected'
if newReply:
repliesButton = 'buttonselectedhighlighted'
elif boxName == 'tlmedia':
mediaButton = 'buttonselected'
elif boxName == 'outbox':
sentButton = 'buttonselected'
elif boxName == 'moderation':
moderationButton = 'buttonselected'
if newReport:
moderationButton = 'buttonselectedhighlighted'
elif boxName == 'tlshares':
sharesButton = 'buttonselected'
if newShare:
sharesButton = 'buttonselectedhighlighted'
elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
bookmarksButton = 'buttonselected'
elif boxName == 'tlevents':
eventsButton = 'buttonselected'
# get the full domain, including any port number
fullDomain = domain
if port != 80 and port != 443:
if ':' not in domain:
fullDomain = domain + ':' + str(port)
usersPath = '/users/' + nickname
actor = httpPrefix + '://' + fullDomain + usersPath
showIndividualPostIcons = True
# show an icon for new follow approvals
followApprovals = ''
followRequestsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/followrequests.txt'
if os.path.isfile(followRequestsFilename):
with open(followRequestsFilename, 'r') as f:
for line in f:
if len(line) > 0:
# show follow approvals icon
followApprovals = \
'' + \
'\n'
break
# benchmark 3
timeDiff = int((time.time() - timelineStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff))
# moderation / reports button
moderationButtonStr = ''
if moderator and not minimal:
moderationButtonStr = \
''
# shares, bookmarks and events buttons
sharesButtonStr = ''
bookmarksButtonStr = ''
eventsButtonStr = ''
if not minimal:
sharesButtonStr = \
''
bookmarksButtonStr = \
''
eventsButtonStr = \
''
tlStr = htmlHeaderWithExternalStyle(cssFilename)
# benchmark 4
timeDiff = int((time.time() - timelineStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff))
# if this is a news instance and we are viewing the news timeline
newsHeader = False
if defaultTimeline == 'tlnews' and boxName == 'tlnews':
newsHeader = True
newPostButtonStr = ''
# start of headericons div
if not newsHeader:
if not iconsAsButtons:
newPostButtonStr += '
'
# what screen to go to when a new post is created
if boxName == 'dm':
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
elif boxName == 'tlblogs' or boxName == 'tlnews':
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
elif boxName == 'tlevents':
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
elif boxName == 'tlshares':
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
else:
if not manuallyApproveFollowers:
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
else:
if not iconsAsButtons:
newPostButtonStr += \
'\n'
else:
newPostButtonStr += \
'' + \
''
# This creates a link to the profile page when viewed
# in lynx, but should be invisible in a graphical web browser
tlStr += \
'\n'
# banner and row of buttons
tlStr += \
'\n'
tlStr += '\n'
if fullWidthTimelineButtonHeader:
tlStr += \
headerButtonsTimeline(defaultTimeline, boxName, pageNumber,
translate, usersPath, mediaButton,
blogsButton, newsButton, inboxButton,
dmButton, newDM, repliesButton,
newReply, minimal, sentButton,
sharesButtonStr, bookmarksButtonStr,
eventsButtonStr, moderationButtonStr,
newPostButtonStr, baseDir, nickname,
domain, iconsPath, timelineStartTime,
newCalendarEvent, calendarPath,
calendarImage, followApprovals,
iconsAsButtons)
# start the timeline
tlStr += '
\n'
tlStr += '
\n'
tlStr += '
\n'
tlStr += '
\n'
tlStr += '
\n'
tlStr += '
\n'
tlStr += ' \n'
tlStr += '
\n'
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
# left column
leftColumnStr = \
getLeftColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate, iconsPath,
editor, False, None, rssIconAtTop,
True, False)
tlStr += '
\n'
# show the posts
itemCtr = 0
if timelineJson:
# if this is the media timeline then add an extra gallery container
if boxName == 'tlmedia':
if pageNumber > 1:
tlStr += ' '
tlStr += '
\n'
# show each post in the timeline
for item in timelineJson['orderedItems']:
timelinePostStartTime = time.time()
if item['type'] == 'Create' or \
item['type'] == 'Announce' or \
item['type'] == 'Update':
# is the actor who sent this post snoozed?
if isPersonSnoozed(baseDir, nickname, domain, item['actor']):
continue
# is the post in the memory cache of recent ones?
currTlStr = None
if boxName != 'tlmedia' and \
recentPostsCache.get('index'):
postId = \
removeIdEnding(item['id']).replace('/', '#')
if postId in recentPostsCache['index']:
if not item.get('muted'):
if recentPostsCache['html'].get(postId):
currTlStr = recentPostsCache['html'][postId]
currTlStr = \
preparePostFromHtmlCache(currTlStr,
boxName,
pageNumber)
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE POST CACHE TIMING ' +
boxName + ' = ' + str(timeDiff))
if not currTlStr:
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE POST DISK TIMING START ' +
boxName + ' = ' + str(timeDiff))
# read the post from disk
currTlStr = \
individualPostAsHtml(False, recentPostsCache,
maxRecentPosts,
iconsPath, translate, pageNumber,
baseDir, session, wfRequest,
personCache,
nickname, domain, port,
item, None, True,
allowDeletion,
httpPrefix, projectVersion,
boxName,
YTReplacementDomain,
showPublishedDateOnly,
boxName != 'dm',
showIndividualPostIcons,
manuallyApproveFollowers,
False, True)
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE POST DISK TIMING ' +
boxName + ' = ' + str(timeDiff))
if currTlStr:
itemCtr += 1
if separatorStr:
tlStr += separatorStr
tlStr += currTlStr
if boxName == 'tlmedia':
tlStr += '
\n'
# page down arrow
if itemCtr > 2:
tlStr += \
'
\n'
return profileStr
def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
baseDir: str, actor: str,
nickname: str, domain: str, port: int,
maxSharesPerAccount: int, httpPrefix: str) -> str:
"""Show shared items timeline as html
"""
sharesJson, lastPage = \
sharesTimelineJson(actor, pageNumber, itemsPerPage,
baseDir, maxSharesPerAccount)
domainFull = domain
if port != 80 and port != 443:
if ':' not in domain:
domainFull = domain + ':' + str(port)
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
timelineStr = ''
if pageNumber > 1:
iconsPath = getIconsWebPath(baseDir)
timelineStr += \
'
\n' + \
' \n' + \
'
\n'
separatorStr = htmlPostSeparator(baseDir, None)
for published, item in sharesJson.items():
showContactButton = False
if item['actor'] != actor:
showContactButton = True
showRemoveButton = False
if item['actor'] == actor:
showRemoveButton = True
timelineStr += separatorStr + \
htmlIndividualShare(actor, item, translate,
showContactButton, showRemoveButton)
if not lastPage:
iconsPath = getIconsWebPath(baseDir)
timelineStr += \
'
\n' + \
' \n' + \
'
\n'
return timelineStr
def htmlHighlightLabel(label: str, highlight: bool) -> str:
"""If the give text should be highlighted then return
the appropriate markup.
This is so that in shell browsers, like lynx, it's possible
to see if the replies or DM button are highlighted.
"""
if not highlight:
return label
return '*' + str(label) + '*'
def headerButtonsTimeline(defaultTimeline: str,
boxName: str,
pageNumber: int,
translate: {},
usersPath: str,
mediaButton: str,
blogsButton: str,
newsButton: str,
inboxButton: str,
dmButton: str,
newDM: str,
repliesButton: str,
newReply: str,
minimal: bool,
sentButton: str,
sharesButtonStr: str,
bookmarksButtonStr: str,
eventsButtonStr: str,
moderationButtonStr: str,
newPostButtonStr: str,
baseDir: str,
nickname: str, domain: str,
iconsPath: str,
timelineStartTime,
newCalendarEvent: bool,
calendarPath: str,
calendarImage: str,
followApprovals: str,
iconsAsButtons: bool) -> str:
"""Returns the header at the top of the timeline, containing
buttons for inbox, outbox, search, calendar, etc
"""
# start of the button header with inbox, outbox, etc
tlStr = '
\n'
# first button
if defaultTimeline == 'tlmedia':
tlStr += \
''
elif defaultTimeline == 'tlblogs':
tlStr += \
''
elif defaultTimeline == 'tlnews':
tlStr += \
''
else:
tlStr += \
''
# if this is a news instance and we are viewing the news timeline
newsHeader = False
if defaultTimeline == 'tlnews' and boxName == 'tlnews':
newsHeader = True
if not newsHeader:
tlStr += \
''
tlStr += \
''
# typically the media button
if defaultTimeline != 'tlmedia':
if not minimal and not newsHeader:
tlStr += \
''
else:
if not minimal:
tlStr += \
''
isFeaturesTimeline = \
defaultTimeline == 'tlnews' and boxName == 'tlnews'
if not isFeaturesTimeline:
# typically the blogs button
# but may change if this is a blogging oriented instance
if defaultTimeline != 'tlblogs':
if not minimal and not isFeaturesTimeline:
titleStr = translate['Blogs']
if defaultTimeline == 'tlnews':
titleStr = translate['Article']
tlStr += \
''
else:
if not minimal:
tlStr += \
''
# typically the news button
# but may change if this is a news oriented instance
if defaultTimeline != 'tlnews':
tlStr += \
''
else:
if not newsHeader:
tlStr += \
''
# show todays events buttons on the first inbox page
happeningStr = ''
if boxName == 'inbox' and pageNumber == 1:
if todaysEventsCheck(baseDir, nickname, domain):
now = datetime.now()
# happening today button
if not iconsAsButtons:
happeningStr += \
'' + \
''
else:
happeningStr += \
'' + \
''
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
happeningStr += \
''
else:
happeningStr += \
''
else:
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
if not iconsAsButtons:
happeningStr += \
''
else:
happeningStr += \
''
if not newsHeader:
# button for the outbox
tlStr += \
''
# add other buttons
tlStr += \
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
moderationButtonStr + happeningStr + newPostButtonStr
if not newsHeader:
if not iconsAsButtons:
# the search icon
tlStr += \
''
else:
# the search button
tlStr += \
''
# benchmark 5
timeDiff = int((time.time() - timelineStartTime) * 1000)
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff))
# the calendar button
if not isFeaturesTimeline:
calendarAltText = translate['Calendar']
if newCalendarEvent:
# indicate that the calendar icon is highlighted
calendarAltText = '*' + calendarAltText + '*'
if not iconsAsButtons:
tlStr += \
' \n'
else:
tlStr += \
''
if not newsHeader:
# the show/hide button, for a simpler header appearance
if not iconsAsButtons:
tlStr += \
' \n'
else:
tlStr += \
''
if newsHeader:
tlStr += \
'' + \
''
# the newswire button to show right column links
if not iconsAsButtons:
tlStr += \
'' + \
''
else:
# NOTE: deliberately no \n at end of line
tlStr += \
''
# the links button to show left column links
if not iconsAsButtons:
tlStr += \
'' + \
''
else:
# NOTE: deliberately no \n at end of line
tlStr += \
''
if newsHeader:
tlStr += \
'' + \
''
if not iconsAsButtons:
# end of headericons div
tlStr += '
'
if not newsHeader:
tlStr += followApprovals
# end of the button header with inbox, outbox, etc
tlStr += '