__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 dangerousMarkup from utils import getConfigParam from utils import getFullDomain from utils import isEditor from utils import removeIdEnding from utils import acctDir from utils import isfloat from utils import localActorUrl 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(baseDir: str, boxName: str) -> str: """Shows help text for the given timeline """ # get the filename for help for this timeline helpFilename = baseDir + '/accounts/help_' + boxName + '.md' if not os.path.isfile(helpFilename): language = \ getConfigParam(baseDir, 'language') if not language: language = 'en' themeName = \ getConfigParam(baseDir, 'theme') defaultFilename = None if themeName: defaultFilename = \ baseDir + '/theme/' + themeName + '/welcome/' + \ 'help_' + boxName + '_' + language + '.md' if not os.path.isfile(defaultFilename): defaultFilename = None if not defaultFilename: defaultFilename = \ baseDir + '/defaultwelcome/' + \ 'help_' + boxName + '_' + language + '.md' if not os.path.isfile(defaultFilename): defaultFilename = \ baseDir + '/defaultwelcome/help_' + boxName + '_en.md' if os.path.isfile(defaultFilename): copyfile(defaultFilename, helpFilename) # show help text if os.path.isfile(helpFilename): instanceTitle = \ getConfigParam(baseDir, '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 '
\n' + \ markdownToHtml(helpText) + '\n' + \ '
\n' return '' def _htmlTimelineNewPost(manuallyApproveFollowers: bool, boxName: str, iconsAsButtons: bool, usersPath: str, translate: {}) -> str: """Returns html for the new post button """ newPostButtonStr = '' if boxName == 'dm': if not iconsAsButtons: newPostButtonStr += \ '| ' + translate['Create a new DM'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif (boxName == 'tlblogs' or boxName == 'tlnews' or boxName == 'tlfeatures'): if not iconsAsButtons: newPostButtonStr += \ '| ' + \
                translate['Create a new post'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif boxName == 'tlshares': if not iconsAsButtons: newPostButtonStr += \ '| ' + \
                translate['Create a new shared item'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif boxName == 'tlwanted': if not iconsAsButtons: newPostButtonStr += \ '| ' + \
                translate['Create a new wanted item'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' else: if not manuallyApproveFollowers: if not iconsAsButtons: newPostButtonStr += \ '| ' + \
                    translate['Create a new post'] + \
                    '\n' else: newPostButtonStr += \ '' + \ '' else: if not iconsAsButtons: newPostButtonStr += \ '| ' + translate['Create a new post'] + \
                    '\n' else: newPostButtonStr += \ '' + \ '' return newPostButtonStr def _htmlTimelineModerationButtons(moderator: bool, boxName: str, nickname: str, moderationActionStr: str, translate: {}) -> str: """Returns html for the moderation screen buttons """ tlStr = '' if moderator and boxName == 'moderation': tlStr += \ '
