__filename__ = "webapp_timeline.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Timeline" import os import time from shutil import copyfile from utils import isArtist from utils import dangerousMarkup from utils import getConfigParam from utils import get_full_domain from utils import isEditor from utils import removeIdEnding from utils import acct_dir from utils import isfloat from utils import local_actor_url from follow import followerApprovalActive from person import isPersonSnoozed from markdown import markdownToHtml from webapp_utils import htmlKeyboardNavigation from webapp_utils import htmlHideFromScreenReader 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_utils import htmlHighlightLabel from webapp_post import preparePostFromHtmlCache from webapp_post import individualPostAsHtml from webapp_column_left import getLeftColumnContent from webapp_column_right import getRightColumnContent from webapp_headerbuttons import headerButtonsTimeline from posts import isModerator from announce import isSelfAnnounce def _logTimelineTiming(enableTimingLog: bool, timelineStartTime, boxName: str, debugId: str) -> None: """Create a log of timings for performance tuning """ if not enableTimingLog: return timeDiff = int((time.time() - timelineStartTime) * 1000) if timeDiff > 100: print('TIMELINE TIMING ' + boxName + ' ' + debugId + ' = ' + str(timeDiff)) def _getHelpForTimeline(base_dir: str, boxName: str) -> str: """Shows help text for the given timeline """ # get the filename for help for this timeline helpFilename = base_dir + '/accounts/help_' + boxName + '.md' if not os.path.isfile(helpFilename): language = \ getConfigParam(base_dir, 'language') if not language: language = 'en' theme_name = \ getConfigParam(base_dir, 'theme') defaultFilename = None if theme_name: defaultFilename = \ base_dir + '/theme/' + theme_name + '/welcome/' + \ 'help_' + boxName + '_' + language + '.md' if not os.path.isfile(defaultFilename): defaultFilename = None if not defaultFilename: defaultFilename = \ base_dir + '/defaultwelcome/' + \ 'help_' + boxName + '_' + language + '.md' if not os.path.isfile(defaultFilename): defaultFilename = \ base_dir + '/defaultwelcome/help_' + boxName + '_en.md' if os.path.isfile(defaultFilename): copyfile(defaultFilename, helpFilename) # show help text if os.path.isfile(helpFilename): instanceTitle = \ getConfigParam(base_dir, 'instanceTitle') if not instanceTitle: instanceTitle = 'Epicyon' with open(helpFilename, 'r') as helpFile: helpText = helpFile.read() if dangerousMarkup(helpText, False): return '' helpText = helpText.replace('INSTANCE', instanceTitle) return '
' + \ leftColumnStr + ' | \n' # center column containing posts tlStr += '\n'
if not full_width_tl_button_header:
tlStr += \
headerButtonsTimeline(defaultTimeline, boxName, pageNumber,
translate, usersPath, mediaButton,
blogsButton, featuresButton,
newsButton, inboxButton,
dmButton, newDM, repliesButton,
newReply, minimal, sentButton,
sharesButtonStr, wantedButtonStr,
bookmarksButtonStr,
eventsButtonStr, moderationButtonStr,
newPostButtonStr, base_dir, nickname,
domain, timelineStartTime,
newCalendarEvent, calendarPath,
calendarImage, followApprovals,
icons_as_buttons, accessKeys)
tlStr += ' \n'
# second row of buttons for moderator actions
tlStr += \
_htmlTimelineModerationButtons(moderator, boxName, nickname,
moderationActionStr, translate)
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '6')
if boxName == 'tlshares':
maxSharesPerAccount = itemsPerPage
return (tlStr +
_htmlSharesTimeline(translate, pageNumber, itemsPerPage,
base_dir, actor, nickname, domain, port,
maxSharesPerAccount, http_prefix,
shared_items_federated_domains, 'shares') +
_htmlTimelineEnd(base_dir, nickname, domain_full,
http_prefix, translate,
moderator, editor,
newswire, positive_voting,
show_publish_as_icon,
rss_icon_at_top, publish_button_at_top,
authorized, theme,
defaultTimeline, accessKeys,
boxName,
enableTimingLog, timelineStartTime) +
htmlFooter())
elif boxName == 'tlwanted':
maxSharesPerAccount = itemsPerPage
return (tlStr +
_htmlSharesTimeline(translate, pageNumber, itemsPerPage,
base_dir, actor, nickname, domain, port,
maxSharesPerAccount, http_prefix,
shared_items_federated_domains, 'wanted') +
_htmlTimelineEnd(base_dir, nickname, domain_full,
http_prefix, translate,
moderator, editor,
newswire, positive_voting,
show_publish_as_icon,
rss_icon_at_top, publish_button_at_top,
authorized, theme,
defaultTimeline, accessKeys,
boxName,
enableTimingLog, timelineStartTime) +
htmlFooter())
_logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '7')
# separator between posts which only appears in shell browsers
# such as Lynx and is not read by screen readers
if boxName != 'tlmedia':
textModeSeparator = \
' ' + _pageNumberButtons(usersPath, boxName, pageNumber) tlStr += \ ' ' tlStr += ' \n'
# show each post in the timeline
for item in timelineJson['orderedItems']:
if item['type'] == 'Create' or \
item['type'] == 'Announce':
# is the actor who sent this post snoozed?
if isPersonSnoozed(base_dir, nickname, domain, item['actor']):
continue
if isSelfAnnounce(item):
continue
# is the post in the memory cache of recent ones?
currTlStr = None
if boxName != 'tlmedia' and recentPostsCache.get('html'):
postId = removeIdEnding(item['id']).replace('/', '#')
if recentPostsCache['html'].get(postId):
currTlStr = recentPostsCache['html'][postId]
currTlStr = \
preparePostFromHtmlCache(nickname,
currTlStr,
boxName,
pageNumber)
_logTimelineTiming(enableTimingLog,
timelineStartTime,
boxName, '10')
if not currTlStr:
_logTimelineTiming(enableTimingLog,
timelineStartTime,
boxName, '11')
# read the post from disk
currTlStr = \
individualPostAsHtml(signing_priv_key_pem,
False, recentPostsCache,
max_recent_posts,
translate, pageNumber,
base_dir, session,
cached_webfingers,
person_cache,
nickname, domain, port,
item, None, True,
allow_deletion,
http_prefix, project_version,
boxName,
yt_replace_domain,
twitter_replacement_domain,
show_published_date_only,
peertube_instances,
allow_local_network_access,
theme, system_language,
max_like_count,
boxName != 'dm',
showIndividualPostIcons,
manuallyApproveFollowers,
False, True, useCacheOnly,
cw_lists, lists_enabled)
_logTimelineTiming(enableTimingLog,
timelineStartTime, boxName, '12')
if currTlStr:
if currTlStr not in tlStr:
itemCtr += 1
tlStr += textModeSeparator + currTlStr
if separatorStr:
tlStr += separatorStr
if boxName == 'tlmedia':
tlStr += ' \n'
if itemCtr < 3:
print('Items added to html timeline ' + boxName + ': ' +
str(itemCtr) + ' ' + str(timelineJson['orderedItems']))
# page down arrow
if itemCtr > 0:
tlStr += textModeSeparator
tlStr += \
' \n' + \ ' \n'
profileStr += \
'\n'
profileStr += \
'\n\n'
profileStr += ' \n'
return profileStr
def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
base_dir: str, actor: str,
nickname: str, domain: str, port: int,
maxSharesPerAccount: int, http_prefix: str,
shared_items_federated_domains: [],
sharesFileType: str) -> str:
"""Show shared items timeline as html
"""
sharesJson, lastPage = \
sharesTimelineJson(actor, pageNumber, itemsPerPage,
base_dir, domain, nickname, maxSharesPerAccount,
shared_items_federated_domains, sharesFileType)
domain_full = get_full_domain(domain, port)
actor = local_actor_url(http_prefix, nickname, domain_full)
adminNickname = getConfigParam(base_dir, 'admin')
adminActor = ''
if adminNickname:
adminActor = \
local_actor_url(http_prefix, adminNickname, domain_full)
timelineStr = ''
if pageNumber > 1:
timelineStr += ' \n'
if sharedItem.get('imageUrl'):
profileStr += '' + sharedItem['summary'] + ' \n'
if sharedItem.get('itemQty'):
if sharedItem['itemQty'] > 1:
profileStr += \
'' + translate['Quantity'] + ': ' + \
str(sharedItem['itemQty']) + ' ' + \ '' + \ '\n' profileStr += \ '\n' if removeButton and domain in shareId: if sharesFileType == 'shares': profileStr += \ ' \n' else: profileStr += \ ' \n' profileStr += ' ' + \ _pageNumberButtons(actor, 'tl' + sharesFileType, pageNumber) timelineStr += \ ' |