__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 '
\n' + \ markdownToHtml(helpText) + '\n' + \ '
\n' return '' def _htmlTimelineNewPost(manuallyApproveFollowers: bool, boxName: str, icons_as_buttons: bool, usersPath: str, translate: {}) -> str: """Returns html for the new post button """ newPostButtonStr = '' if boxName == 'dm': if not icons_as_buttons: newPostButtonStr += \ '| ' + translate['Create a new DM'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif (boxName == 'tlblogs' or boxName == 'tlnews' or boxName == 'tlfeatures'): if not icons_as_buttons: newPostButtonStr += \ '| ' + \
                translate['Create a new post'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif boxName == 'tlshares': if not icons_as_buttons: newPostButtonStr += \ '| ' + \
                translate['Create a new shared item'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' elif boxName == 'tlwanted': if not icons_as_buttons: newPostButtonStr += \ '| ' + \
                translate['Create a new wanted item'] + \
                '\n' else: newPostButtonStr += \ '' + \ '' else: if not manuallyApproveFollowers: if not icons_as_buttons: newPostButtonStr += \ '| ' + \
                    translate['Create a new post'] + \
                    '\n' else: newPostButtonStr += \ '' + \ '' else: if not icons_as_buttons: 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, text_mode_banner: 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(text_mode_banner, navLinks, navAccessKeys, None, usersPath, translate, followApprovals) def _htmlTimelineEnd(base_dir: str, nickname: str, domain_full: str, http_prefix: str, translate: {}, moderator: bool, editor: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, rss_icon_at_top: bool, publish_button_at_top: 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(base_dir, nickname, domain_full, http_prefix, translate, moderator, editor, newswire, positive_voting, False, None, True, show_publish_as_icon, rss_icon_at_top, publish_button_at_top, 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: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, timelineJson: {}, boxName: str, allow_deletion: bool, http_prefix: str, project_version: str, manuallyApproveFollowers: bool, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, moderator: bool, editor: bool, artist: bool, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, moderationActionStr: str, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the timeline as html """ enableTimingLog = False timelineStartTime = time.time() accountDir = acct_dir(base_dir, 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 OSError: print('EX: htmlTimeline unable to delete ' + dmFile) # 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 OSError: print('EX: htmlTimeline unable to delete ' + replyFile) # 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 OSError: print('EX: htmlTimeline unable to delete ' + newShareFile) # 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 OSError: print('EX: htmlTimeline unable to delete ' + newWantedFile) # 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 OSError: print('EX: htmlTimeline unable to delete ' + newReportFile) separatorStr = '' if boxName != 'tlmedia': separatorStr = htmlPostSeparator(base_dir, None) # the css filename cssFilename = base_dir + '/epicyon-profile.css' if os.path.isfile(base_dir + '/epicyon.css'): cssFilename = base_dir + '/epicyon.css' # filename of the banner shown at the top bannerFile, bannerFilename = \ getBannerFile(base_dir, nickname, domain, theme) _logTimelineTiming(enableTimingLog, timelineStartTime, boxName, '1') # is the user a moderator? if not moderator: moderator = isModerator(base_dir, nickname) # is the user a site editor? if not editor: editor = isEditor(base_dir, 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 = get_full_domain(domain, port) usersPath = '/users/' + nickname actor = http_prefix + '://' + fullDomain + usersPath showIndividualPostIcons = True # show an icon for new follow approvals followApprovals = '' followRequestsFilename = \ acct_dir(base_dir, 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(base_dir, 'instanceTitle') tlStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None) _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 icons_as_buttons: newPostButtonStr += '
' # what screen to go to when a new post is created newPostButtonStr += \ _htmlTimelineNewPost(manuallyApproveFollowers, boxName, icons_as_buttons, usersPath, translate) # keyboard navigation tlStr += \ _htmlTimelineKeyboard(moderator, text_mode_banner, usersPath, nickname, newCalendarEvent, newDM, newReply, newShare, newWanted, followApprovals, accessKeys, translate) # banner and row of buttons tlStr += \ '
\n' + \ '\n' tlStr += '\n' + \ '
\n' if 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) # start the timeline tlStr += \ '\n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' + \ ' \n' domain_full = get_full_domain(domain, port) # left column leftColumnStr = \ getLeftColumnContent(base_dir, nickname, domain_full, http_prefix, translate, editor, artist, False, None, rss_icon_at_top, True, False, theme, accessKeys, shared_items_federated_domains) tlStr += ' \n' # center column containing posts tlStr += '
' + \ leftColumnStr + ' \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 = \ '

' 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(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' + \ ' ' + \ '' + \
            translate['Page down'] + '\n' + \ '
\n' tlStr += _pageNumberButtons(usersPath, boxName, pageNumber) tlStr += textModeSeparator elif itemCtr == 0: tlStr += _getHelpForTimeline(base_dir, boxName) tlStr += \ _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) 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, 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 += '
' + \ _pageNumberButtons(actor, 'tl' + sharesFileType, pageNumber) timelineStr += \ '
\n' + \ ' ' + translate['Page up'] + '\n' + \ '
\n' separatorStr = htmlPostSeparator(base_dir, None) ctr = 0 isAdminAccount = False if adminActor and actor == adminActor: isAdminAccount = True isModeratorAccount = False if isModerator(base_dir, 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(base_dir, '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: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, allow_deletion: bool, http_prefix: str, project_version: str, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the shares timeline as html """ manuallyApproveFollowers = \ followerApprovalActive(base_dir, nickname, domain) artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, None, 'tlshares', allow_deletion, http_prefix, project_version, manuallyApproveFollowers, False, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlWanted(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, allow_deletion: bool, http_prefix: str, project_version: str, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the wanted timeline as html """ manuallyApproveFollowers = \ followerApprovalActive(base_dir, nickname, domain) artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, None, 'tlwanted', allow_deletion, http_prefix, project_version, manuallyApproveFollowers, False, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInbox(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the inbox as html """ manuallyApproveFollowers = \ followerApprovalActive(base_dir, nickname, domain) artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'inbox', allow_deletion, http_prefix, project_version, manuallyApproveFollowers, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlBookmarks(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, bookmarksJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the bookmarks as html """ manuallyApproveFollowers = \ followerApprovalActive(base_dir, nickname, domain) artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, bookmarksJson, 'tlbookmarks', allow_deletion, http_prefix, project_version, manuallyApproveFollowers, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxDMs(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the DM timeline as html """ artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'dm', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxReplies(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the replies timeline as html """ artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'tlreplies', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxMedia(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the media timeline as html """ artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'tlmedia', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the blogs timeline as html """ artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'tlblogs', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxFeatures(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the features timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'tlfeatures', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, False, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlInboxNews(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, moderator: bool, editor: bool, artist: bool, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the news timeline as html """ return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, inboxJson, 'tlnews', allow_deletion, http_prefix, project_version, False, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, moderator, editor, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled) def htmlOutbox(cssCache: {}, defaultTimeline: str, recentPostsCache: {}, max_recent_posts: int, translate: {}, pageNumber: int, itemsPerPage: int, session, base_dir: str, cached_webfingers: {}, person_cache: {}, nickname: str, domain: str, port: int, outboxJson: {}, allow_deletion: bool, http_prefix: str, project_version: str, minimal: bool, yt_replace_domain: str, twitter_replacement_domain: str, show_published_date_only: bool, newswire: {}, positive_voting: bool, show_publish_as_icon: bool, full_width_tl_button_header: bool, icons_as_buttons: bool, rss_icon_at_top: bool, publish_button_at_top: bool, authorized: bool, theme: str, peertube_instances: [], allow_local_network_access: bool, text_mode_banner: str, accessKeys: {}, system_language: str, max_like_count: int, shared_items_federated_domains: [], signing_priv_key_pem: str, cw_lists: {}, lists_enabled: str) -> str: """Show the Outbox as html """ manuallyApproveFollowers = \ followerApprovalActive(base_dir, nickname, domain) artist = isArtist(base_dir, nickname) return htmlTimeline(cssCache, defaultTimeline, recentPostsCache, max_recent_posts, translate, pageNumber, itemsPerPage, session, base_dir, cached_webfingers, person_cache, nickname, domain, port, outboxJson, 'outbox', allow_deletion, http_prefix, project_version, manuallyApproveFollowers, minimal, yt_replace_domain, twitter_replacement_domain, show_published_date_only, newswire, False, False, artist, positive_voting, show_publish_as_icon, full_width_tl_button_header, icons_as_buttons, rss_icon_at_top, publish_button_at_top, authorized, None, theme, peertube_instances, allow_local_network_access, text_mode_banner, accessKeys, system_language, max_like_count, shared_items_federated_domains, signing_priv_key_pem, cw_lists, lists_enabled)