' tlStr += '
\n' idx = 'Nickname or URL. Block using *@domain or nickname@domain' tlStr += \ ' ' + translate[idx] + '
\n' if moderationActionStr: tlStr += '
\n' else: tlStr += '
\n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += \ ' \n' tlStr += '
\n
\n' return tlStr def _htmlTimelineKeyboard(moderator: bool, textModeBanner: str, usersPath: str, nickname: str, newCalendarEvent: bool, newDM: bool, newReply: bool, newShare: bool, newWanted: bool, followApprovals: bool, accessKeys: {}, translate: {}) -> str: """Returns html for timeline keyboard navigation """ calendarStr = translate['Calendar'] if newCalendarEvent: calendarStr = '' + calendarStr + '' dmStr = translate['DM'] if newDM: dmStr = '' + dmStr + '' repliesStr = translate['Replies'] if newReply: repliesStr = '' + repliesStr + '' sharesStr = translate['Shares'] if newShare: sharesStr = '' + sharesStr + '' wantedStr = translate['Wanted'] if newWanted: wantedStr = '' + wantedStr + '' menuProfile = \ htmlHideFromScreenReader('πŸ‘€') + ' ' + \ translate['Switch to profile view'] menuInbox = \ htmlHideFromScreenReader('πŸ“₯') + ' ' + translate['Inbox'] menuOutbox = \ htmlHideFromScreenReader('πŸ“€') + ' ' + translate['Sent'] menuSearch = \ htmlHideFromScreenReader('πŸ”') + ' ' + \ translate['Search and follow'] menuCalendar = \ htmlHideFromScreenReader('πŸ“…') + ' ' + calendarStr menuDM = \ htmlHideFromScreenReader('πŸ“©') + ' ' + dmStr menuReplies = \ htmlHideFromScreenReader('πŸ“¨') + ' ' + repliesStr menuBookmarks = \ htmlHideFromScreenReader('πŸ”–') + ' ' + translate['Bookmarks'] menuShares = \ htmlHideFromScreenReader('🀝') + ' ' + sharesStr menuWanted = \ htmlHideFromScreenReader('β›±') + ' ' + wantedStr menuBlogs = \ htmlHideFromScreenReader('πŸ“') + ' ' + translate['Blogs'] menuNewswire = \ htmlHideFromScreenReader('πŸ“°') + ' ' + translate['Newswire'] menuLinks = \ htmlHideFromScreenReader('πŸ”—') + ' ' + translate['Links'] menuNewPost = \ htmlHideFromScreenReader('βž•') + ' ' + translate['Create a new post'] menuModeration = \ htmlHideFromScreenReader('⚑️') + ' ' + translate['Mod'] navLinks = { menuProfile: '/users/' + nickname, menuInbox: usersPath + '/inbox#timelineposts', menuSearch: usersPath + '/search', menuNewPost: usersPath + '/newpost', menuCalendar: usersPath + '/calendar', menuDM: usersPath + '/dm#timelineposts', menuReplies: usersPath + '/tlreplies#timelineposts', menuOutbox: usersPath + '/outbox#timelineposts', menuBookmarks: usersPath + '/tlbookmarks#timelineposts', menuShares: usersPath + '/tlshares#timelineposts', menuWanted: usersPath + '/tlwanted#timelineposts', menuBlogs: usersPath + '/tlblogs#timelineposts', menuNewswire: usersPath + '/newswiremobile', menuLinks: usersPath + '/linksmobile' } navAccessKeys = {} for variableName, key in accessKeys.items(): if not locals().get(variableName): continue navAccessKeys[locals()[variableName]] = key if moderator: navLinks[menuModeration] = usersPath + '/moderation#modtimeline' return htmlKeyboardNavigation(textModeBanner, navLinks, navAccessKeys, None, usersPath, translate, followApprovals) def _htmlTimelineEnd(baseDir: str, nickname: str, domainFull: str, httpPrefix: str, translate: {}, moderator: bool, editor: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, defaultTimeline: str, accessKeys: {}, boxName: str, enableTimingLog: bool, timelineStartTime) -> str: """Ending of the timeline, containing the right column """ # end of timeline-posts tlStr = ' \n' # end of column-center tlStr += ' \n' # right column rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, httpPrefix, translate, moderator, editor, newswire, positiveVoting, False, None, True, showPublishAsIcon, rssIconAtTop, publishButtonAtTop, authorized, True, theme, defaultTimeline, accessKeys) tlStr += ' ' + \ rightColumnStr + ' \n' tlStr += ' \n' _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '9') tlStr += ' \n' tlStr += '\n' return tlStr def _pageNumberButtons(usersPath: str, boxName: str, pageNumber: int) -> str: """Shows selactable page numbers at the bottom of the screen """ pagesWidth = 3 minPageNumber = pageNumber - pagesWidth if minPageNumber < 1: minPageNumber = 1 maxPageNumber = minPageNumber + 1 + (pagesWidth * 2) numStr = '' for page in range(minPageNumber, maxPageNumber): if numStr: numStr += ' βΈ» ' pageStr = str(page) if page == pageNumber: pageStr = '' + str(page) + '' numStr += \ '' + pageStr + '' return '
' + numStr + '
' def htmlTimeline(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, timelineJson: {}, boxName: str, allowDeletion: bool, httpPrefix: str, projectVersion: str, manuallyApproveFollowers: bool, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, moderator: bool, editor: bool, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, moderationActionStr: str, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the timeline as html """ enableTimingLog = False timelineStartTime = time.time() accountDir = acctDir(baseDir, 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': try: os.remove(dmFile) except BaseException: print('EX: htmlTimeline unable to delete ' + dmFile) pass # should the Replies button be highlighted? newReply = False replyFile = accountDir + '/.newReply' if os.path.isfile(replyFile): newReply = True if boxName == 'tlreplies': try: os.remove(replyFile) except BaseException: print('EX: htmlTimeline unable to delete ' + replyFile) pass # should the Shares button be highlighted? newShare = False newShareFile = accountDir + '/.newShare' if os.path.isfile(newShareFile): newShare = True if boxName == 'tlshares': try: os.remove(newShareFile) except BaseException: print('EX: htmlTimeline unable to delete ' + newShareFile) pass # should the Wanted button be highlighted? newWanted = False newWantedFile = accountDir + '/.newWanted' if os.path.isfile(newWantedFile): newWanted = True if boxName == 'tlwanted': try: os.remove(newWantedFile) except BaseException: print('EX: htmlTimeline unable to delete ' + newWantedFile) pass # should the Moderation/reports button be highlighted? newReport = False newReportFile = accountDir + '/.newReport' if os.path.isfile(newReportFile): newReport = True if boxName == 'moderation': try: os.remove(newReportFile) except BaseException: print('EX: htmlTimeline unable to delete ' + newReportFile) pass 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, theme) _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1') # 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) _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '2') # the appearance of buttons - highlighted or not inboxButton = 'button' blogsButton = 'button' featuresButton = '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' wantedButton = 'button' if newWanted: wantedButton = 'buttonhighlighted' moderationButton = 'button' if newReport: moderationButton = 'buttonhighlighted' if boxName == 'inbox': inboxButton = 'buttonselected' elif boxName == 'tlblogs': blogsButton = 'buttonselected' elif boxName == 'tlfeatures': featuresButton = '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 == 'tlwanted': wantedButton = 'buttonselected' if newWanted: wantedButton = 'buttonselectedhighlighted' elif boxName == 'tlbookmarks' or boxName == 'bookmarks': bookmarksButton = 'buttonselected' # get the full domain, including any port number fullDomain = getFullDomain(domain, port) usersPath = '/users/' + nickname actor = httpPrefix + '://' + fullDomain + usersPath showIndividualPostIcons = True # show an icon for new follow approvals followApprovals = '' followRequestsFilename = \ acctDir(baseDir, 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 = \ '' + \ '' + \
                        translate['Approve follow requests'] + \
                        '\n' break _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '3') # moderation / reports button moderationButtonStr = '' if moderator and not minimal: moderationButtonStr = \ '' # shares, bookmarks and events buttons sharesButtonStr = '' wantedButtonStr = '' bookmarksButtonStr = '' eventsButtonStr = '' if not minimal: sharesButtonStr = \ '' wantedButtonStr = \ '' bookmarksButtonStr = \ '' instanceTitle = \ getConfigParam(baseDir, 'instanceTitle') tlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '4') # if this is a news instance and we are viewing the news timeline newsHeader = False if defaultTimeline == 'tlfeatures' and boxName == 'tlfeatures': 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 newPostButtonStr += \ _htmlTimelineNewPost(manuallyApproveFollowers, boxName, iconsAsButtons, usersPath, translate) # keyboard navigation tlStr += \ _htmlTimelineKeyboard(moderator, textModeBanner, usersPath, nickname, newCalendarEvent, newDM, newReply, newShare, newWanted, followApprovals, accessKeys, translate) # banner and row of buttons tlStr += \ '
\n' + \ '\n' tlStr += '\n' + \ '
\n' if fullWidthTimelineButtonHeader: tlStr += \ headerButtonsTimeline(defaultTimeline, boxName, pageNumber, translate, usersPath, mediaButton, blogsButton, featuresButton, newsButton, inboxButton, dmButton, newDM, repliesButton, newReply, minimal, sentButton, sharesButtonStr, wantedButtonStr, bookmarksButtonStr, eventsButtonStr, moderationButtonStr, newPostButtonStr, baseDir, nickname, domain, timelineStartTime, newCalendarEvent, calendarPath, calendarImage, followApprovals, iconsAsButtons, accessKeys) # start the timeline tlStr += \ '\n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' domainFull = getFullDomain(domain, port) # left column leftColumnStr = \ getLeftColumnContent(baseDir, nickname, domainFull, httpPrefix, translate, editor, False, None, rssIconAtTop, True, False, theme, accessKeys, sharedItemsFederatedDomains) tlStr += ' \n' # center column containing posts tlStr += '
' + \ leftColumnStr + ' \n' if not fullWidthTimelineButtonHeader: tlStr += \ headerButtonsTimeline(defaultTimeline, boxName, pageNumber, translate, usersPath, mediaButton, blogsButton, featuresButton, newsButton, inboxButton, dmButton, newDM, repliesButton, newReply, minimal, sentButton, sharesButtonStr, wantedButtonStr, bookmarksButtonStr, eventsButtonStr, moderationButtonStr, newPostButtonStr, baseDir, nickname, domain, timelineStartTime, newCalendarEvent, calendarPath, calendarImage, followApprovals, iconsAsButtons, 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, baseDir, actor, nickname, domain, port, maxSharesPerAccount, httpPrefix, sharedItemsFederatedDomains, 'shares') + _htmlTimelineEnd(baseDir, nickname, domainFull, httpPrefix, translate, moderator, editor, newswire, positiveVoting, showPublishAsIcon, rssIconAtTop, publishButtonAtTop, authorized, theme, defaultTimeline, accessKeys, boxName, enableTimingLog, timelineStartTime) + htmlFooter()) elif boxName == 'tlwanted': maxSharesPerAccount = itemsPerPage return (tlStr + _htmlSharesTimeline(translate, pageNumber, itemsPerPage, baseDir, actor, nickname, domain, port, maxSharesPerAccount, httpPrefix, sharedItemsFederatedDomains, 'wanted') + _htmlTimelineEnd(baseDir, nickname, domainFull, httpPrefix, translate, moderator, editor, newswire, positiveVoting, showPublishAsIcon, rssIconAtTop, publishButtonAtTop, 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 = \ '

' else: textModeSeparator = '' # page up arrow if pageNumber > 1: tlStr += textModeSeparator tlStr += '
' + _pageNumberButtons(usersPath, boxName, pageNumber) tlStr += \ '
\n' + \ ' ' + \ '' + \
            translate['Page up'] + '\n' + \ '
\n' # show the posts itemCtr = 0 if timelineJson: if 'orderedItems' not in timelineJson: print('ERROR: no orderedItems in timeline for ' + boxName + ' ' + str(timelineJson)) return '' useCacheOnly = False if boxName == 'inbox': useCacheOnly = True 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']: if item['type'] == 'Create' or \ item['type'] == 'Announce': # is the actor who sent this post snoozed? if isPersonSnoozed(baseDir, 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(signingPrivateKeyPem, False, recentPostsCache, maxRecentPosts, translate, pageNumber, baseDir, session, cachedWebfingers, personCache, nickname, domain, port, item, None, True, allowDeletion, httpPrefix, projectVersion, boxName, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, peertubeInstances, allowLocalNetworkAccess, theme, systemLanguage, maxLikeCount, boxName != 'dm', showIndividualPostIcons, manuallyApproveFollowers, False, True, useCacheOnly, CWlists, listsEnabled) _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' + \ ' ' + \ '' + \
            translate['Page down'] + '\n' + \ '
\n' tlStr += _pageNumberButtons(usersPath, boxName, pageNumber) tlStr += textModeSeparator elif itemCtr == 0: tlStr += _getHelpForTimeline(baseDir, boxName) tlStr += \ _htmlTimelineEnd(baseDir, nickname, domainFull, httpPrefix, translate, moderator, editor, newswire, positiveVoting, showPublishAsIcon, rssIconAtTop, publishButtonAtTop, authorized, theme, defaultTimeline, accessKeys, boxName, enableTimingLog, timelineStartTime) tlStr += htmlFooter() return tlStr def htmlIndividualShare(domain: str, shareId: str, actor: str, sharedItem: {}, translate: {}, showContact: bool, removeButton: bool, sharesFileType: str) -> str: """Returns an individual shared item as html """ profileStr = '
\n' profileStr += \ '\n' if sharedItem.get('imageUrl'): profileStr += '\n' profileStr += \ '' + translate['Item image'] + '\n\n' profileStr += '

' + sharedItem['summary'] + '

\n

' if sharedItem.get('itemQty'): if sharedItem['itemQty'] > 1: profileStr += \ '' + translate['Quantity'] + ': ' + \ str(sharedItem['itemQty']) + '
' profileStr += \ '' + translate['Type'] + ': ' + sharedItem['itemType'] + '
' profileStr += \ '' + translate['Category'] + ': ' + \ sharedItem['category'] + '
' if sharedItem.get('location'): profileStr += \ '' + translate['Location'] + ': ' + \ sharedItem['location'] + '
' contactTitleStr = translate['Contact'] if sharedItem.get('itemPrice') and sharedItem.get('itemCurrency'): if isfloat(sharedItem['itemPrice']): if float(sharedItem['itemPrice']) > 0: profileStr += ' ' + \ '' + translate['Price'] + ': ' + \ sharedItem['itemPrice'] + ' ' + sharedItem['itemCurrency'] contactTitleStr = translate['Buy'] profileStr += '

\n' sharedesc = sharedItem['displayName'] if '<' not in sharedesc and ';' not in sharedesc: if showContact: buttonStyleStr = 'button' if sharedItem['category'] == 'accommodation': contactTitleStr = translate['Request to stay'] buttonStyleStr = 'contactbutton' contactActor = sharedItem['actor'] profileStr += \ '

' + \ '' + \ '\n' profileStr += \ '\n' if removeButton and domain in shareId: if sharesFileType == 'shares': profileStr += \ ' \n' else: profileStr += \ ' \n' profileStr += '

\n' return profileStr def _htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int, baseDir: str, actor: str, nickname: str, domain: str, port: int, maxSharesPerAccount: int, httpPrefix: str, sharedItemsFederatedDomains: [], sharesFileType: str) -> str: """Show shared items timeline as html """ sharesJson, lastPage = \ sharesTimelineJson(actor, pageNumber, itemsPerPage, baseDir, domain, nickname, maxSharesPerAccount, sharedItemsFederatedDomains, sharesFileType) domainFull = getFullDomain(domain, port) actor = localActorUrl(httpPrefix, nickname, domainFull) adminNickname = getConfigParam(baseDir, 'admin') adminActor = '' if adminNickname: adminActor = \ localActorUrl(httpPrefix, adminNickname, domainFull) timelineStr = '' if pageNumber > 1: timelineStr += '
' + \ _pageNumberButtons(actor, 'tl' + sharesFileType, pageNumber) timelineStr += \ '
\n' + \ ' ' + translate['Page up'] + '\n' + \ '
\n' separatorStr = htmlPostSeparator(baseDir, None) ctr = 0 isAdminAccount = False if adminActor and actor == adminActor: isAdminAccount = True isModeratorAccount = False if isModerator(baseDir, nickname): isModeratorAccount = True for published, sharedItem in sharesJson.items(): showContactButton = False if sharedItem['actor'] != actor: showContactButton = True showRemoveButton = False if '___' + domain in sharedItem['shareId']: if sharedItem['actor'] == actor or \ isAdminAccount or isModeratorAccount: showRemoveButton = True timelineStr += \ htmlIndividualShare(domain, sharedItem['shareId'], actor, sharedItem, translate, showContactButton, showRemoveButton, sharesFileType) timelineStr += separatorStr ctr += 1 if ctr == 0: timelineStr += _getHelpForTimeline(baseDir, 'tl' + sharesFileType) if not lastPage: timelineStr += \ '
\n' + \ ' ' + translate['Page down'] + '\n' + \ '
\n' timelineStr += \ _pageNumberButtons(actor, 'tl' + sharesFileType, pageNumber) return timelineStr def htmlShares(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, allowDeletion: bool, httpPrefix: str, projectVersion: str, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the shares timeline as html """ manuallyApproveFollowers = \ followerApprovalActive(baseDir, nickname, domain) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, None, 'tlshares', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, False, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlWanted(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, allowDeletion: bool, httpPrefix: str, projectVersion: str, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the wanted timeline as html """ manuallyApproveFollowers = \ followerApprovalActive(baseDir, nickname, domain) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, None, 'tlwanted', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, False, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInbox(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the inbox as html """ manuallyApproveFollowers = \ followerApprovalActive(baseDir, nickname, domain) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'inbox', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlBookmarks(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, bookmarksJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the bookmarks as html """ manuallyApproveFollowers = \ followerApprovalActive(baseDir, nickname, domain) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, bookmarksJson, 'tlbookmarks', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxDMs(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the DM timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'dm', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxReplies(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the replies timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'tlreplies', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxMedia(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the media timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'tlmedia', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the blogs timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'tlblogs', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxFeatures(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the features timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'tlfeatures', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlInboxNews(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, moderator: bool, editor: bool, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the news timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, inboxJson, 'tlnews', allowDeletion, httpPrefix, projectVersion, False, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, moderator, editor, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled) def htmlOutbox(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, maxRecentPosts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, baseDir: str, cachedWebfingers: {}, personCache: {}, nickname: str, domain: str, port: int, outboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, minimal: bool, YTReplacementDomain: str, twitterReplacementDomain: str, showPublishedDateOnly: bool, newswire: {}, positiveVoting: bool, showPublishAsIcon: bool, fullWidthTimelineButtonHeader: bool, iconsAsButtons: bool, rssIconAtTop: bool, publishButtonAtTop: bool, authorized: bool, theme: str, peertubeInstances: [], allowLocalNetworkAccess: bool, textModeBanner: str, accessKeys: {}, systemLanguage: str, maxLikeCount: int, sharedItemsFederatedDomains: [], signingPrivateKeyPem: str, CWlists: {}, listsEnabled: str) -> str: """Show the Outbox as html """ manuallyApproveFollowers = \ followerApprovalActive(baseDir, nickname, domain) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, cachedWebfingers, personCache, nickname, domain, port, outboxJson, 'outbox', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, minimal, YTReplacementDomain, twitterReplacementDomain, showPublishedDateOnly, newswire, False, False, positiveVoting, showPublishAsIcon, fullWidthTimelineButtonHeader, iconsAsButtons, rssIconAtTop, publishButtonAtTop, authorized, None, theme, peertubeInstances, allowLocalNetworkAccess, textModeBanner, accessKeys, systemLanguage, maxLikeCount, sharedItemsFederatedDomains, signingPrivateKeyPem, CWlists, listsEnabled)