diff --git a/daemon.py b/daemon.py index ce2a28da..4da348bd 100644 --- a/daemon.py +++ b/daemon.py @@ -114,46 +114,46 @@ from blog import htmlBlogPage from blog import htmlBlogPost from blog import htmlEditBlog from webapp_utils import setBlogAddress +from webapp_utils import getBlogAddress from webapp_calendar import htmlCalendarDeleteConfirm from webapp_calendar import htmlCalendar -from webapp import htmlCitations from webapp import htmlFollowingList -from webapp import getBlogAddress from webapp import htmlDeletePost from webapp import htmlAbout from webapp import htmlRemoveSharedItem -from webapp import htmlInboxDMs -from webapp import htmlInboxReplies -from webapp import htmlInboxMedia -from webapp import htmlInboxBlogs -from webapp import htmlInboxNews from webapp import htmlUnblockConfirm -from webapp import htmlPersonOptions -from webapp import htmlIndividualPost -from webapp import htmlProfile -from webapp import htmlInbox -from webapp import htmlBookmarks -from webapp import htmlEvents -from webapp import htmlShares -from webapp import htmlOutbox -from webapp import htmlModeration -from webapp import htmlPostReplies +from webapp_person_options import htmlPersonOptions +from webapp_timeline import htmlShares +from webapp_timeline import htmlInbox +from webapp_timeline import htmlBookmarks +from webapp_timeline import htmlEvents +from webapp_timeline import htmlInboxDMs +from webapp_timeline import htmlInboxReplies +from webapp_timeline import htmlInboxMedia +from webapp_timeline import htmlInboxBlogs +from webapp_timeline import htmlInboxNews +from webapp_timeline import htmlOutbox +from webapp_timeline import htmlModeration from webapp import htmlLogin from webapp import htmlSuspended from webapp import htmlGetLoginCredentials from webapp import htmlNewPost from webapp import htmlFollowConfirm -from webapp import htmlNewswireMobile -from webapp import htmlLinksMobile from webapp import htmlUnfollowConfirm -from webapp import htmlProfileAfterSearch -from webapp import htmlEditProfile -from webapp import htmlEditLinks -from webapp import htmlEditNewswire from webapp import htmlEditNewsPost from webapp import htmlTermsOfService from webapp import htmlModerationInfo from webapp import htmlHashtagBlocked +from webapp_post import htmlPostReplies +from webapp_post import htmlIndividualPost +from webapp_profile import htmlEditProfile +from webapp_profile import htmlProfileAfterSearch +from webapp_profile import htmlProfile +from webapp_column_left import htmlLinksMobile +from webapp_column_left import htmlEditLinks +from webapp_column_right import htmlNewswireMobile +from webapp_column_right import htmlEditNewswire +from webapp_column_right import htmlCitations from webapp_search import htmlSkillsSearch from webapp_search import htmlHistorySearch from webapp_search import htmlHashtagSearch diff --git a/webapp.py b/webapp.py index 6675d576..01122e48 100644 --- a/webapp.py +++ b/webapp.py @@ -8,22 +8,8 @@ __status__ = "Production" import time import os -from datetime import datetime from shutil import copyfile -from pprint import pprint -from person import personBoxJson -from person import isPersonSnoozed -from pgp import getEmailAddress -from pgp import getPGPpubKey -from pgp import getPGPfingerprint -from xmpp import getXmppAddress -from ssb import getSSBAddress -from tox import getToxAddress -from matrix import getMatrixAddress -from donate import getDonationUrl from utils import getCSS -from utils import isSystemAccount -from utils import removeIdEnding from utils import getNicknameFromActor from utils import getDomainFromActor from utils import locatePost @@ -31,43 +17,14 @@ from utils import noOfAccounts from utils import isPublicPostFromUrl from utils import loadJson from utils import getConfigParam -from utils import votesOnNewswireItem -from utils import removeHtml -from follow import isFollowingActor -from follow import followerApprovalActive -from webfinger import webfingerHandle -from posts import getPersonBox -from posts import getUserUrl -from posts import parseUserFeed -from posts import populateRepliesJson -from posts import isModerator from posts import isEditor -from session import getJson -from blocking import isBlocked -from content import removeLongWords -from skills import getSkills from shares import getValidSharedItemID -from happening import todaysEventsCheck -from happening import thisWeeksEventsCheck -from theme import getThemesList -from petnames import getPetName -from followingCalendar import receivingCalendarEvents from webapp_utils import getAltPath -from webapp_utils import getBlogAddress -from webapp_utils import getPersonAvatarUrl from webapp_utils import getIconsDir -from webapp_utils import scheduledPostsExist -from webapp_utils import sharesTimelineJson -from webapp_utils import getImageFile from webapp_utils import getBannerFile -from webapp_utils import getLeftImageFile -from webapp_utils import getRightImageFile from webapp_utils import htmlHeader from webapp_utils import htmlFooter -from webapp_utils import addEmojiToDisplayName -from webapp_utils import htmlPostSeparator from webapp_post import individualPostAsHtml -from webapp_post import preparePostFromHtmlCache def htmlFollowingList(cssCache: {}, baseDir: str, @@ -202,210 +159,6 @@ def htmlModerationInfo(cssCache: {}, translate: {}, return infoForm -def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str, - domain: str, port: int, httpPrefix: str, - defaultTimeline: str) -> str: - """Shows the edit links screen - """ - if '/users/' not in path: - return '' - path = path.replace('/inbox', '').replace('/outbox', '') - path = path.replace('/shares', '') - - nickname = getNicknameFromActor(path) - if not nickname: - return '' - - # is the user a moderator? - if not isEditor(baseDir, nickname): - return '' - - cssFilename = baseDir + '/epicyon-links.css' - if os.path.isfile(baseDir + '/links.css'): - cssFilename = baseDir + '/links.css' - - editCSS = getCSS(baseDir, cssFilename, cssCache) - if editCSS: - if httpPrefix != 'https': - editCSS = \ - editCSS.replace('https://', httpPrefix + '://') - - # filename of the banner shown at the top - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - - editLinksForm = htmlHeader(cssFilename, editCSS) - - # top banner - editLinksForm += \ - '\n' - editLinksForm += '\n' - - editLinksForm += \ - '
\n' - editLinksForm += \ - '
\n' - editLinksForm += \ - '

' + translate['Edit Links'] + '

' - editLinksForm += \ - '
\n' - # editLinksForm += \ - # ' \n' - editLinksForm += \ - '
\n' + \ - ' \n' + \ - '
\n' - editLinksForm += \ - '
\n' - - linksFilename = baseDir + '/accounts/links.txt' - linksStr = '' - if os.path.isfile(linksFilename): - with open(linksFilename, 'r') as fp: - linksStr = fp.read() - - editLinksForm += \ - '
' - editLinksForm += \ - ' ' + \ - translate['One link per line. Description followed by the link.'] + \ - '
' - editLinksForm += \ - ' ' - editLinksForm += \ - '
' - - editLinksForm += htmlFooter() - return editLinksForm - - -def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, - domain: str, port: int, httpPrefix: str, - defaultTimeline: str) -> str: - """Shows the edit newswire screen - """ - if '/users/' not in path: - return '' - path = path.replace('/inbox', '').replace('/outbox', '') - path = path.replace('/shares', '') - - nickname = getNicknameFromActor(path) - if not nickname: - return '' - - # is the user a moderator? - if not isModerator(baseDir, nickname): - return '' - - cssFilename = baseDir + '/epicyon-links.css' - if os.path.isfile(baseDir + '/links.css'): - cssFilename = baseDir + '/links.css' - - editCSS = getCSS(baseDir, cssFilename, cssCache) - if editCSS: - if httpPrefix != 'https': - editCSS = \ - editCSS.replace('https://', httpPrefix + '://') - - # filename of the banner shown at the top - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - - editNewswireForm = htmlHeader(cssFilename, editCSS) - - # top banner - editNewswireForm += \ - '\n' - editNewswireForm += '\n' - - editNewswireForm += \ - '\n' - editNewswireForm += \ - '
\n' - editNewswireForm += \ - '

' + translate['Edit newswire'] + '

' - editNewswireForm += \ - '
\n' - # editNewswireForm += \ - # ' \n' - editNewswireForm += \ - '
\n' + \ - ' \n' + \ - '
\n' - editNewswireForm += \ - '
\n' - - newswireFilename = baseDir + '/accounts/newswire.txt' - newswireStr = '' - if os.path.isfile(newswireFilename): - with open(newswireFilename, 'r') as fp: - newswireStr = fp.read() - - editNewswireForm += \ - '
' - - editNewswireForm += \ - ' ' + \ - translate['Add RSS feed links below.'] + \ - '
' - editNewswireForm += \ - ' ' - - filterStr = '' - filterFilename = \ - baseDir + '/accounts/news@' + domain + '/filters.txt' - if os.path.isfile(filterFilename): - with open(filterFilename, 'r') as filterfile: - filterStr = filterfile.read() - - editNewswireForm += \ - '
\n' - editNewswireForm += '
' - editNewswireForm += ' \n' - - hashtagRulesStr = '' - hashtagRulesFilename = \ - baseDir + '/accounts/hashtagrules.txt' - if os.path.isfile(hashtagRulesFilename): - with open(hashtagRulesFilename, 'r') as rulesfile: - hashtagRulesStr = rulesfile.read() - - editNewswireForm += \ - '
\n' - editNewswireForm += '
\n' - editNewswireForm += \ - ' ' + translate['See instructions'] + '\n' - editNewswireForm += ' \n' - - editNewswireForm += \ - '
' - - editNewswireForm += htmlFooter() - return editNewswireForm - - def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, domain: str, port: int, httpPrefix: str, postUrl: str) -> str: @@ -484,625 +237,6 @@ def htmlEditNewsPost(cssCache: {}, translate: {}, baseDir: str, path: str, return editNewsPostForm -def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, - domain: str, port: int, httpPrefix: str, - defaultTimeline: str) -> str: - """Shows the edit profile screen - """ - imageFormats = '.png, .jpg, .jpeg, .gif, .webp, .avif' - path = path.replace('/inbox', '').replace('/outbox', '') - path = path.replace('/shares', '') - nickname = getNicknameFromActor(path) - if not nickname: - return '' - domainFull = domain - if port: - if port != 80 and port != 443: - if ':' not in domain: - domainFull = domain + ':' + str(port) - - actorFilename = \ - baseDir + '/accounts/' + nickname + '@' + domain + '.json' - if not os.path.isfile(actorFilename): - return '' - - # filename of the banner shown at the top - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - - isBot = '' - isGroup = '' - followDMs = '' - removeTwitter = '' - notifyLikes = '' - hideLikeButton = '' - mediaInstanceStr = '' - blogsInstanceStr = '' - newsInstanceStr = '' - displayNickname = nickname - bioStr = '' - donateUrl = '' - emailAddress = '' - PGPpubKey = '' - PGPfingerprint = '' - xmppAddress = '' - matrixAddress = '' - ssbAddress = '' - blogAddress = '' - toxAddress = '' - manuallyApprovesFollowers = '' - actorJson = loadJson(actorFilename) - if actorJson: - donateUrl = getDonationUrl(actorJson) - xmppAddress = getXmppAddress(actorJson) - matrixAddress = getMatrixAddress(actorJson) - ssbAddress = getSSBAddress(actorJson) - blogAddress = getBlogAddress(actorJson) - toxAddress = getToxAddress(actorJson) - emailAddress = getEmailAddress(actorJson) - PGPpubKey = getPGPpubKey(actorJson) - PGPfingerprint = getPGPfingerprint(actorJson) - if actorJson.get('name'): - displayNickname = actorJson['name'] - if actorJson.get('summary'): - bioStr = \ - actorJson['summary'].replace('

', '').replace('

', '') - if actorJson.get('manuallyApprovesFollowers'): - if actorJson['manuallyApprovesFollowers']: - manuallyApprovesFollowers = 'checked' - else: - manuallyApprovesFollowers = '' - if actorJson.get('type'): - if actorJson['type'] == 'Service': - isBot = 'checked' - isGroup = '' - elif actorJson['type'] == 'Group': - isGroup = 'checked' - isBot = '' - if os.path.isfile(baseDir + '/accounts/' + - nickname + '@' + domain + '/.followDMs'): - followDMs = 'checked' - if os.path.isfile(baseDir + '/accounts/' + - nickname + '@' + domain + '/.removeTwitter'): - removeTwitter = 'checked' - if os.path.isfile(baseDir + '/accounts/' + - nickname + '@' + domain + '/.notifyLikes'): - notifyLikes = 'checked' - if os.path.isfile(baseDir + '/accounts/' + - nickname + '@' + domain + '/.hideLikeButton'): - hideLikeButton = 'checked' - - mediaInstance = getConfigParam(baseDir, "mediaInstance") - if mediaInstance: - if mediaInstance is True: - mediaInstanceStr = 'checked' - blogsInstanceStr = '' - newsInstanceStr = '' - - newsInstance = getConfigParam(baseDir, "newsInstance") - if newsInstance: - if newsInstance is True: - newsInstanceStr = 'checked' - blogsInstanceStr = '' - mediaInstanceStr = '' - - blogsInstance = getConfigParam(baseDir, "blogsInstance") - if blogsInstance: - if blogsInstance is True: - blogsInstanceStr = 'checked' - mediaInstanceStr = '' - newsInstanceStr = '' - - filterStr = '' - filterFilename = \ - baseDir + '/accounts/' + nickname + '@' + domain + '/filters.txt' - if os.path.isfile(filterFilename): - with open(filterFilename, 'r') as filterfile: - filterStr = filterfile.read() - - switchStr = '' - switchFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/replacewords.txt' - if os.path.isfile(switchFilename): - with open(switchFilename, 'r') as switchfile: - switchStr = switchfile.read() - - autoTags = '' - autoTagsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/autotags.txt' - if os.path.isfile(autoTagsFilename): - with open(autoTagsFilename, 'r') as autoTagsFile: - autoTags = autoTagsFile.read() - - autoCW = '' - autoCWFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/autocw.txt' - if os.path.isfile(autoCWFilename): - with open(autoCWFilename, 'r') as autoCWFile: - autoCW = autoCWFile.read() - - blockedStr = '' - blockedFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/blocking.txt' - if os.path.isfile(blockedFilename): - with open(blockedFilename, 'r') as blockedfile: - blockedStr = blockedfile.read() - - allowedInstancesStr = '' - allowedInstancesFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/allowedinstances.txt' - if os.path.isfile(allowedInstancesFilename): - with open(allowedInstancesFilename, 'r') as allowedInstancesFile: - allowedInstancesStr = allowedInstancesFile.read() - - gitProjectsStr = '' - gitProjectsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/gitprojects.txt' - if os.path.isfile(gitProjectsFilename): - with open(gitProjectsFilename, 'r') as gitProjectsFile: - gitProjectsStr = gitProjectsFile.read() - - skills = getSkills(baseDir, nickname, domain) - skillsStr = '' - skillCtr = 1 - if skills: - for skillDesc, skillValue in skills.items(): - skillsStr += \ - '

' - skillsStr += \ - '

' - skillCtr += 1 - - skillsStr += \ - '

' - skillsStr += \ - '

' - - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - editProfileCSS = getCSS(baseDir, cssFilename, cssCache) - if editProfileCSS: - if httpPrefix != 'https': - editProfileCSS = \ - editProfileCSS.replace('https://', httpPrefix + '://') - - moderatorsStr = '' - themesDropdown = '' - instanceStr = '' - - adminNickname = getConfigParam(baseDir, 'admin') - if adminNickname: - if path.startswith('/users/' + adminNickname + '/'): - instanceDescription = \ - getConfigParam(baseDir, 'instanceDescription') - instanceDescriptionShort = \ - getConfigParam(baseDir, 'instanceDescriptionShort') - instanceTitle = \ - getConfigParam(baseDir, 'instanceTitle') - instanceStr += '
' - instanceStr += \ - ' ' - if instanceTitle: - instanceStr += \ - '
' - else: - instanceStr += \ - '
' - instanceStr += \ - ' ' - if instanceDescriptionShort: - instanceStr += \ - '
' - else: - instanceStr += \ - '
' - instanceStr += \ - ' ' - if instanceDescription: - instanceStr += \ - ' ' - else: - instanceStr += \ - ' ' - instanceStr += \ - ' ' - instanceStr += \ - ' ' - instanceStr += '
' - - moderators = '' - moderatorsFile = baseDir + '/accounts/moderators.txt' - if os.path.isfile(moderatorsFile): - with open(moderatorsFile, "r") as f: - moderators = f.read() - moderatorsStr = '
' - moderatorsStr += ' ' + translate['Moderators'] + '
' - moderatorsStr += ' ' + \ - translate['A list of moderator nicknames. One per line.'] - moderatorsStr += \ - ' ' - moderatorsStr += '
' - - editors = '' - editorsFile = baseDir + '/accounts/editors.txt' - if os.path.isfile(editorsFile): - with open(editorsFile, "r") as f: - editors = f.read() - editorsStr = '
' - editorsStr += ' ' + translate['Site Editors'] + '
' - editorsStr += ' ' + \ - translate['A list of editor nicknames. One per line.'] - editorsStr += \ - ' ' - editorsStr += '
' - - themes = getThemesList() - themesDropdown = '
' - themesDropdown += ' ' + translate['Theme'] + '
' - grayscaleFilename = \ - baseDir + '/accounts/.grayscale' - grayscale = '' - if os.path.isfile(grayscaleFilename): - grayscale = 'checked' - themesDropdown += \ - ' ' + translate['Grayscale'] + '
' - themesDropdown += '
' - if os.path.isfile(baseDir + '/fonts/custom.woff') or \ - os.path.isfile(baseDir + '/fonts/custom.woff2') or \ - os.path.isfile(baseDir + '/fonts/custom.otf') or \ - os.path.isfile(baseDir + '/fonts/custom.ttf'): - themesDropdown += \ - ' ' + \ - translate['Remove the custom font'] + '
' - themesDropdown += '
' - themeName = getConfigParam(baseDir, 'theme') - themesDropdown = \ - themesDropdown.replace('\n' - editProfileForm += htmlFooter() - return editProfileForm - - def htmlGetLoginCredentials(loginParams: str, lastLoginTime: int) -> (str, str, bool): """Receives login credentials via HTTPServer POST @@ -2068,2827 +1202,6 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {}, return newPostForm -def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int, - translate: {}, - baseDir: str, httpPrefix: str, - authorized: bool, - nickname: str, domain: str, port: int, - session, wfRequest: {}, personCache: {}, - projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool) -> str: - """Shows posts on the profile screen - These should only be public posts - """ - iconsDir = getIconsDir(baseDir) - separatorStr = htmlPostSeparator(baseDir, None) - profileStr = '' - maxItems = 4 - ctr = 0 - currPage = 1 - while ctr < maxItems and currPage < 4: - outboxFeed = \ - personBoxJson({}, session, baseDir, domain, - port, - '/users/' + nickname + '/outbox?page=' + - str(currPage), - httpPrefix, - 10, 'outbox', - authorized, 0, False, 0) - if not outboxFeed: - break - if len(outboxFeed['orderedItems']) == 0: - break - for item in outboxFeed['orderedItems']: - if item['type'] == 'Create': - postStr = \ - individualPostAsHtml(True, recentPostsCache, - maxRecentPosts, - iconsDir, translate, None, - baseDir, session, wfRequest, - personCache, - nickname, domain, port, item, - None, True, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, False, False, True, False) - if postStr: - profileStr += separatorStr + postStr - ctr += 1 - if ctr >= maxItems: - break - currPage += 1 - return profileStr - - -def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, - authorized: bool, - nickname: str, domain: str, port: int, - session, wfRequest: {}, personCache: {}, - followingJson: {}, projectVersion: str, - buttons: [], - feedName: str, actor: str, - pageNumber: int, - maxItemsPerPage: int) -> str: - """Shows following on the profile screen - """ - profileStr = '' - - iconsDir = getIconsDir(baseDir) - if authorized and pageNumber: - if authorized and pageNumber > 1: - # page up arrow - profileStr += \ - '
\n' + \ - ' ' + \
-                translate['Page up'] + '\n' + \ - '
\n' - - for item in followingJson['orderedItems']: - profileStr += \ - individualFollowAsHtml(translate, baseDir, session, - wfRequest, personCache, - domain, item, authorized, nickname, - httpPrefix, projectVersion, - buttons) - if authorized and maxItemsPerPage and pageNumber: - if len(followingJson['orderedItems']) >= maxItemsPerPage: - # page down arrow - profileStr += \ - '
\n' + \ - ' ' + \
-                translate['Page down'] + '\n' + \ - '
\n' - return profileStr - - -def htmlProfileRoles(translate: {}, nickname: str, domain: str, - rolesJson: {}) -> str: - """Shows roles on the profile screen - """ - profileStr = '' - for project, rolesList in rolesJson.items(): - profileStr += \ - '
\n

' + project + \ - '

\n
\n' - for role in rolesList: - profileStr += '

' + role + '

\n' - profileStr += '
\n' - if len(profileStr) == 0: - profileStr += \ - '

@' + nickname + '@' + domain + ' has no roles assigned

\n' - else: - profileStr = '
' + profileStr + '
\n' - return profileStr - - -def htmlProfileSkills(translate: {}, nickname: str, domain: str, - skillsJson: {}) -> str: - """Shows skills on the profile screen - """ - profileStr = '' - for skill, level in skillsJson.items(): - profileStr += \ - '
' + skill + \ - '
\n
\n' - if len(profileStr) > 0: - profileStr = '
' + \ - profileStr + '
\n' - return profileStr - - -def htmlIndividualShare(actor: str, item: {}, translate: {}, - showContact: bool, removeButton: bool) -> str: - """Returns an individual shared item as html - """ - profileStr = '
\n' - profileStr += '\n' - if item.get('imageUrl'): - profileStr += '\n' - profileStr += \ - '' + translate['Item image'] + '\n\n' - profileStr += '

' + item['summary'] + '

\n' - profileStr += \ - '

' + translate['Type'] + ': ' + item['itemType'] + ' ' - profileStr += \ - '' + translate['Category'] + ': ' + item['category'] + ' ' - profileStr += \ - '' + translate['Location'] + ': ' + item['location'] + '

\n' - if showContact: - contactActor = item['actor'] - profileStr += \ - '

\n' - if removeButton: - profileStr += \ - ' \n' - profileStr += '

\n' - return profileStr - - -def htmlProfileShares(actor: str, translate: {}, - nickname: str, domain: str, sharesJson: {}) -> str: - """Shows shares on the profile screen - """ - profileStr = '' - for item in sharesJson['orderedItems']: - profileStr += htmlIndividualShare(actor, item, translate, False, False) - if len(profileStr) > 0: - 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) -> str: - """Show shared items timeline as html - """ - sharesJson, lastPage = \ - sharesTimelineJson(actor, pageNumber, itemsPerPage, - baseDir, maxSharesPerAccount) - domainFull = domain - if port != 80 and port != 443: - if ':' not in domain: - domainFull = domain + ':' + str(port) - actor = httpPrefix + '://' + domainFull + '/users/' + nickname - timelineStr = '' - - if pageNumber > 1: - iconsDir = getIconsDir(baseDir) - timelineStr += \ - '
\n' + \ - ' ' + translate['Page up'] + '\n' + \ - '
\n' - - separatorStr = htmlPostSeparator(baseDir, None) - for published, item in sharesJson.items(): - showContactButton = False - if item['actor'] != actor: - showContactButton = True - showRemoveButton = False - if item['actor'] == actor: - showRemoveButton = True - timelineStr += separatorStr + \ - htmlIndividualShare(actor, item, translate, - showContactButton, showRemoveButton) - - if not lastPage: - iconsDir = getIconsDir(baseDir) - timelineStr += \ - '
\n' + \ - ' ' + translate['Page down'] + '\n' + \ - '
\n' - - return timelineStr - - -def htmlProfile(rssIconAtTop: bool, - cssCache: {}, iconsAsButtons: bool, - defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, projectVersion: str, - baseDir: str, httpPrefix: str, authorized: bool, - profileJson: {}, selected: str, - session, wfRequest: {}, personCache: {}, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, extraJson=None, - pageNumber=None, maxItemsPerPage=None) -> str: - """Show the profile page as html - """ - nickname = profileJson['preferredUsername'] - if not nickname: - return "" - domain, port = getDomainFromActor(profileJson['id']) - if not domain: - return "" - displayName = \ - addEmojiToDisplayName(baseDir, httpPrefix, - nickname, domain, - profileJson['name'], True) - domainFull = domain - if port: - domainFull = domain + ':' + str(port) - profileDescription = \ - addEmojiToDisplayName(baseDir, httpPrefix, - nickname, domain, - profileJson['summary'], False) - postsButton = 'button' - followingButton = 'button' - followersButton = 'button' - rolesButton = 'button' - skillsButton = 'button' - sharesButton = 'button' - if selected == 'posts': - postsButton = 'buttonselected' - elif selected == 'following': - followingButton = 'buttonselected' - elif selected == 'followers': - followersButton = 'buttonselected' - elif selected == 'roles': - rolesButton = 'buttonselected' - elif selected == 'skills': - skillsButton = 'buttonselected' - elif selected == 'shares': - sharesButton = 'buttonselected' - loginButton = '' - - followApprovalsSection = '' - followApprovals = False - linkToTimelineStart = '' - linkToTimelineEnd = '' - editProfileStr = '' - logoutStr = '' - actor = profileJson['id'] - usersPath = '/users/' + actor.split('/users/')[1] - - donateSection = '' - donateUrl = getDonationUrl(profileJson) - PGPpubKey = getPGPpubKey(profileJson) - PGPfingerprint = getPGPfingerprint(profileJson) - emailAddress = getEmailAddress(profileJson) - xmppAddress = getXmppAddress(profileJson) - matrixAddress = getMatrixAddress(profileJson) - ssbAddress = getSSBAddress(profileJson) - toxAddress = getToxAddress(profileJson) - if donateUrl or xmppAddress or matrixAddress or \ - ssbAddress or toxAddress or PGPpubKey or \ - PGPfingerprint or emailAddress: - donateSection = '
\n' - donateSection += '
\n' - if donateUrl and not isSystemAccount(nickname): - donateSection += \ - '

\n' - if emailAddress: - donateSection += \ - '

' + translate['Email'] + ': ' + emailAddress + '

\n' - if xmppAddress: - donateSection += \ - '

' + translate['XMPP'] + ': '+xmppAddress + '

\n' - if matrixAddress: - donateSection += \ - '

' + translate['Matrix'] + ': ' + matrixAddress + '

\n' - if ssbAddress: - donateSection += \ - '

SSB:

\n' - if toxAddress: - donateSection += \ - '

Tox:

\n' - if PGPfingerprint: - donateSection += \ - '

PGP: ' + \ - PGPfingerprint.replace('\n', '
') + '

\n' - if PGPpubKey: - donateSection += \ - '

' + PGPpubKey.replace('\n', '
') + '

\n' - donateSection += '
\n' - donateSection += '
\n' - - iconsDir = getIconsDir(baseDir) - if not authorized: - loginButton = headerButtonsFrontScreen(translate, nickname, - 'features', authorized, - iconsAsButtons, iconsDir) - else: - editProfileStr = \ - '' + \ - '| ' + translate['Edit'] + '\n' - - logoutStr = \ - '' + \ - '| ' + translate['Logout'] + \
-            '\n' - - linkToTimelineStart = \ - '' - linkToTimelineStart += \ - '' - linkToTimelineEnd = '' - # are there any follow requests? - followRequestsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/followrequests.txt' - if os.path.isfile(followRequestsFilename): - with open(followRequestsFilename, 'r') as f: - for line in f: - if len(line) > 0: - followApprovals = True - followersButton = 'buttonhighlighted' - if selected == 'followers': - followersButton = 'buttonselectedhighlighted' - break - if selected == 'followers': - if followApprovals: - with open(followRequestsFilename, 'r') as f: - for followerHandle in f: - if len(line) > 0: - if '://' in followerHandle: - followerActor = followerHandle - else: - followerActor = \ - httpPrefix + '://' + \ - followerHandle.split('@')[1] + \ - '/users/' + followerHandle.split('@')[0] - basePath = '/users/' + nickname - followApprovalsSection += '
' - followApprovalsSection += \ - '' - followApprovalsSection += \ - '' + \ - followerHandle + '' - followApprovalsSection += \ - '' - followApprovalsSection += \ - '

' - followApprovalsSection += \ - '' - followApprovalsSection += \ - '' - followApprovalsSection += '
' - - profileDescriptionShort = profileDescription - if '\n' in profileDescription: - if len(profileDescription.split('\n')) > 2: - profileDescriptionShort = '' - else: - if '
' in profileDescription: - if len(profileDescription.split('
')) > 2: - profileDescriptionShort = '' - profileDescription = profileDescription.replace('
', '\n') - # keep the profile description short - if len(profileDescriptionShort) > 256: - profileDescriptionShort = '' - # remove formatting from profile description used on title - avatarDescription = '' - if profileJson.get('summary'): - avatarDescription = profileJson['summary'].replace('
', '\n') - avatarDescription = avatarDescription.replace('

', '') - avatarDescription = avatarDescription.replace('

', '') - - # If this is the news account then show a different banner - if isSystemAccount(nickname): - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - profileHeaderStr = \ - '\n' - if loginButton: - profileHeaderStr += '
' + loginButton + '
\n' - - profileHeaderStr += '\n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileHeaderStr += ' \n' - profileFooterStr += ' \n' - profileFooterStr += ' \n' - profileFooterStr += ' \n' - profileFooterStr += '
\n' - iconsDir = getIconsDir(baseDir) - profileHeaderStr += \ - getLeftColumnContent(baseDir, 'news', domainFull, - httpPrefix, translate, - iconsDir, False, - False, None, rssIconAtTop, True, - True) - profileHeaderStr += ' \n' - else: - profileHeaderStr = '
\n' - profileHeaderStr += '
\n' - profileHeaderStr += \ - ' ' + \
-            avatarDescription + '\n' - profileHeaderStr += '

' + displayName + '

\n' - iconsDir = getIconsDir(baseDir) - profileHeaderStr += \ - '

@' + nickname + '@' + domainFull + '
' - profileHeaderStr += \ - '' + \ - '

\n' - profileHeaderStr += '

' + profileDescriptionShort + '

\n' - profileHeaderStr += loginButton - profileHeaderStr += '
\n' - profileHeaderStr += '
\n' - - profileStr = \ - linkToTimelineStart + profileHeaderStr + \ - linkToTimelineEnd + donateSection - if not isSystemAccount(nickname): - profileStr += '
\n' - profileStr += '
' - profileStr += \ - ' ' - profileStr += \ - ' ' + \ - '' - profileStr += \ - ' ' + \ - '' - profileStr += \ - ' ' + \ - '' - profileStr += \ - ' ' + \ - '' - profileStr += \ - ' ' + \ - '' - profileStr += logoutStr + editProfileStr - profileStr += '
' - profileStr += '
' - - profileStr += followApprovalsSection - - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - profileStyle = \ - profileStyle.replace('image.png', - profileJson['image']['url']) - if isSystemAccount(nickname): - bannerFile, bannerFilename = \ - getBannerFile(baseDir, nickname, domain) - profileStyle = \ - profileStyle.replace('banner.png', - '/users/' + nickname + '/' + bannerFile) - - licenseStr = \ - '' + \ - '' + \
-            translate['Get the source code'] + '' - - if selected == 'posts': - profileStr += \ - htmlProfilePosts(recentPostsCache, maxRecentPosts, - translate, - baseDir, httpPrefix, authorized, - nickname, domain, port, - session, wfRequest, personCache, - projectVersion, - YTReplacementDomain, - showPublishedDateOnly) + licenseStr - if selected == 'following': - profileStr += \ - htmlProfileFollowing(translate, baseDir, httpPrefix, - authorized, nickname, - domain, port, session, - wfRequest, personCache, extraJson, - projectVersion, ["unfollow"], selected, - usersPath, pageNumber, maxItemsPerPage) - if selected == 'followers': - profileStr += \ - htmlProfileFollowing(translate, baseDir, httpPrefix, - authorized, nickname, - domain, port, session, - wfRequest, personCache, extraJson, - projectVersion, ["block"], - selected, usersPath, pageNumber, - maxItemsPerPage) - if selected == 'roles': - profileStr += \ - htmlProfileRoles(translate, nickname, domainFull, extraJson) - if selected == 'skills': - profileStr += \ - htmlProfileSkills(translate, nickname, domainFull, extraJson) - if selected == 'shares': - profileStr += \ - htmlProfileShares(actor, translate, - nickname, domainFull, - extraJson) + licenseStr - - # Footer which is only used for system accounts - profileFooterStr = '' - if isSystemAccount(nickname): - profileFooterStr = '
\n' - iconsDir = getIconsDir(baseDir) - profileFooterStr += \ - getRightColumnContent(baseDir, 'news', domainFull, - httpPrefix, translate, - iconsDir, False, False, - newswire, False, - False, None, False, False, - False, True, authorized, True) - profileFooterStr += '
\n' - - profileStr = \ - htmlHeader(cssFilename, profileStyle) + \ - profileStr + profileFooterStr + htmlFooter() - return profileStr - - -def individualFollowAsHtml(translate: {}, - baseDir: str, session, wfRequest: {}, - personCache: {}, domain: str, - followUrl: str, - authorized: bool, - actorNickname: str, - httpPrefix: str, - projectVersion: str, - buttons=[]) -> str: - """An individual follow entry on the profile screen - """ - nickname = getNicknameFromActor(followUrl) - domain, port = getDomainFromActor(followUrl) - titleStr = '@' + nickname + '@' + domain - avatarUrl = getPersonAvatarUrl(baseDir, followUrl, personCache, True) - if not avatarUrl: - avatarUrl = followUrl + '/avatar.png' - if domain not in followUrl: - (inboxUrl, pubKeyId, pubKey, - fromPersonId, sharedInbox, - avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest, - personCache, projectVersion, - httpPrefix, nickname, - domain, 'outbox') - if avatarUrl2: - avatarUrl = avatarUrl2 - if displayName: - titleStr = displayName + ' ' + titleStr - - buttonsStr = '' - if authorized: - for b in buttons: - if b == 'block': - buttonsStr += \ - '\n' - if b == 'unfollow': - buttonsStr += \ - '\n' - - resultStr = '
\n' - resultStr += \ - '\n' - resultStr += '

 ' - resultStr += titleStr + '' + buttonsStr + '

\n' - resultStr += '
\n' - return resultStr - - -def htmlHighlightLabel(label: str, highlight: bool) -> str: - """If the give text should be highlighted then return - the appropriate markup. - This is so that in shell browsers, like lynx, it's possible - to see if the replies or DM button are highlighted. - """ - if not highlight: - return label - return '*' + str(label) + '*' - - -def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str, - httpPrefix: str, translate: {}, - iconsDir: str, editor: bool, - showBackButton: bool, timelinePath: str, - rssIconAtTop: bool, showHeaderImage: bool, - frontPage: bool) -> str: - """Returns html content for the left column - """ - htmlStr = '' - - separatorStr = htmlPostSeparator(baseDir, 'left') - domain = domainFull - if ':' in domain: - domain = domain.split(':') - - editImageClass = '' - if showHeaderImage: - leftImageFile, leftColumnImageFilename = \ - getLeftImageFile(baseDir, nickname, domain) - if not os.path.isfile(leftColumnImageFilename): - theme = getConfigParam(baseDir, 'theme').lower() - if theme == 'default': - theme = '' - else: - theme = '_' + theme - themeLeftImageFile, themeLeftColumnImageFilename = \ - getImageFile(baseDir, 'left_col_image', baseDir + '/img', - nickname, domain) - if os.path.isfile(themeLeftColumnImageFilename): - leftColumnImageFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/' + themeLeftImageFile - copyfile(themeLeftColumnImageFilename, - leftColumnImageFilename) - leftImageFile = themeLeftImageFile - - # show the image at the top of the column - editImageClass = 'leftColEdit' - if os.path.isfile(leftColumnImageFilename): - editImageClass = 'leftColEditImage' - htmlStr += \ - '\n
\n' + \ - ' \n' + \ - '
\n' - - if showBackButton: - htmlStr += \ - '
' + \ - ' ' + \ - '\n' - - if (editor or rssIconAtTop) and not showHeaderImage: - htmlStr += '
' - - if editImageClass == 'leftColEdit': - htmlStr += '\n
\n' - - htmlStr += '
\n' - if editor: - # show the edit icon - htmlStr += \ - ' ' + \ - '' + \
-            translate['Edit Links'] + '\n' - - # RSS icon - if nickname != 'news': - # rss feed for this account - rssUrl = httpPrefix + '://' + domainFull + \ - '/blog/' + nickname + '/rss.xml' - else: - # rss feed for all accounts on the instance - rssUrl = httpPrefix + '://' + domainFull + '/blog/rss.xml' - if not frontPage: - rssTitle = translate['RSS feed for your blog'] - else: - rssTitle = translate['RSS feed for this site'] - rssIconStr = \ - ' ' + \ - '' + rssTitle + \
-        '\n' - if rssIconAtTop: - htmlStr += rssIconStr - htmlStr += '
\n' - - if editImageClass == 'leftColEdit': - htmlStr += '
\n' - - if (editor or rssIconAtTop) and not showHeaderImage: - htmlStr += '

' - - # if showHeaderImage: - # htmlStr += '
' - - linksFilename = baseDir + '/accounts/links.txt' - linksFileContainsEntries = False - if os.path.isfile(linksFilename): - linksList = None - with open(linksFilename, "r") as f: - linksList = f.readlines() - if linksList: - for lineStr in linksList: - if ' ' not in lineStr: - if '#' not in lineStr: - if '*' not in lineStr: - continue - lineStr = lineStr.strip() - words = lineStr.split(' ') - # get the link - linkStr = None - for word in words: - if word == '#': - continue - if word == '*': - continue - if '://' in word: - linkStr = word - break - if linkStr: - lineStr = lineStr.replace(linkStr, '').strip() - # avoid any dubious scripts being added - if '<' not in lineStr: - # remove trailing comma if present - if lineStr.endswith(','): - lineStr = lineStr[:len(lineStr)-1] - # add link to the returned html - htmlStr += \ - '

' + \ - lineStr + '

\n' - linksFileContainsEntries = True - else: - if lineStr.startswith('#') or lineStr.startswith('*'): - lineStr = lineStr[1:].strip() - htmlStr += separatorStr - htmlStr += \ - '

' + \ - lineStr + '

\n' - else: - htmlStr += \ - '

' + lineStr + '

\n' - linksFileContainsEntries = True - - if linksFileContainsEntries and not rssIconAtTop: - htmlStr += '
' + rssIconStr + '
' - return htmlStr - - -def votesIndicator(totalVotes: int, positiveVoting: bool) -> str: - """Returns an indicator of the number of votes on a newswire item - """ - if totalVotes <= 0: - return '' - totalVotesStr = ' ' - for v in range(totalVotes): - if positiveVoting: - totalVotesStr += '✓' - else: - totalVotesStr += '✗' - return totalVotesStr - - -def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool, - translate: {}, positiveVoting: bool, iconsDir: str) -> str: - """Converts a newswire dict into html - """ - separatorStr = htmlPostSeparator(baseDir, 'right') - htmlStr = '' - for dateStr, item in newswire.items(): - publishedDate = \ - datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") - dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") - - dateStrLink = dateStr.replace('T', ' ') - dateStrLink = dateStrLink.replace('Z', '') - moderatedItem = item[5] - htmlStr += separatorStr - if moderatedItem and 'vote:' + nickname in item[2]: - totalVotesStr = '' - totalVotes = 0 - if moderator: - totalVotes = votesOnNewswireItem(item[2]) - totalVotesStr = \ - votesIndicator(totalVotes, positiveVoting) - - title = removeLongWords(item[0], 16, []).replace('\n', '
') - htmlStr += '

' + \ - '' + \ - '' + title + \ - '' + totalVotesStr - if moderator: - htmlStr += \ - ' ' + dateShown + '' - htmlStr += '

\n' - else: - htmlStr += ' ' - htmlStr += dateShown + '

\n' - else: - totalVotesStr = '' - totalVotes = 0 - if moderator: - if moderatedItem: - totalVotes = votesOnNewswireItem(item[2]) - # show a number of ticks or crosses for how many - # votes for or against - totalVotesStr = \ - votesIndicator(totalVotes, positiveVoting) - - title = removeLongWords(item[0], 16, []).replace('\n', '
') - if moderator and moderatedItem: - htmlStr += '

' + \ - '' + \ - title + '' + totalVotesStr - htmlStr += ' ' + dateShown - htmlStr += '' - htmlStr += '' - htmlStr += '

\n' - else: - htmlStr += '

' + \ - '' + \ - title + '' + \ - totalVotesStr - htmlStr += ' ' - htmlStr += dateShown + '

\n' - return htmlStr - - -def htmlCitations(baseDir: str, nickname: str, domain: str, - httpPrefix: str, defaultTimeline: str, - translate: {}, newswire: {}, cssCache: {}, - blogTitle: str, blogContent: str, - blogImageFilename: str, - blogImageAttachmentMediaType: str, - blogImageDescription: str) -> str: - """Show the citations screen when creating a blog - """ - htmlStr = '' - - # create a list of dates for citations - # these can then be used to re-select checkboxes later - citationsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/.citations.txt' - citationsSelected = [] - if os.path.isfile(citationsFilename): - citationsSeparator = '#####' - with open(citationsFilename, "r") as f: - citations = f.readlines() - for line in citations: - if citationsSeparator not in line: - continue - sections = line.strip().split(citationsSeparator) - if len(sections) != 3: - continue - dateStr = sections[0] - citationsSelected.append(dateStr) - - # the css filename - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - # replace any https within the css with whatever prefix is needed - if httpPrefix != 'https': - profileStyle = \ - profileStyle.replace('https://', httpPrefix + '://') - - # iconsDir = getIconsDir(baseDir) - - htmlStr = htmlHeader(cssFilename, profileStyle) - - # top banner - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - htmlStr += \ - '\n' - htmlStr += '\n' - - htmlStr += \ - '
\n' - htmlStr += '
\n' - htmlStr += translate['Choose newswire items ' + - 'referenced in your article'] + '
' - if blogTitle is None: - blogTitle = '' - htmlStr += \ - ' \n' - if blogContent is None: - blogContent = '' - htmlStr += \ - ' \n' - # submit button - htmlStr += \ - ' \n' - htmlStr += '
\n' - - citationsSeparator = '#####' - - # list of newswire items - if newswire: - ctr = 0 - for dateStr, item in newswire.items(): - # should this checkbox be selected? - selectedStr = '' - if dateStr in citationsSelected: - selectedStr = ' checked' - - publishedDate = \ - datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") - dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") - - title = removeLongWords(item[0], 16, []).replace('\n', '
') - link = item[1] - - citationValue = \ - dateStr + citationsSeparator + \ - title + citationsSeparator + \ - link - htmlStr += \ - '' + \ - '' + title + ' ' - htmlStr += '' + \ - dateShown + '
\n' - ctr += 1 - - htmlStr += '
\n' - return htmlStr + htmlFooter() - - -def getRightColumnContent(baseDir: str, nickname: str, domainFull: str, - httpPrefix: str, translate: {}, - iconsDir: str, moderator: bool, editor: bool, - newswire: {}, positiveVoting: bool, - showBackButton: bool, timelinePath: str, - showPublishButton: bool, - showPublishAsIcon: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool, - showHeaderImage: bool) -> str: - """Returns html content for the right column - """ - htmlStr = '' - - domain = domainFull - if ':' in domain: - domain = domain.split(':') - - if authorized: - # only show the publish button if logged in, otherwise replace it with - # a login button - publishButtonStr = \ - ' ' + \ - '\n' - else: - # if not logged in then replace the publish button with - # a login button - publishButtonStr = \ - ' \n' - - # show publish button at the top if needed - if publishButtonAtTop: - htmlStr += '
' + publishButtonStr + '
' - - # show a column header image, eg. title of the theme or newswire banner - editImageClass = '' - if showHeaderImage: - rightImageFile, rightColumnImageFilename = \ - getRightImageFile(baseDir, nickname, domain) - if not os.path.isfile(rightColumnImageFilename): - theme = getConfigParam(baseDir, 'theme').lower() - if theme == 'default': - theme = '' - else: - theme = '_' + theme - themeRightImageFile, themeRightColumnImageFilename = \ - getImageFile(baseDir, 'right_col_image', baseDir + '/img', - nickname, domain) - if os.path.isfile(themeRightColumnImageFilename): - rightColumnImageFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/' + themeRightImageFile - copyfile(themeRightColumnImageFilename, - rightColumnImageFilename) - rightImageFile = themeRightImageFile - - # show the image at the top of the column - editImageClass = 'rightColEdit' - if os.path.isfile(rightColumnImageFilename): - editImageClass = 'rightColEditImage' - htmlStr += \ - '\n
\n' + \ - ' \n' + \ - '
\n' - - if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: - htmlStr += '
' - - if editImageClass == 'rightColEdit': - htmlStr += '\n
\n' - - # whether to show a back icon - # This is probably going to be osolete soon - if showBackButton: - htmlStr += \ - ' ' + \ - '\n' - - if showPublishButton and not publishButtonAtTop: - if not showPublishAsIcon: - htmlStr += publishButtonStr - - # show the edit icon - if editor: - if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'): - # show the edit icon highlighted - htmlStr += \ - ' ' + \ - '' + \
-                translate['Edit newswire'] + '\n' - else: - # show the edit icon - htmlStr += \ - ' ' + \ - '' + \
-                translate['Edit newswire'] + '\n' - - # show the RSS icon - rssIconStr = \ - ' ' + \ - '' + \
-        translate['Newswire RSS Feed'] + '\n' - if rssIconAtTop: - htmlStr += rssIconStr - - # show publish icon at top - if showPublishButton: - if showPublishAsIcon: - htmlStr += \ - ' ' + \ - '' + \
-                translate['Publish a news article'] + '\n' - - if editImageClass == 'rightColEdit': - htmlStr += '
\n' - else: - if showHeaderImage: - htmlStr += '
\n' - - if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: - htmlStr += '

' - - # show the newswire lines - newswireContentStr = \ - htmlNewswire(baseDir, newswire, nickname, moderator, translate, - positiveVoting, iconsDir) - htmlStr += newswireContentStr - - # show the rss icon at the bottom, typically on the right hand side - if newswireContentStr and not rssIconAtTop: - htmlStr += '
' + rssIconStr + '
' - return htmlStr - - -def htmlLinksMobile(cssCache: {}, baseDir: str, - nickname: str, domainFull: str, - httpPrefix: str, translate, - timelinePath: str, authorized: bool, - rssIconAtTop: bool, - iconsAsButtons: bool, - defaultTimeline: str) -> str: - """Show the left column links within mobile view - """ - htmlStr = '' - - # the css filename - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - # replace any https within the css with whatever prefix is needed - if httpPrefix != 'https': - profileStyle = \ - profileStyle.replace('https://', httpPrefix + '://') - - iconsDir = getIconsDir(baseDir) - - # is the user a site editor? - if nickname == 'news': - editor = False - else: - editor = isEditor(baseDir, nickname) - - domain = domainFull - if ':' in domain: - domain = domain.split(':')[0] - - htmlStr = htmlHeader(cssFilename, profileStyle) - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - htmlStr += \ - '' + \ - '\n' - - htmlStr += '
' + \ - headerButtonsFrontScreen(translate, nickname, - 'links', authorized, - iconsAsButtons, iconsDir) + '
' - htmlStr += \ - getLeftColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, - iconsDir, editor, - False, timelinePath, - rssIconAtTop, False, False) - htmlStr += '
\n' + htmlFooter() - return htmlStr - - -def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str, - domain: str, domainFull: str, - httpPrefix: str, translate: {}, - newswire: {}, - positiveVoting: bool, - timelinePath: str, - showPublishAsIcon: bool, - authorized: bool, - rssIconAtTop: bool, - iconsAsButtons: bool, - defaultTimeline: str) -> str: - """Shows the mobile version of the newswire right column - """ - htmlStr = '' - - # the css filename - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - # replace any https within the css with whatever prefix is needed - if httpPrefix != 'https': - profileStyle = \ - profileStyle.replace('https://', - httpPrefix + '://') - - iconsDir = getIconsDir(baseDir) - - if nickname == 'news': - editor = False - moderator = False - else: - # is the user a moderator? - moderator = isModerator(baseDir, nickname) - - # is the user a site editor? - editor = isEditor(baseDir, nickname) - - showPublishButton = editor - - htmlStr = htmlHeader(cssFilename, profileStyle) - - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - htmlStr += \ - '' + \ - '\n' - - htmlStr += '
' + \ - headerButtonsFrontScreen(translate, nickname, - 'newswire', authorized, - iconsAsButtons, iconsDir) + '
' - htmlStr += \ - getRightColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, - iconsDir, moderator, editor, - newswire, positiveVoting, - False, timelinePath, showPublishButton, - showPublishAsIcon, rssIconAtTop, False, - authorized, False) - htmlStr += htmlFooter() - return htmlStr - - -def headerButtonsFrontScreen(translate: {}, - nickname: str, boxName: str, - authorized: bool, - iconsAsButtons: bool, - iconsDir: bool) -> str: - """Returns the header buttons for the front page of a news instance - """ - headerStr = '' - if nickname == 'news': - buttonFeatures = 'buttonMobile' - buttonNewswire = 'buttonMobile' - buttonLinks = 'buttonMobile' - if boxName == 'features': - buttonFeatures = 'buttonselected' - elif boxName == 'newswire': - buttonNewswire = 'buttonselected' - elif boxName == 'links': - buttonLinks = 'buttonselected' - - headerStr += \ - ' ' + \ - '' - if not authorized: - headerStr += \ - ' ' + \ - '' - if iconsAsButtons: - headerStr += \ - ' ' + \ - '' - headerStr += \ - ' ' + \ - '' - else: - headerStr += \ - ' ' + \ - '| ' + translate['Newswire'] + '\n' - headerStr += \ - ' ' + \ - '| ' + translate['Links'] + '\n' - else: - if not authorized: - headerStr += \ - ' ' + \ - '' - - if headerStr: - headerStr = \ - '\n
\n' + \ - headerStr + \ - '
\n' - return headerStr - - -def headerButtonsTimeline(defaultTimeline: str, - boxName: str, - pageNumber: int, - translate: {}, - usersPath: str, - mediaButton: str, - blogsButton: str, - newsButton: str, - inboxButton: str, - dmButton: str, - newDM: str, - repliesButton: str, - newReply: str, - minimal: bool, - sentButton: str, - sharesButtonStr: str, - bookmarksButtonStr: str, - eventsButtonStr: str, - moderationButtonStr: str, - newPostButtonStr: str, - baseDir: str, - nickname: str, domain: str, - iconsDir: str, - timelineStartTime, - newCalendarEvent: bool, - calendarPath: str, - calendarImage: str, - followApprovals: str, - iconsAsButtons: bool) -> str: - """Returns the header at the top of the timeline, containing - buttons for inbox, outbox, search, calendar, etc - """ - # start of the button header with inbox, outbox, etc - tlStr = '
\n' - # first button - if defaultTimeline == 'tlmedia': - tlStr += \ - '' - elif defaultTimeline == 'tlblogs': - tlStr += \ - '' - elif defaultTimeline == 'tlnews': - tlStr += \ - '' - else: - tlStr += \ - '' - - # if this is a news instance and we are viewing the news timeline - newsHeader = False - if defaultTimeline == 'tlnews' and boxName == 'tlnews': - newsHeader = True - - if not newsHeader: - tlStr += \ - '' - - tlStr += \ - '' - - # typically the media button - if defaultTimeline != 'tlmedia': - if not minimal and not newsHeader: - tlStr += \ - '' - else: - if not minimal: - tlStr += \ - '' - - isFeaturesTimeline = \ - defaultTimeline == 'tlnews' and boxName == 'tlnews' - - if not isFeaturesTimeline: - # typically the blogs button - # but may change if this is a blogging oriented instance - if defaultTimeline != 'tlblogs': - if not minimal and not isFeaturesTimeline: - titleStr = translate['Blogs'] - if defaultTimeline == 'tlnews': - titleStr = translate['Article'] - tlStr += \ - '' - else: - if not minimal: - tlStr += \ - '' - - # typically the news button - # but may change if this is a news oriented instance - if defaultTimeline != 'tlnews': - tlStr += \ - '' - else: - if not newsHeader: - tlStr += \ - '' - - # show todays events buttons on the first inbox page - happeningStr = '' - if boxName == 'inbox' and pageNumber == 1: - if todaysEventsCheck(baseDir, nickname, domain): - now = datetime.now() - - # happening today button - if not iconsAsButtons: - happeningStr += \ - '' + \ - '' - else: - happeningStr += \ - '' + \ - '' - - # happening this week button - if thisWeeksEventsCheck(baseDir, nickname, domain): - if not iconsAsButtons: - happeningStr += \ - '' - else: - happeningStr += \ - '' - else: - # happening this week button - if thisWeeksEventsCheck(baseDir, nickname, domain): - if not iconsAsButtons: - happeningStr += \ - '' - else: - happeningStr += \ - '' - - if not newsHeader: - # button for the outbox - tlStr += \ - '' - - # add other buttons - tlStr += \ - sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \ - moderationButtonStr + happeningStr + newPostButtonStr - - if not newsHeader: - if not iconsAsButtons: - # the search icon - tlStr += \ - '| ' + \
-                translate['Search and follow'] + \
-                '' - else: - # the search button - tlStr += \ - '' - - # benchmark 5 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff)) - - # the calendar button - if not isFeaturesTimeline: - calendarAltText = translate['Calendar'] - if newCalendarEvent: - # indicate that the calendar icon is highlighted - calendarAltText = '*' + calendarAltText + '*' - if not iconsAsButtons: - tlStr += \ - ' | ' + calendarAltText + \
-                '\n' - else: - tlStr += \ - '' - - if not newsHeader: - # the show/hide button, for a simpler header appearance - if not iconsAsButtons: - tlStr += \ - ' | ' + translate['Show/Hide Buttons'] + \
-                '\n' - else: - tlStr += \ - '' - - if newsHeader: - tlStr += \ - '' + \ - '' - - # the newswire button to show right column links - if not iconsAsButtons: - tlStr += \ - '' + \ - '| ' + translate['News'] + \
-            '' - else: - # NOTE: deliberately no \n at end of line - tlStr += \ - '' - - # the links button to show left column links - if not iconsAsButtons: - tlStr += \ - '' + \ - '| ' + translate['Edit Links'] + \
-            '' - # end of headericons div - tlStr += '
' - else: - # NOTE: deliberately no \n at end of line - tlStr += \ - '' - - if newsHeader: - tlStr += \ - '' + \ - '' - - if not newsHeader: - tlStr += followApprovals - # end of the button header with inbox, outbox, etc - tlStr += '
\n' - return tlStr - - -def htmlTimeline(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, - itemsPerPage: int, session, baseDir: str, - wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, timelineJson: {}, - boxName: str, allowDeletion: bool, - httpPrefix: str, projectVersion: str, - manuallyApproveFollowers: bool, - minimal: bool, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, moderator: bool, - editor: bool, - positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the timeline as html - """ - timelineStartTime = time.time() - - accountDir = baseDir + '/accounts/' + nickname + '@' + domain - - # should the calendar icon be highlighted? - newCalendarEvent = False - calendarImage = 'calendar.png' - calendarPath = '/calendar' - calendarFile = accountDir + '/.newCalendar' - if os.path.isfile(calendarFile): - newCalendarEvent = True - calendarImage = 'calendar_notify.png' - with open(calendarFile, 'r') as calfile: - calendarPath = calfile.read().replace('##sent##', '') - calendarPath = calendarPath.replace('\n', '').replace('\r', '') - - # should the DM button be highlighted? - newDM = False - dmFile = accountDir + '/.newDM' - if os.path.isfile(dmFile): - newDM = True - if boxName == 'dm': - os.remove(dmFile) - - # should the Replies button be highlighted? - newReply = False - replyFile = accountDir + '/.newReply' - if os.path.isfile(replyFile): - newReply = True - if boxName == 'tlreplies': - os.remove(replyFile) - - # should the Shares button be highlighted? - newShare = False - newShareFile = accountDir + '/.newShare' - if os.path.isfile(newShareFile): - newShare = True - if boxName == 'tlshares': - os.remove(newShareFile) - - # should the Moderation/reports button be highlighted? - newReport = False - newReportFile = accountDir + '/.newReport' - if os.path.isfile(newReportFile): - newReport = True - if boxName == 'moderation': - os.remove(newReportFile) - - # directory where icons are found - # This changes depending upon theme - iconsDir = getIconsDir(baseDir) - - separatorStr = '' - if boxName != 'tlmedia': - separatorStr = htmlPostSeparator(baseDir, None) - - # the css filename - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - # filename of the banner shown at the top - bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) - - # benchmark 1 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff)) - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if not profileStyle: - print('ERROR: css file not found ' + cssFilename) - return None - - # replace any https within the css with whatever prefix is needed - if httpPrefix != 'https': - profileStyle = \ - profileStyle.replace('https://', - httpPrefix + '://') - - # is the user a moderator? - if not moderator: - moderator = isModerator(baseDir, nickname) - - # is the user a site editor? - if not editor: - editor = isEditor(baseDir, nickname) - - # benchmark 2 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff)) - - # the appearance of buttons - highlighted or not - inboxButton = 'button' - blogsButton = 'button' - newsButton = 'button' - dmButton = 'button' - if newDM: - dmButton = 'buttonhighlighted' - repliesButton = 'button' - if newReply: - repliesButton = 'buttonhighlighted' - mediaButton = 'button' - bookmarksButton = 'button' - eventsButton = 'button' - sentButton = 'button' - sharesButton = 'button' - if newShare: - sharesButton = 'buttonhighlighted' - moderationButton = 'button' - if newReport: - moderationButton = 'buttonhighlighted' - if boxName == 'inbox': - inboxButton = 'buttonselected' - elif boxName == 'tlblogs': - blogsButton = 'buttonselected' - elif boxName == 'tlnews': - newsButton = 'buttonselected' - elif boxName == 'dm': - dmButton = 'buttonselected' - if newDM: - dmButton = 'buttonselectedhighlighted' - elif boxName == 'tlreplies': - repliesButton = 'buttonselected' - if newReply: - repliesButton = 'buttonselectedhighlighted' - elif boxName == 'tlmedia': - mediaButton = 'buttonselected' - elif boxName == 'outbox': - sentButton = 'buttonselected' - elif boxName == 'moderation': - moderationButton = 'buttonselected' - if newReport: - moderationButton = 'buttonselectedhighlighted' - elif boxName == 'tlshares': - sharesButton = 'buttonselected' - if newShare: - sharesButton = 'buttonselectedhighlighted' - elif boxName == 'tlbookmarks' or boxName == 'bookmarks': - bookmarksButton = 'buttonselected' - elif boxName == 'tlevents': - eventsButton = 'buttonselected' - - # get the full domain, including any port number - fullDomain = domain - if port != 80 and port != 443: - if ':' not in domain: - fullDomain = domain + ':' + str(port) - - usersPath = '/users/' + nickname - actor = httpPrefix + '://' + fullDomain + usersPath - - showIndividualPostIcons = True - - # show an icon for new follow approvals - followApprovals = '' - followRequestsFilename = \ - baseDir + '/accounts/' + \ - nickname + '@' + domain + '/followrequests.txt' - if os.path.isfile(followRequestsFilename): - with open(followRequestsFilename, 'r') as f: - for line in f: - if len(line) > 0: - # show follow approvals icon - followApprovals = \ - '' + \ - '' + \
-                        translate['Approve follow requests'] + \
-                        '\n' - break - - # benchmark 3 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff)) - - # moderation / reports button - moderationButtonStr = '' - if moderator and not minimal: - moderationButtonStr = \ - '' - - # shares, bookmarks and events buttons - sharesButtonStr = '' - bookmarksButtonStr = '' - eventsButtonStr = '' - if not minimal: - sharesButtonStr = \ - '' - - bookmarksButtonStr = \ - '' - - eventsButtonStr = \ - '' - - tlStr = htmlHeader(cssFilename, profileStyle) - - # benchmark 4 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff)) - - # if this is a news instance and we are viewing the news timeline - newsHeader = False - if defaultTimeline == 'tlnews' and boxName == 'tlnews': - newsHeader = True - - newPostButtonStr = '' - # start of headericons div - if not newsHeader: - if not iconsAsButtons: - newPostButtonStr += '
' - - # what screen to go to when a new post is created - if boxName == 'dm': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + translate['Create a new DM'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlblogs' or boxName == 'tlnews': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new post'] + \
-                '\n' - else: - newPostButtonStr += \ - '' + \ - '' - elif boxName == 'tlevents': - if not iconsAsButtons: - newPostButtonStr += \ - '| ' + \
-                translate['Create a new event'] + \
-                '\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 += \ - '' + \ - '' - - # This creates a link to the profile page when viewed - # in lynx, but should be invisible in a graphical web browser - tlStr += \ - '\n' - - # banner and row of buttons - tlStr += \ - '\n' - tlStr += '\n' - - if fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, iconsDir, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) - - # start the timeline - tlStr += '\n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - tlStr += ' \n' - - domainFull = domain - if port: - if port != 80 and port != 443: - domainFull = domain + ':' + str(port) - - # left column - leftColumnStr = \ - getLeftColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, iconsDir, - editor, False, None, rssIconAtTop, - True, False) - tlStr += ' \n' - # center column containing posts - tlStr += ' \n' - - # right column - rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, - httpPrefix, translate, iconsDir, - moderator, editor, - newswire, positiveVoting, - False, None, True, - showPublishAsIcon, - rssIconAtTop, publishButtonAtTop, - authorized, True) - tlStr += ' \n' - tlStr += ' \n' - - # benchmark 9 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 9 = ' + str(timeDiff)) - - tlStr += ' \n' - tlStr += '
' + \ - leftColumnStr + ' \n' - - if not fullWidthTimelineButtonHeader: - tlStr += \ - headerButtonsTimeline(defaultTimeline, boxName, pageNumber, - translate, usersPath, mediaButton, - blogsButton, newsButton, inboxButton, - dmButton, newDM, repliesButton, - newReply, minimal, sentButton, - sharesButtonStr, bookmarksButtonStr, - eventsButtonStr, moderationButtonStr, - newPostButtonStr, baseDir, nickname, - domain, iconsDir, timelineStartTime, - newCalendarEvent, calendarPath, - calendarImage, followApprovals, - iconsAsButtons) - - # second row of buttons for moderator actions - if moderator and boxName == 'moderation': - tlStr += \ - '
' - tlStr += '
\n' - idx = 'Nickname or URL. Block using *@domain or nickname@domain' - tlStr += \ - ' ' + translate[idx] + '
\n' - tlStr += '
\n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += \ - ' \n' - tlStr += '
\n
\n' - - # benchmark 6 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 6 = ' + str(timeDiff)) - - if boxName == 'tlshares': - maxSharesPerAccount = itemsPerPage - return (tlStr + - htmlSharesTimeline(translate, pageNumber, itemsPerPage, - baseDir, actor, nickname, domain, port, - maxSharesPerAccount, httpPrefix) + - htmlFooter()) - - # benchmark 7 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff)) - - # benchmark 8 - timeDiff = int((time.time() - timelineStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE TIMING ' + boxName + ' 8 = ' + str(timeDiff)) - - # page up arrow - if pageNumber > 1: - tlStr += \ - '
\n' + \ - ' ' + \
-            translate['Page up'] + '\n' + \ - '
\n' - - # show the posts - itemCtr = 0 - if timelineJson: - # if this is the media timeline then add an extra gallery container - if boxName == 'tlmedia': - if pageNumber > 1: - tlStr += '
' - tlStr += '
\n' - - # show each post in the timeline - for item in timelineJson['orderedItems']: - timelinePostStartTime = time.time() - - if item['type'] == 'Create' or \ - item['type'] == 'Announce' or \ - item['type'] == 'Update': - # is the actor who sent this post snoozed? - if isPersonSnoozed(baseDir, nickname, domain, item['actor']): - continue - - # is the post in the memory cache of recent ones? - currTlStr = None - if boxName != 'tlmedia' and \ - recentPostsCache.get('index'): - postId = \ - removeIdEnding(item['id']).replace('/', '#') - if postId in recentPostsCache['index']: - if not item.get('muted'): - if recentPostsCache['html'].get(postId): - currTlStr = recentPostsCache['html'][postId] - currTlStr = \ - preparePostFromHtmlCache(currTlStr, - boxName, - pageNumber) - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST CACHE TIMING ' + - boxName + ' = ' + str(timeDiff)) - - if not currTlStr: - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST DISK TIMING START ' + - boxName + ' = ' + str(timeDiff)) - - # read the post from disk - currTlStr = \ - individualPostAsHtml(False, recentPostsCache, - maxRecentPosts, - iconsDir, translate, pageNumber, - baseDir, session, wfRequest, - personCache, - nickname, domain, port, - item, None, True, - allowDeletion, - httpPrefix, projectVersion, - boxName, - YTReplacementDomain, - showPublishedDateOnly, - boxName != 'dm', - showIndividualPostIcons, - manuallyApproveFollowers, - False, True) - # benchmark cache post - timeDiff = \ - int((time.time() - - timelinePostStartTime) * 1000) - if timeDiff > 100: - print('TIMELINE POST DISK TIMING ' + - boxName + ' = ' + str(timeDiff)) - - if currTlStr: - itemCtr += 1 - if separatorStr: - tlStr += separatorStr - tlStr += currTlStr - if boxName == 'tlmedia': - tlStr += '
\n' - - # page down arrow - if itemCtr > 2: - tlStr += \ - '
\n' + \ - ' ' + \
-            translate['Page down'] + '\n' + \ - '
\n' - - # end of column-center - tlStr += '
' + \ - rightColumnStr + '
\n' - tlStr += htmlFooter() - return tlStr - - -def htmlShares(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the shares timeline as html - """ - manuallyApproveFollowers = \ - followerApprovalActive(baseDir, nickname, domain) - - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, None, - 'tlshares', allowDeletion, - httpPrefix, projectVersion, manuallyApproveFollowers, - False, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInbox(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the inbox as html - """ - manuallyApproveFollowers = \ - followerApprovalActive(baseDir, nickname, domain) - - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, - 'inbox', allowDeletion, - httpPrefix, projectVersion, manuallyApproveFollowers, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlBookmarks(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, bookmarksJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the bookmarks as html - """ - manuallyApproveFollowers = \ - followerApprovalActive(baseDir, nickname, domain) - - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, bookmarksJson, - 'tlbookmarks', allowDeletion, - httpPrefix, projectVersion, manuallyApproveFollowers, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlEvents(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, bookmarksJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the events as html - """ - manuallyApproveFollowers = \ - followerApprovalActive(baseDir, nickname, domain) - - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, bookmarksJson, - 'tlevents', allowDeletion, - httpPrefix, projectVersion, manuallyApproveFollowers, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInboxDMs(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the DM timeline as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'dm', allowDeletion, - httpPrefix, projectVersion, False, minimal, - YTReplacementDomain, showPublishedDateOnly, - newswire, False, False, positiveVoting, - showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInboxReplies(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the replies timeline as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'tlreplies', - allowDeletion, httpPrefix, projectVersion, False, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInboxMedia(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the media timeline as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'tlmedia', - allowDeletion, httpPrefix, projectVersion, False, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the blogs timeline as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'tlblogs', - allowDeletion, httpPrefix, projectVersion, False, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, False, False, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlInboxNews(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, moderator: bool, editor: bool, - positiveVoting: bool, showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the news timeline as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'tlnews', - allowDeletion, httpPrefix, projectVersion, False, - minimal, YTReplacementDomain, - showPublishedDateOnly, - newswire, moderator, editor, - positiveVoting, showPublishAsIcon, - fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlModeration(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, inboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the moderation feed as html - """ - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, inboxJson, 'moderation', - allowDeletion, httpPrefix, projectVersion, True, False, - YTReplacementDomain, showPublishedDateOnly, - newswire, False, False, positiveVoting, - showPublishAsIcon, fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlOutbox(cssCache: {}, defaultTimeline: str, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, pageNumber: int, itemsPerPage: int, - session, baseDir: str, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, outboxJson: {}, - allowDeletion: bool, - httpPrefix: str, projectVersion: str, - minimal: bool, YTReplacementDomain: str, - showPublishedDateOnly: bool, - newswire: {}, positiveVoting: bool, - showPublishAsIcon: bool, - fullWidthTimelineButtonHeader: bool, - iconsAsButtons: bool, - rssIconAtTop: bool, - publishButtonAtTop: bool, - authorized: bool) -> str: - """Show the Outbox as html - """ - manuallyApproveFollowers = \ - followerApprovalActive(baseDir, nickname, domain) - return htmlTimeline(cssCache, defaultTimeline, - recentPostsCache, maxRecentPosts, - translate, pageNumber, - itemsPerPage, session, baseDir, wfRequest, personCache, - nickname, domain, port, outboxJson, 'outbox', - allowDeletion, httpPrefix, projectVersion, - manuallyApproveFollowers, minimal, - YTReplacementDomain, showPublishedDateOnly, - newswire, False, False, positiveVoting, - showPublishAsIcon, fullWidthTimelineButtonHeader, - iconsAsButtons, rssIconAtTop, publishButtonAtTop, - authorized) - - -def htmlIndividualPost(cssCache: {}, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, - baseDir: str, session, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, authorized: bool, - postJsonObject: {}, httpPrefix: str, - projectVersion: str, likedBy: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool) -> str: - """Show an individual post as html - """ - iconsDir = getIconsDir(baseDir) - postStr = '' - if likedBy: - likedByNickname = getNicknameFromActor(likedBy) - likedByDomain, likedByPort = getDomainFromActor(likedBy) - if likedByPort: - if likedByPort != 80 and likedByPort != 443: - likedByDomain += ':' + str(likedByPort) - likedByHandle = likedByNickname + '@' + likedByDomain - postStr += \ - '

' + translate['Liked by'] + \ - ' @' + \ - likedByHandle + '\n' - - domainFull = domain - if port: - if port != 80 and port != 443: - domainFull = domain + ':' + str(port) - actor = '/users/' + nickname - followStr = '

\n' - followStr += \ - ' \n' - followStr += \ - ' \n' - if not isFollowingActor(baseDir, nickname, domainFull, likedBy): - followStr += ' \n' - followStr += ' \n' - followStr += '
\n' - postStr += followStr + '

\n' - - postStr += \ - individualPostAsHtml(True, recentPostsCache, maxRecentPosts, - iconsDir, translate, None, - baseDir, session, wfRequest, personCache, - nickname, domain, port, postJsonObject, - None, True, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, authorized, False, False, False) - messageId = removeIdEnding(postJsonObject['id']) - - # show the previous posts - if isinstance(postJsonObject['object'], dict): - while postJsonObject['object'].get('inReplyTo'): - postFilename = \ - locatePost(baseDir, nickname, domain, - postJsonObject['object']['inReplyTo']) - if not postFilename: - break - postJsonObject = loadJson(postFilename) - if postJsonObject: - postStr = \ - individualPostAsHtml(True, recentPostsCache, - maxRecentPosts, - iconsDir, translate, None, - baseDir, session, wfRequest, - personCache, - nickname, domain, port, - postJsonObject, - None, True, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, authorized, - False, False, False) + postStr - - # show the following posts - postFilename = locatePost(baseDir, nickname, domain, messageId) - if postFilename: - # is there a replies file for this post? - repliesFilename = postFilename.replace('.json', '.replies') - if os.path.isfile(repliesFilename): - # get items from the replies file - repliesJson = { - 'orderedItems': [] - } - populateRepliesJson(baseDir, nickname, domain, - repliesFilename, authorized, repliesJson) - # add items to the html output - for item in repliesJson['orderedItems']: - postStr += \ - individualPostAsHtml(True, recentPostsCache, - maxRecentPosts, - iconsDir, translate, None, - baseDir, session, wfRequest, - personCache, - nickname, domain, port, item, - None, True, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, authorized, - False, False, False) - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - postsCSS = getCSS(baseDir, cssFilename, cssCache) - if postsCSS: - if httpPrefix != 'https': - postsCSS = postsCSS.replace('https://', - httpPrefix + '://') - return htmlHeader(cssFilename, postsCSS) + postStr + htmlFooter() - - -def htmlPostReplies(cssCache: {}, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, baseDir: str, - session, wfRequest: {}, personCache: {}, - nickname: str, domain: str, port: int, repliesJson: {}, - httpPrefix: str, projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool) -> str: - """Show the replies to an individual post as html - """ - iconsDir = getIconsDir(baseDir) - repliesStr = '' - if repliesJson.get('orderedItems'): - for item in repliesJson['orderedItems']: - repliesStr += \ - individualPostAsHtml(True, recentPostsCache, - maxRecentPosts, - iconsDir, translate, None, - baseDir, session, wfRequest, personCache, - nickname, domain, port, item, - None, True, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, False, False, False, False) - - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - postsCSS = getCSS(baseDir, cssFilename, cssCache) - if postsCSS: - if httpPrefix != 'https': - postsCSS = postsCSS.replace('https://', - httpPrefix + '://') - return htmlHeader(cssFilename, postsCSS) + repliesStr + htmlFooter() - - def htmlRemoveSharedItem(cssCache: {}, translate: {}, baseDir: str, actor: str, shareName: str, callingDomain: str) -> str: @@ -5132,236 +1445,6 @@ def htmlUnfollowConfirm(cssCache: {}, translate: {}, baseDir: str, return followStr -def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str, - domain: str, domainFull: str, - originPathStr: str, - optionsActor: str, - optionsProfileUrl: str, - optionsLink: str, - pageNumber: int, - donateUrl: str, - xmppAddress: str, - matrixAddress: str, - ssbAddress: str, - blogAddress: str, - toxAddress: str, - PGPpubKey: str, - PGPfingerprint: str, - emailAddress) -> str: - """Show options for a person: view/follow/block/report - """ - optionsDomain, optionsPort = getDomainFromActor(optionsActor) - optionsDomainFull = optionsDomain - if optionsPort: - if optionsPort != 80 and optionsPort != 443: - optionsDomainFull = optionsDomain + ':' + str(optionsPort) - - if os.path.isfile(baseDir + '/accounts/options-background-custom.jpg'): - if not os.path.isfile(baseDir + '/accounts/options-background.jpg'): - copyfile(baseDir + '/accounts/options-background.jpg', - baseDir + '/accounts/options-background.jpg') - - followStr = 'Follow' - blockStr = 'Block' - nickname = None - optionsNickname = None - if originPathStr.startswith('/users/'): - nickname = originPathStr.split('/users/')[1] - if '/' in nickname: - nickname = nickname.split('/')[0] - if '?' in nickname: - nickname = nickname.split('?')[0] - followerDomain, followerPort = getDomainFromActor(optionsActor) - if isFollowingActor(baseDir, nickname, domain, optionsActor): - followStr = 'Unfollow' - - optionsNickname = getNicknameFromActor(optionsActor) - optionsDomainFull = optionsDomain - if optionsPort: - if optionsPort != 80 and optionsPort != 443: - optionsDomainFull = optionsDomain + ':' + str(optionsPort) - if isBlocked(baseDir, nickname, domain, - optionsNickname, optionsDomainFull): - blockStr = 'Block' - - optionsLinkStr = '' - if optionsLink: - optionsLinkStr = \ - ' \n' - cssFilename = baseDir + '/epicyon-options.css' - if os.path.isfile(baseDir + '/options.css'): - cssFilename = baseDir + '/options.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - profileStyle = \ - profileStyle.replace('--follow-text-entry-width: 90%;', - '--follow-text-entry-width: 20%;') - - if not os.path.isfile(baseDir + '/accounts/' + - 'options-background.jpg'): - profileStyle = \ - profileStyle.replace('background-image: ' + - 'url("options-background.jpg");', - 'background-image: none;') - - # To snooze, or not to snooze? That is the question - snoozeButtonStr = 'Snooze' - if nickname: - if isPersonSnoozed(baseDir, nickname, domain, optionsActor): - snoozeButtonStr = 'Unsnooze' - - donateStr = '' - if donateUrl: - donateStr = \ - ' \n' - - optionsStr = htmlHeader(cssFilename, profileStyle) - optionsStr += '

\n' - optionsStr += '
\n' - optionsStr += '
\n' - optionsStr += '
\n' - optionsStr += ' \n' - optionsStr += ' \n' - handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain - optionsStr += \ - '

' + translate['Options for'] + \ - ' @' + handle + '

\n' - if emailAddress: - optionsStr += \ - '

' + translate['Email'] + \ - ': ' + emailAddress + '

\n' - if xmppAddress: - optionsStr += \ - '

' + translate['XMPP'] + \ - ': ' + \ - xmppAddress + '

\n' - if matrixAddress: - optionsStr += \ - '

' + translate['Matrix'] + ': ' + \ - matrixAddress + '

\n' - if ssbAddress: - optionsStr += \ - '

SSB: ' + ssbAddress + '

\n' - if blogAddress: - optionsStr += \ - '

Blog: ' + \ - blogAddress + '

\n' - if toxAddress: - optionsStr += \ - '

Tox: ' + toxAddress + '

\n' - if PGPfingerprint: - optionsStr += '

PGP: ' + \ - PGPfingerprint.replace('\n', '
') + '

\n' - if PGPpubKey: - optionsStr += '

' + \ - PGPpubKey.replace('\n', '
') + '

\n' - optionsStr += '
\n' - optionsStr += ' \n' - optionsStr += ' \n' - optionsStr += ' \n' - if optionsNickname: - handle = optionsNickname + '@' + optionsDomainFull - petname = getPetName(baseDir, nickname, domain, handle) - optionsStr += \ - ' ' + translate['Petname'] + ': \n' + \ - ' \n' \ - '
\n' - - # checkbox for receiving calendar events - if isFollowingActor(baseDir, nickname, domain, optionsActor): - checkboxStr = \ - ' ' + \ - translate['Receive calendar events from this account'] + \ - '\n
\n' - if not receivingCalendarEvents(baseDir, nickname, domain, - optionsNickname, optionsDomainFull): - checkboxStr = checkboxStr.replace(' checked>', '>') - optionsStr += checkboxStr - - # checkbox for permission to post to newswire - if optionsDomainFull == domainFull: - if isModerator(baseDir, nickname) and \ - not isModerator(baseDir, optionsNickname): - newswireBlockedFilename = \ - baseDir + '/accounts/' + \ - optionsNickname + '@' + optionsDomain + '/.nonewswire' - checkboxStr = \ - ' ' + \ - translate['Allow news posts'] + \ - '\n
\n' - if os.path.isfile(newswireBlockedFilename): - checkboxStr = checkboxStr.replace(' checked>', '>') - optionsStr += checkboxStr - - optionsStr += optionsLinkStr - optionsStr += \ - ' ' - optionsStr += \ - ' ' - optionsStr += donateStr - optionsStr += \ - ' ' - optionsStr += \ - ' ' - optionsStr += \ - ' ' - optionsStr += \ - ' ' - optionsStr += \ - ' ' - - personNotes = '' - personNotesFilename = \ - baseDir + '/accounts/' + nickname + '@' + domain + \ - '/notes/' + handle + '.txt' - if os.path.isfile(personNotesFilename): - with open(personNotesFilename, 'r') as fp: - personNotes = fp.read() - - optionsStr += \ - '

' + translate['Notes'] + ': \n' - optionsStr += '
\n' - optionsStr += \ - ' \n' - - optionsStr += '
\n' - optionsStr += '
\n' - optionsStr += '
\n' - optionsStr += '
\n' - optionsStr += htmlFooter() - return optionsStr - - def htmlUnblockConfirm(cssCache: {}, translate: {}, baseDir: str, originPathStr: str, blockActor: str, @@ -5406,231 +1489,3 @@ def htmlUnblockConfirm(cssCache: {}, translate: {}, baseDir: str, blockStr += '
\n' blockStr += htmlFooter() return blockStr - - -def htmlProfileAfterSearch(cssCache: {}, - recentPostsCache: {}, maxRecentPosts: int, - translate: {}, - baseDir: str, path: str, httpPrefix: str, - nickname: str, domain: str, port: int, - profileHandle: str, - session, cachedWebfingers: {}, personCache: {}, - debug: bool, projectVersion: str, - YTReplacementDomain: str, - showPublishedDateOnly: bool) -> str: - """Show a profile page after a search for a fediverse address - """ - if '/users/' in profileHandle or \ - '/accounts/' in profileHandle or \ - '/channel/' in profileHandle or \ - '/profile/' in profileHandle or \ - '/@' in profileHandle: - searchNickname = getNicknameFromActor(profileHandle) - searchDomain, searchPort = getDomainFromActor(profileHandle) - else: - if '@' not in profileHandle: - print('DEBUG: no @ in ' + profileHandle) - return None - if profileHandle.startswith('@'): - profileHandle = profileHandle[1:] - if '@' not in profileHandle: - print('DEBUG: no @ in ' + profileHandle) - return None - searchNickname = profileHandle.split('@')[0] - searchDomain = profileHandle.split('@')[1] - searchPort = None - if ':' in searchDomain: - searchPortStr = searchDomain.split(':')[1] - if searchPortStr.isdigit(): - searchPort = int(searchPortStr) - searchDomain = searchDomain.split(':')[0] - if searchPort: - print('DEBUG: Search for handle ' + - str(searchNickname) + '@' + str(searchDomain) + ':' + - str(searchPort)) - else: - print('DEBUG: Search for handle ' + - str(searchNickname) + '@' + str(searchDomain)) - if not searchNickname: - print('DEBUG: No nickname found in ' + profileHandle) - return None - if not searchDomain: - print('DEBUG: No domain found in ' + profileHandle) - return None - - searchDomainFull = searchDomain - if searchPort: - if searchPort != 80 and searchPort != 443: - if ':' not in searchDomain: - searchDomainFull = searchDomain + ':' + str(searchPort) - - profileStr = '' - cssFilename = baseDir + '/epicyon-profile.css' - if os.path.isfile(baseDir + '/epicyon.css'): - cssFilename = baseDir + '/epicyon.css' - - profileStyle = getCSS(baseDir, cssFilename, cssCache) - if profileStyle: - wf = \ - webfingerHandle(session, - searchNickname + '@' + searchDomainFull, - httpPrefix, cachedWebfingers, - domain, projectVersion) - if not wf: - print('DEBUG: Unable to webfinger ' + - searchNickname + '@' + searchDomainFull) - print('DEBUG: cachedWebfingers ' + str(cachedWebfingers)) - print('DEBUG: httpPrefix ' + httpPrefix) - print('DEBUG: domain ' + domain) - return None - if not isinstance(wf, dict): - print('WARN: Webfinger search for ' + - searchNickname + '@' + searchDomainFull + - ' did not return a dict. ' + - str(wf)) - return None - - personUrl = None - if wf.get('errors'): - personUrl = httpPrefix + '://' + \ - searchDomainFull + '/users/' + searchNickname - - profileStr = 'https://www.w3.org/ns/activitystreams' - asHeader = { - 'Accept': 'application/activity+json; profile="' + profileStr + '"' - } - if not personUrl: - personUrl = getUserUrl(wf) - if not personUrl: - # try single user instance - asHeader = { - 'Accept': 'application/ld+json; profile="' + profileStr + '"' - } - personUrl = httpPrefix + '://' + searchDomainFull - profileJson = \ - getJson(session, personUrl, asHeader, None, - projectVersion, httpPrefix, domain) - if not profileJson: - asHeader = { - 'Accept': 'application/ld+json; profile="' + profileStr + '"' - } - profileJson = \ - getJson(session, personUrl, asHeader, None, - projectVersion, httpPrefix, domain) - if not profileJson: - print('DEBUG: No actor returned from ' + personUrl) - return None - avatarUrl = '' - if profileJson.get('icon'): - if profileJson['icon'].get('url'): - avatarUrl = profileJson['icon']['url'] - if not avatarUrl: - avatarUrl = getPersonAvatarUrl(baseDir, personUrl, - personCache, True) - displayName = searchNickname - if profileJson.get('name'): - displayName = profileJson['name'] - profileDescription = '' - if profileJson.get('summary'): - profileDescription = profileJson['summary'] - outboxUrl = None - if not profileJson.get('outbox'): - if debug: - pprint(profileJson) - print('DEBUG: No outbox found') - return None - outboxUrl = profileJson['outbox'] - profileBackgroundImage = '' - if profileJson.get('image'): - if profileJson['image'].get('url'): - profileBackgroundImage = profileJson['image']['url'] - - profileStyle = profileStyle.replace('image.png', - profileBackgroundImage) - if httpPrefix != 'https': - profileStyle = profileStyle.replace('https://', - httpPrefix + '://') - # url to return to - backUrl = path - if not backUrl.endswith('/inbox'): - backUrl += '/inbox' - - profileDescriptionShort = profileDescription - if '\n' in profileDescription: - if len(profileDescription.split('\n')) > 2: - profileDescriptionShort = '' - else: - if '
' in profileDescription: - if len(profileDescription.split('
')) > 2: - profileDescriptionShort = '' - # keep the profile description short - if len(profileDescriptionShort) > 256: - profileDescriptionShort = '' - # remove formatting from profile description used on title - avatarDescription = '' - if profileJson.get('summary'): - if isinstance(profileJson['summary'], str): - avatarDescription = \ - profileJson['summary'].replace('
', '\n') - avatarDescription = avatarDescription.replace('

', '') - avatarDescription = avatarDescription.replace('

', '') - if '<' in avatarDescription: - avatarDescription = removeHtml(avatarDescription) - profileStr = '
\n' - profileStr += '
\n' - if avatarUrl: - profileStr += \ - ' ' + avatarDescription + '\n' - profileStr += '

' + displayName + '

\n' - profileStr += '

@' + searchNickname + '@' + \ - searchDomainFull + '

\n' - profileStr += '

' + profileDescriptionShort + '

\n' - profileStr += '
\n' - profileStr += '
\n' - profileStr += '
\n' - profileStr += '
\n' - profileStr += '
\n' - profileStr += \ - ' \n' - profileStr += \ - ' \n' - profileStr += \ - ' \n' - profileStr += \ - ' \n' - profileStr += '
\n' - profileStr += '
\n' - profileStr += '
\n' - - iconsDir = getIconsDir(baseDir) - i = 0 - for item in parseUserFeed(session, outboxUrl, asHeader, - projectVersion, httpPrefix, domain): - if not item.get('type'): - continue - if item['type'] != 'Create' and item['type'] != 'Announce': - continue - if not item.get('object'): - continue - profileStr += \ - individualPostAsHtml(True, recentPostsCache, maxRecentPosts, - iconsDir, translate, None, baseDir, - session, cachedWebfingers, personCache, - nickname, domain, port, - item, avatarUrl, False, False, - httpPrefix, projectVersion, 'inbox', - YTReplacementDomain, - showPublishedDateOnly, - False, False, False, False, False) - i += 1 - if i >= 20: - break - - return htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter() diff --git a/webapp_column_left.py b/webapp_column_left.py new file mode 100644 index 00000000..736bfb1b --- /dev/null +++ b/webapp_column_left.py @@ -0,0 +1,317 @@ +__filename__ = "webapp_column_left.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from shutil import copyfile +from utils import getConfigParam +from utils import getCSS +from utils import getNicknameFromActor +from posts import isEditor +from webapp_utils import htmlPostSeparator +from webapp_utils import getLeftImageFile +from webapp_utils import getImageFile +from webapp_utils import headerButtonsFrontScreen +from webapp_utils import getIconsDir +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter +from webapp_utils import getBannerFile + + +def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str, + httpPrefix: str, translate: {}, + iconsDir: str, editor: bool, + showBackButton: bool, timelinePath: str, + rssIconAtTop: bool, showHeaderImage: bool, + frontPage: bool) -> str: + """Returns html content for the left column + """ + htmlStr = '' + + separatorStr = htmlPostSeparator(baseDir, 'left') + domain = domainFull + if ':' in domain: + domain = domain.split(':') + + editImageClass = '' + if showHeaderImage: + leftImageFile, leftColumnImageFilename = \ + getLeftImageFile(baseDir, nickname, domain) + if not os.path.isfile(leftColumnImageFilename): + theme = getConfigParam(baseDir, 'theme').lower() + if theme == 'default': + theme = '' + else: + theme = '_' + theme + themeLeftImageFile, themeLeftColumnImageFilename = \ + getImageFile(baseDir, 'left_col_image', baseDir + '/img', + nickname, domain) + if os.path.isfile(themeLeftColumnImageFilename): + leftColumnImageFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/' + themeLeftImageFile + copyfile(themeLeftColumnImageFilename, + leftColumnImageFilename) + leftImageFile = themeLeftImageFile + + # show the image at the top of the column + editImageClass = 'leftColEdit' + if os.path.isfile(leftColumnImageFilename): + editImageClass = 'leftColEditImage' + htmlStr += \ + '\n
\n' + \ + ' \n' + \ + '
\n' + + if showBackButton: + htmlStr += \ + '
' + \ + ' ' + \ + '\n' + + if (editor or rssIconAtTop) and not showHeaderImage: + htmlStr += '
' + + if editImageClass == 'leftColEdit': + htmlStr += '\n
\n' + + htmlStr += '
\n' + if editor: + # show the edit icon + htmlStr += \ + ' ' + \ + '' + \
+            translate['Edit Links'] + '\n' + + # RSS icon + if nickname != 'news': + # rss feed for this account + rssUrl = httpPrefix + '://' + domainFull + \ + '/blog/' + nickname + '/rss.xml' + else: + # rss feed for all accounts on the instance + rssUrl = httpPrefix + '://' + domainFull + '/blog/rss.xml' + if not frontPage: + rssTitle = translate['RSS feed for your blog'] + else: + rssTitle = translate['RSS feed for this site'] + rssIconStr = \ + ' ' + \ + '' + rssTitle + \
+        '\n' + if rssIconAtTop: + htmlStr += rssIconStr + htmlStr += '
\n' + + if editImageClass == 'leftColEdit': + htmlStr += '
\n' + + if (editor or rssIconAtTop) and not showHeaderImage: + htmlStr += '

' + + # if showHeaderImage: + # htmlStr += '
' + + linksFilename = baseDir + '/accounts/links.txt' + linksFileContainsEntries = False + if os.path.isfile(linksFilename): + linksList = None + with open(linksFilename, "r") as f: + linksList = f.readlines() + if linksList: + for lineStr in linksList: + if ' ' not in lineStr: + if '#' not in lineStr: + if '*' not in lineStr: + continue + lineStr = lineStr.strip() + words = lineStr.split(' ') + # get the link + linkStr = None + for word in words: + if word == '#': + continue + if word == '*': + continue + if '://' in word: + linkStr = word + break + if linkStr: + lineStr = lineStr.replace(linkStr, '').strip() + # avoid any dubious scripts being added + if '<' not in lineStr: + # remove trailing comma if present + if lineStr.endswith(','): + lineStr = lineStr[:len(lineStr)-1] + # add link to the returned html + htmlStr += \ + '

' + \ + lineStr + '

\n' + linksFileContainsEntries = True + else: + if lineStr.startswith('#') or lineStr.startswith('*'): + lineStr = lineStr[1:].strip() + htmlStr += separatorStr + htmlStr += \ + '

' + \ + lineStr + '

\n' + else: + htmlStr += \ + '

' + lineStr + '

\n' + linksFileContainsEntries = True + + if linksFileContainsEntries and not rssIconAtTop: + htmlStr += '
' + rssIconStr + '
' + return htmlStr + + +def htmlLinksMobile(cssCache: {}, baseDir: str, + nickname: str, domainFull: str, + httpPrefix: str, translate, + timelinePath: str, authorized: bool, + rssIconAtTop: bool, + iconsAsButtons: bool, + defaultTimeline: str) -> str: + """Show the left column links within mobile view + """ + htmlStr = '' + + # the css filename + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + # replace any https within the css with whatever prefix is needed + if httpPrefix != 'https': + profileStyle = \ + profileStyle.replace('https://', httpPrefix + '://') + + iconsDir = getIconsDir(baseDir) + + # is the user a site editor? + if nickname == 'news': + editor = False + else: + editor = isEditor(baseDir, nickname) + + domain = domainFull + if ':' in domain: + domain = domain.split(':')[0] + + htmlStr = htmlHeader(cssFilename, profileStyle) + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + htmlStr += \ + '' + \ + '\n' + + htmlStr += '
' + \ + headerButtonsFrontScreen(translate, nickname, + 'links', authorized, + iconsAsButtons, iconsDir) + '
' + htmlStr += \ + getLeftColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, + iconsDir, editor, + False, timelinePath, + rssIconAtTop, False, False) + htmlStr += '
\n' + htmlFooter() + return htmlStr + + +def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str, + domain: str, port: int, httpPrefix: str, + defaultTimeline: str) -> str: + """Shows the edit links screen + """ + if '/users/' not in path: + return '' + path = path.replace('/inbox', '').replace('/outbox', '') + path = path.replace('/shares', '') + + nickname = getNicknameFromActor(path) + if not nickname: + return '' + + # is the user a moderator? + if not isEditor(baseDir, nickname): + return '' + + cssFilename = baseDir + '/epicyon-links.css' + if os.path.isfile(baseDir + '/links.css'): + cssFilename = baseDir + '/links.css' + + editCSS = getCSS(baseDir, cssFilename, cssCache) + if editCSS: + if httpPrefix != 'https': + editCSS = \ + editCSS.replace('https://', httpPrefix + '://') + + # filename of the banner shown at the top + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + + editLinksForm = htmlHeader(cssFilename, editCSS) + + # top banner + editLinksForm += \ + '\n' + editLinksForm += '\n' + + editLinksForm += \ + '
\n' + editLinksForm += \ + '
\n' + editLinksForm += \ + '

' + translate['Edit Links'] + '

' + editLinksForm += \ + '
\n' + # editLinksForm += \ + # ' \n' + editLinksForm += \ + '
\n' + \ + ' \n' + \ + '
\n' + editLinksForm += \ + '
\n' + + linksFilename = baseDir + '/accounts/links.txt' + linksStr = '' + if os.path.isfile(linksFilename): + with open(linksFilename, 'r') as fp: + linksStr = fp.read() + + editLinksForm += \ + '
' + editLinksForm += \ + ' ' + \ + translate['One link per line. Description followed by the link.'] + \ + '
' + editLinksForm += \ + ' ' + editLinksForm += \ + '
' + + editLinksForm += htmlFooter() + return editLinksForm diff --git a/webapp_column_right.py b/webapp_column_right.py new file mode 100644 index 00000000..c8b72a7b --- /dev/null +++ b/webapp_column_right.py @@ -0,0 +1,567 @@ +__filename__ = "webapp_column_right.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from datetime import datetime +from shutil import copyfile +from content import removeLongWords +from utils import getCSS +from utils import getConfigParam +from utils import votesOnNewswireItem +from utils import getNicknameFromActor +from posts import isEditor +from posts import isModerator +from webapp_utils import getRightImageFile +from webapp_utils import getImageFile +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter +from webapp_utils import getBannerFile +from webapp_utils import htmlPostSeparator +from webapp_utils import headerButtonsFrontScreen +from webapp_utils import getIconsDir + + +def votesIndicator(totalVotes: int, positiveVoting: bool) -> str: + """Returns an indicator of the number of votes on a newswire item + """ + if totalVotes <= 0: + return '' + totalVotesStr = ' ' + for v in range(totalVotes): + if positiveVoting: + totalVotesStr += '✓' + else: + totalVotesStr += '✗' + return totalVotesStr + + +def getRightColumnContent(baseDir: str, nickname: str, domainFull: str, + httpPrefix: str, translate: {}, + iconsDir: str, moderator: bool, editor: bool, + newswire: {}, positiveVoting: bool, + showBackButton: bool, timelinePath: str, + showPublishButton: bool, + showPublishAsIcon: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool, + showHeaderImage: bool) -> str: + """Returns html content for the right column + """ + htmlStr = '' + + domain = domainFull + if ':' in domain: + domain = domain.split(':') + + if authorized: + # only show the publish button if logged in, otherwise replace it with + # a login button + publishButtonStr = \ + ' ' + \ + '\n' + else: + # if not logged in then replace the publish button with + # a login button + publishButtonStr = \ + ' \n' + + # show publish button at the top if needed + if publishButtonAtTop: + htmlStr += '
' + publishButtonStr + '
' + + # show a column header image, eg. title of the theme or newswire banner + editImageClass = '' + if showHeaderImage: + rightImageFile, rightColumnImageFilename = \ + getRightImageFile(baseDir, nickname, domain) + if not os.path.isfile(rightColumnImageFilename): + theme = getConfigParam(baseDir, 'theme').lower() + if theme == 'default': + theme = '' + else: + theme = '_' + theme + themeRightImageFile, themeRightColumnImageFilename = \ + getImageFile(baseDir, 'right_col_image', baseDir + '/img', + nickname, domain) + if os.path.isfile(themeRightColumnImageFilename): + rightColumnImageFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/' + themeRightImageFile + copyfile(themeRightColumnImageFilename, + rightColumnImageFilename) + rightImageFile = themeRightImageFile + + # show the image at the top of the column + editImageClass = 'rightColEdit' + if os.path.isfile(rightColumnImageFilename): + editImageClass = 'rightColEditImage' + htmlStr += \ + '\n
\n' + \ + ' \n' + \ + '
\n' + + if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: + htmlStr += '
' + + if editImageClass == 'rightColEdit': + htmlStr += '\n
\n' + + # whether to show a back icon + # This is probably going to be osolete soon + if showBackButton: + htmlStr += \ + ' ' + \ + '\n' + + if showPublishButton and not publishButtonAtTop: + if not showPublishAsIcon: + htmlStr += publishButtonStr + + # show the edit icon + if editor: + if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'): + # show the edit icon highlighted + htmlStr += \ + ' ' + \ + '' + \
+                translate['Edit newswire'] + '\n' + else: + # show the edit icon + htmlStr += \ + ' ' + \ + '' + \
+                translate['Edit newswire'] + '\n' + + # show the RSS icon + rssIconStr = \ + ' ' + \ + '' + \
+        translate['Newswire RSS Feed'] + '\n' + if rssIconAtTop: + htmlStr += rssIconStr + + # show publish icon at top + if showPublishButton: + if showPublishAsIcon: + htmlStr += \ + ' ' + \ + '' + \
+                translate['Publish a news article'] + '\n' + + if editImageClass == 'rightColEdit': + htmlStr += '
\n' + else: + if showHeaderImage: + htmlStr += '
\n' + + if (showPublishButton or editor or rssIconAtTop) and not showHeaderImage: + htmlStr += '

' + + # show the newswire lines + newswireContentStr = \ + htmlNewswire(baseDir, newswire, nickname, moderator, translate, + positiveVoting, iconsDir) + htmlStr += newswireContentStr + + # show the rss icon at the bottom, typically on the right hand side + if newswireContentStr and not rssIconAtTop: + htmlStr += '
' + rssIconStr + '
' + return htmlStr + + +def htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool, + translate: {}, positiveVoting: bool, iconsDir: str) -> str: + """Converts a newswire dict into html + """ + separatorStr = htmlPostSeparator(baseDir, 'right') + htmlStr = '' + for dateStr, item in newswire.items(): + publishedDate = \ + datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") + dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") + + dateStrLink = dateStr.replace('T', ' ') + dateStrLink = dateStrLink.replace('Z', '') + moderatedItem = item[5] + htmlStr += separatorStr + if moderatedItem and 'vote:' + nickname in item[2]: + totalVotesStr = '' + totalVotes = 0 + if moderator: + totalVotes = votesOnNewswireItem(item[2]) + totalVotesStr = \ + votesIndicator(totalVotes, positiveVoting) + + title = removeLongWords(item[0], 16, []).replace('\n', '
') + htmlStr += '

' + \ + '' + \ + '' + title + \ + '' + totalVotesStr + if moderator: + htmlStr += \ + ' ' + dateShown + '' + htmlStr += '

\n' + else: + htmlStr += ' ' + htmlStr += dateShown + '

\n' + else: + totalVotesStr = '' + totalVotes = 0 + if moderator: + if moderatedItem: + totalVotes = votesOnNewswireItem(item[2]) + # show a number of ticks or crosses for how many + # votes for or against + totalVotesStr = \ + votesIndicator(totalVotes, positiveVoting) + + title = removeLongWords(item[0], 16, []).replace('\n', '
') + if moderator and moderatedItem: + htmlStr += '

' + \ + '' + \ + title + '' + totalVotesStr + htmlStr += ' ' + dateShown + htmlStr += '' + htmlStr += '' + htmlStr += '

\n' + else: + htmlStr += '

' + \ + '' + \ + title + '' + \ + totalVotesStr + htmlStr += ' ' + htmlStr += dateShown + '

\n' + return htmlStr + + +def htmlCitations(baseDir: str, nickname: str, domain: str, + httpPrefix: str, defaultTimeline: str, + translate: {}, newswire: {}, cssCache: {}, + blogTitle: str, blogContent: str, + blogImageFilename: str, + blogImageAttachmentMediaType: str, + blogImageDescription: str) -> str: + """Show the citations screen when creating a blog + """ + htmlStr = '' + + # create a list of dates for citations + # these can then be used to re-select checkboxes later + citationsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/.citations.txt' + citationsSelected = [] + if os.path.isfile(citationsFilename): + citationsSeparator = '#####' + with open(citationsFilename, "r") as f: + citations = f.readlines() + for line in citations: + if citationsSeparator not in line: + continue + sections = line.strip().split(citationsSeparator) + if len(sections) != 3: + continue + dateStr = sections[0] + citationsSelected.append(dateStr) + + # the css filename + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + # replace any https within the css with whatever prefix is needed + if httpPrefix != 'https': + profileStyle = \ + profileStyle.replace('https://', httpPrefix + '://') + + # iconsDir = getIconsDir(baseDir) + + htmlStr = htmlHeader(cssFilename, profileStyle) + + # top banner + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + htmlStr += \ + '\n' + htmlStr += '\n' + + htmlStr += \ + '\n' + htmlStr += '
\n' + htmlStr += translate['Choose newswire items ' + + 'referenced in your article'] + '
' + if blogTitle is None: + blogTitle = '' + htmlStr += \ + ' \n' + if blogContent is None: + blogContent = '' + htmlStr += \ + ' \n' + # submit button + htmlStr += \ + ' \n' + htmlStr += '
\n' + + citationsSeparator = '#####' + + # list of newswire items + if newswire: + ctr = 0 + for dateStr, item in newswire.items(): + # should this checkbox be selected? + selectedStr = '' + if dateStr in citationsSelected: + selectedStr = ' checked' + + publishedDate = \ + datetime.strptime(dateStr, "%Y-%m-%d %H:%M:%S%z") + dateShown = publishedDate.strftime("%Y-%m-%d %H:%M") + + title = removeLongWords(item[0], 16, []).replace('\n', '
') + link = item[1] + + citationValue = \ + dateStr + citationsSeparator + \ + title + citationsSeparator + \ + link + htmlStr += \ + '' + \ + '' + title + ' ' + htmlStr += '' + \ + dateShown + '
\n' + ctr += 1 + + htmlStr += '\n' + return htmlStr + htmlFooter() + + +def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str, + domain: str, domainFull: str, + httpPrefix: str, translate: {}, + newswire: {}, + positiveVoting: bool, + timelinePath: str, + showPublishAsIcon: bool, + authorized: bool, + rssIconAtTop: bool, + iconsAsButtons: bool, + defaultTimeline: str) -> str: + """Shows the mobile version of the newswire right column + """ + htmlStr = '' + + # the css filename + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + # replace any https within the css with whatever prefix is needed + if httpPrefix != 'https': + profileStyle = \ + profileStyle.replace('https://', + httpPrefix + '://') + + iconsDir = getIconsDir(baseDir) + + if nickname == 'news': + editor = False + moderator = False + else: + # is the user a moderator? + moderator = isModerator(baseDir, nickname) + + # is the user a site editor? + editor = isEditor(baseDir, nickname) + + showPublishButton = editor + + htmlStr = htmlHeader(cssFilename, profileStyle) + + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + htmlStr += \ + '' + \ + '\n' + + htmlStr += '
' + \ + headerButtonsFrontScreen(translate, nickname, + 'newswire', authorized, + iconsAsButtons, iconsDir) + '
' + htmlStr += \ + getRightColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, + iconsDir, moderator, editor, + newswire, positiveVoting, + False, timelinePath, showPublishButton, + showPublishAsIcon, rssIconAtTop, False, + authorized, False) + htmlStr += htmlFooter() + return htmlStr + + +def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str, + domain: str, port: int, httpPrefix: str, + defaultTimeline: str) -> str: + """Shows the edit newswire screen + """ + if '/users/' not in path: + return '' + path = path.replace('/inbox', '').replace('/outbox', '') + path = path.replace('/shares', '') + + nickname = getNicknameFromActor(path) + if not nickname: + return '' + + # is the user a moderator? + if not isModerator(baseDir, nickname): + return '' + + cssFilename = baseDir + '/epicyon-links.css' + if os.path.isfile(baseDir + '/links.css'): + cssFilename = baseDir + '/links.css' + + editCSS = getCSS(baseDir, cssFilename, cssCache) + if editCSS: + if httpPrefix != 'https': + editCSS = \ + editCSS.replace('https://', httpPrefix + '://') + + # filename of the banner shown at the top + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + + editNewswireForm = htmlHeader(cssFilename, editCSS) + + # top banner + editNewswireForm += \ + '\n' + editNewswireForm += '\n' + + editNewswireForm += \ + '
\n' + editNewswireForm += \ + '
\n' + editNewswireForm += \ + '

' + translate['Edit newswire'] + '

' + editNewswireForm += \ + '
\n' + # editNewswireForm += \ + # ' \n' + editNewswireForm += \ + '
\n' + \ + ' \n' + \ + '
\n' + editNewswireForm += \ + '
\n' + + newswireFilename = baseDir + '/accounts/newswire.txt' + newswireStr = '' + if os.path.isfile(newswireFilename): + with open(newswireFilename, 'r') as fp: + newswireStr = fp.read() + + editNewswireForm += \ + '
' + + editNewswireForm += \ + ' ' + \ + translate['Add RSS feed links below.'] + \ + '
' + editNewswireForm += \ + ' ' + + filterStr = '' + filterFilename = \ + baseDir + '/accounts/news@' + domain + '/filters.txt' + if os.path.isfile(filterFilename): + with open(filterFilename, 'r') as filterfile: + filterStr = filterfile.read() + + editNewswireForm += \ + '
\n' + editNewswireForm += '
' + editNewswireForm += ' \n' + + hashtagRulesStr = '' + hashtagRulesFilename = \ + baseDir + '/accounts/hashtagrules.txt' + if os.path.isfile(hashtagRulesFilename): + with open(hashtagRulesFilename, 'r') as rulesfile: + hashtagRulesStr = rulesfile.read() + + editNewswireForm += \ + '
\n' + editNewswireForm += '
\n' + editNewswireForm += \ + ' ' + translate['See instructions'] + '\n' + editNewswireForm += ' \n' + + editNewswireForm += \ + '
' + + editNewswireForm += htmlFooter() + return editNewswireForm diff --git a/webapp_person_options.py b/webapp_person_options.py new file mode 100644 index 00000000..9c37e00d --- /dev/null +++ b/webapp_person_options.py @@ -0,0 +1,251 @@ +__filename__ = "webapp_person_options.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from shutil import copyfile +from petnames import getPetName +from person import isPersonSnoozed +from posts import isModerator +from utils import getDomainFromActor +from utils import getNicknameFromActor +from utils import getCSS +from blocking import isBlocked +from follow import isFollowingActor +from followingCalendar import receivingCalendarEvents +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter + + +def htmlPersonOptions(cssCache: {}, translate: {}, baseDir: str, + domain: str, domainFull: str, + originPathStr: str, + optionsActor: str, + optionsProfileUrl: str, + optionsLink: str, + pageNumber: int, + donateUrl: str, + xmppAddress: str, + matrixAddress: str, + ssbAddress: str, + blogAddress: str, + toxAddress: str, + PGPpubKey: str, + PGPfingerprint: str, + emailAddress) -> str: + """Show options for a person: view/follow/block/report + """ + optionsDomain, optionsPort = getDomainFromActor(optionsActor) + optionsDomainFull = optionsDomain + if optionsPort: + if optionsPort != 80 and optionsPort != 443: + optionsDomainFull = optionsDomain + ':' + str(optionsPort) + + if os.path.isfile(baseDir + '/accounts/options-background-custom.jpg'): + if not os.path.isfile(baseDir + '/accounts/options-background.jpg'): + copyfile(baseDir + '/accounts/options-background.jpg', + baseDir + '/accounts/options-background.jpg') + + followStr = 'Follow' + blockStr = 'Block' + nickname = None + optionsNickname = None + if originPathStr.startswith('/users/'): + nickname = originPathStr.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + if '?' in nickname: + nickname = nickname.split('?')[0] + followerDomain, followerPort = getDomainFromActor(optionsActor) + if isFollowingActor(baseDir, nickname, domain, optionsActor): + followStr = 'Unfollow' + + optionsNickname = getNicknameFromActor(optionsActor) + optionsDomainFull = optionsDomain + if optionsPort: + if optionsPort != 80 and optionsPort != 443: + optionsDomainFull = optionsDomain + ':' + str(optionsPort) + if isBlocked(baseDir, nickname, domain, + optionsNickname, optionsDomainFull): + blockStr = 'Block' + + optionsLinkStr = '' + if optionsLink: + optionsLinkStr = \ + ' \n' + cssFilename = baseDir + '/epicyon-options.css' + if os.path.isfile(baseDir + '/options.css'): + cssFilename = baseDir + '/options.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + profileStyle = \ + profileStyle.replace('--follow-text-entry-width: 90%;', + '--follow-text-entry-width: 20%;') + + if not os.path.isfile(baseDir + '/accounts/' + + 'options-background.jpg'): + profileStyle = \ + profileStyle.replace('background-image: ' + + 'url("options-background.jpg");', + 'background-image: none;') + + # To snooze, or not to snooze? That is the question + snoozeButtonStr = 'Snooze' + if nickname: + if isPersonSnoozed(baseDir, nickname, domain, optionsActor): + snoozeButtonStr = 'Unsnooze' + + donateStr = '' + if donateUrl: + donateStr = \ + ' \n' + + optionsStr = htmlHeader(cssFilename, profileStyle) + optionsStr += '

\n' + optionsStr += '
\n' + optionsStr += '
\n' + optionsStr += '
\n' + optionsStr += ' \n' + optionsStr += ' \n' + handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain + optionsStr += \ + '

' + translate['Options for'] + \ + ' @' + handle + '

\n' + if emailAddress: + optionsStr += \ + '

' + translate['Email'] + \ + ': ' + emailAddress + '

\n' + if xmppAddress: + optionsStr += \ + '

' + translate['XMPP'] + \ + ': ' + \ + xmppAddress + '

\n' + if matrixAddress: + optionsStr += \ + '

' + translate['Matrix'] + ': ' + \ + matrixAddress + '

\n' + if ssbAddress: + optionsStr += \ + '

SSB: ' + ssbAddress + '

\n' + if blogAddress: + optionsStr += \ + '

Blog: ' + \ + blogAddress + '

\n' + if toxAddress: + optionsStr += \ + '

Tox: ' + toxAddress + '

\n' + if PGPfingerprint: + optionsStr += '

PGP: ' + \ + PGPfingerprint.replace('\n', '
') + '

\n' + if PGPpubKey: + optionsStr += '

' + \ + PGPpubKey.replace('\n', '
') + '

\n' + optionsStr += ' \n' + optionsStr += ' \n' + optionsStr += ' \n' + optionsStr += ' \n' + if optionsNickname: + handle = optionsNickname + '@' + optionsDomainFull + petname = getPetName(baseDir, nickname, domain, handle) + optionsStr += \ + ' ' + translate['Petname'] + ': \n' + \ + ' \n' \ + '
\n' + + # checkbox for receiving calendar events + if isFollowingActor(baseDir, nickname, domain, optionsActor): + checkboxStr = \ + ' ' + \ + translate['Receive calendar events from this account'] + \ + '\n
\n' + if not receivingCalendarEvents(baseDir, nickname, domain, + optionsNickname, optionsDomainFull): + checkboxStr = checkboxStr.replace(' checked>', '>') + optionsStr += checkboxStr + + # checkbox for permission to post to newswire + if optionsDomainFull == domainFull: + if isModerator(baseDir, nickname) and \ + not isModerator(baseDir, optionsNickname): + newswireBlockedFilename = \ + baseDir + '/accounts/' + \ + optionsNickname + '@' + optionsDomain + '/.nonewswire' + checkboxStr = \ + ' ' + \ + translate['Allow news posts'] + \ + '\n
\n' + if os.path.isfile(newswireBlockedFilename): + checkboxStr = checkboxStr.replace(' checked>', '>') + optionsStr += checkboxStr + + optionsStr += optionsLinkStr + optionsStr += \ + ' ' + optionsStr += \ + ' ' + optionsStr += donateStr + optionsStr += \ + ' ' + optionsStr += \ + ' ' + optionsStr += \ + ' ' + optionsStr += \ + ' ' + optionsStr += \ + ' ' + + personNotes = '' + personNotesFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + \ + '/notes/' + handle + '.txt' + if os.path.isfile(personNotesFilename): + with open(personNotesFilename, 'r') as fp: + personNotes = fp.read() + + optionsStr += \ + '

' + translate['Notes'] + ': \n' + optionsStr += '
\n' + optionsStr += \ + ' \n' + + optionsStr += ' \n' + optionsStr += '
\n' + optionsStr += '
\n' + optionsStr += '
\n' + optionsStr += htmlFooter() + return optionsStr diff --git a/webapp_post.py b/webapp_post.py index 95fea56c..c86557ec 100644 --- a/webapp_post.py +++ b/webapp_post.py @@ -16,11 +16,16 @@ from cache import getPersonFromCache from bookmarks import bookmarkedByPerson from like import likedByPerson from like import noOfLikes +from follow import isFollowingActor from posts import isEditor from posts import postIsMuted from posts import getPersonBox from posts import isDM from posts import downloadAnnounce +from posts import populateRepliesJson +from utils import locatePost +from utils import loadJson +from utils import getCSS from utils import getCachedPostDirectory from utils import getCachedPostFilename from utils import getProtocolPrefixes @@ -49,6 +54,9 @@ from webapp_utils import addEmojiToDisplayName from webapp_utils import postContainsPublic from webapp_utils import getContentWarningButton from webapp_utils import getPostAttachmentsAsHtml +from webapp_utils import getIconsDir +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter from webapp_media import addEmbeddedElements from webapp_question import insertQuestion from devices import E2EEdecryptMessageFromDevice @@ -1215,3 +1223,162 @@ def individualPostAsHtml(allowDownloads: bool, print('TIMING INDIV ' + boxName + ' 19 = ' + str(timeDiff)) return postHtml + + +def htmlIndividualPost(cssCache: {}, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, + baseDir: str, session, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, authorized: bool, + postJsonObject: {}, httpPrefix: str, + projectVersion: str, likedBy: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool) -> str: + """Show an individual post as html + """ + iconsDir = getIconsDir(baseDir) + postStr = '' + if likedBy: + likedByNickname = getNicknameFromActor(likedBy) + likedByDomain, likedByPort = getDomainFromActor(likedBy) + if likedByPort: + if likedByPort != 80 and likedByPort != 443: + likedByDomain += ':' + str(likedByPort) + likedByHandle = likedByNickname + '@' + likedByDomain + postStr += \ + '

' + translate['Liked by'] + \ + ' @' + \ + likedByHandle + '\n' + + domainFull = domain + if port: + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + actor = '/users/' + nickname + followStr = '

\n' + followStr += \ + ' \n' + followStr += \ + ' \n' + if not isFollowingActor(baseDir, nickname, domainFull, likedBy): + followStr += ' \n' + followStr += ' \n' + followStr += '
\n' + postStr += followStr + '

\n' + + postStr += \ + individualPostAsHtml(True, recentPostsCache, maxRecentPosts, + iconsDir, translate, None, + baseDir, session, wfRequest, personCache, + nickname, domain, port, postJsonObject, + None, True, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, authorized, False, False, False) + messageId = removeIdEnding(postJsonObject['id']) + + # show the previous posts + if isinstance(postJsonObject['object'], dict): + while postJsonObject['object'].get('inReplyTo'): + postFilename = \ + locatePost(baseDir, nickname, domain, + postJsonObject['object']['inReplyTo']) + if not postFilename: + break + postJsonObject = loadJson(postFilename) + if postJsonObject: + postStr = \ + individualPostAsHtml(True, recentPostsCache, + maxRecentPosts, + iconsDir, translate, None, + baseDir, session, wfRequest, + personCache, + nickname, domain, port, + postJsonObject, + None, True, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, authorized, + False, False, False) + postStr + + # show the following posts + postFilename = locatePost(baseDir, nickname, domain, messageId) + if postFilename: + # is there a replies file for this post? + repliesFilename = postFilename.replace('.json', '.replies') + if os.path.isfile(repliesFilename): + # get items from the replies file + repliesJson = { + 'orderedItems': [] + } + populateRepliesJson(baseDir, nickname, domain, + repliesFilename, authorized, repliesJson) + # add items to the html output + for item in repliesJson['orderedItems']: + postStr += \ + individualPostAsHtml(True, recentPostsCache, + maxRecentPosts, + iconsDir, translate, None, + baseDir, session, wfRequest, + personCache, + nickname, domain, port, item, + None, True, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, authorized, + False, False, False) + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + postsCSS = getCSS(baseDir, cssFilename, cssCache) + if postsCSS: + if httpPrefix != 'https': + postsCSS = postsCSS.replace('https://', + httpPrefix + '://') + return htmlHeader(cssFilename, postsCSS) + postStr + htmlFooter() + + +def htmlPostReplies(cssCache: {}, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, baseDir: str, + session, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, repliesJson: {}, + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool) -> str: + """Show the replies to an individual post as html + """ + iconsDir = getIconsDir(baseDir) + repliesStr = '' + if repliesJson.get('orderedItems'): + for item in repliesJson['orderedItems']: + repliesStr += \ + individualPostAsHtml(True, recentPostsCache, + maxRecentPosts, + iconsDir, translate, None, + baseDir, session, wfRequest, personCache, + nickname, domain, port, item, + None, True, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, False, False, False, False) + + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + postsCSS = getCSS(baseDir, cssFilename, cssCache) + if postsCSS: + if httpPrefix != 'https': + postsCSS = postsCSS.replace('https://', + httpPrefix + '://') + return htmlHeader(cssFilename, postsCSS) + repliesStr + htmlFooter() diff --git a/webapp_profile.py b/webapp_profile.py new file mode 100644 index 00000000..4af17a41 --- /dev/null +++ b/webapp_profile.py @@ -0,0 +1,1472 @@ +__filename__ = "webapp_profile.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "1.1.0" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import os +from pprint import pprint +from utils import getNicknameFromActor +from utils import getDomainFromActor +from utils import getCSS +from utils import isSystemAccount +from utils import removeHtml +from utils import loadJson +from utils import getConfigParam +from skills import getSkills +from theme import getThemesList +from person import personBoxJson +from webfinger import webfingerHandle +from session import getJson +from posts import parseUserFeed +from posts import getUserUrl +from posts import getPersonBox +from donate import getDonationUrl +from xmpp import getXmppAddress +from matrix import getMatrixAddress +from ssb import getSSBAddress +from pgp import getEmailAddress +from pgp import getPGPfingerprint +from pgp import getPGPpubKey +from tox import getToxAddress +from webapp_utils import scheduledPostsExist +from webapp_utils import getPersonAvatarUrl +from webapp_utils import getIconsDir +from webapp_utils import htmlHeader +from webapp_utils import htmlFooter +from webapp_utils import addEmojiToDisplayName +from webapp_utils import headerButtonsFrontScreen +from webapp_utils import getBannerFile +from webapp_utils import htmlPostSeparator +from webapp_utils import getBlogAddress +from webapp_post import individualPostAsHtml +from webapp_column_left import getLeftColumnContent +from webapp_column_right import getRightColumnContent +from webapp_timeline import htmlIndividualShare + + +def htmlProfileAfterSearch(cssCache: {}, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, + baseDir: str, path: str, httpPrefix: str, + nickname: str, domain: str, port: int, + profileHandle: str, + session, cachedWebfingers: {}, personCache: {}, + debug: bool, projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool) -> str: + """Show a profile page after a search for a fediverse address + """ + if '/users/' in profileHandle or \ + '/accounts/' in profileHandle or \ + '/channel/' in profileHandle or \ + '/profile/' in profileHandle or \ + '/@' in profileHandle: + searchNickname = getNicknameFromActor(profileHandle) + searchDomain, searchPort = getDomainFromActor(profileHandle) + else: + if '@' not in profileHandle: + print('DEBUG: no @ in ' + profileHandle) + return None + if profileHandle.startswith('@'): + profileHandle = profileHandle[1:] + if '@' not in profileHandle: + print('DEBUG: no @ in ' + profileHandle) + return None + searchNickname = profileHandle.split('@')[0] + searchDomain = profileHandle.split('@')[1] + searchPort = None + if ':' in searchDomain: + searchPortStr = searchDomain.split(':')[1] + if searchPortStr.isdigit(): + searchPort = int(searchPortStr) + searchDomain = searchDomain.split(':')[0] + if searchPort: + print('DEBUG: Search for handle ' + + str(searchNickname) + '@' + str(searchDomain) + ':' + + str(searchPort)) + else: + print('DEBUG: Search for handle ' + + str(searchNickname) + '@' + str(searchDomain)) + if not searchNickname: + print('DEBUG: No nickname found in ' + profileHandle) + return None + if not searchDomain: + print('DEBUG: No domain found in ' + profileHandle) + return None + + searchDomainFull = searchDomain + if searchPort: + if searchPort != 80 and searchPort != 443: + if ':' not in searchDomain: + searchDomainFull = searchDomain + ':' + str(searchPort) + + profileStr = '' + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + wf = \ + webfingerHandle(session, + searchNickname + '@' + searchDomainFull, + httpPrefix, cachedWebfingers, + domain, projectVersion) + if not wf: + print('DEBUG: Unable to webfinger ' + + searchNickname + '@' + searchDomainFull) + print('DEBUG: cachedWebfingers ' + str(cachedWebfingers)) + print('DEBUG: httpPrefix ' + httpPrefix) + print('DEBUG: domain ' + domain) + return None + if not isinstance(wf, dict): + print('WARN: Webfinger search for ' + + searchNickname + '@' + searchDomainFull + + ' did not return a dict. ' + + str(wf)) + return None + + personUrl = None + if wf.get('errors'): + personUrl = httpPrefix + '://' + \ + searchDomainFull + '/users/' + searchNickname + + profileStr = 'https://www.w3.org/ns/activitystreams' + asHeader = { + 'Accept': 'application/activity+json; profile="' + profileStr + '"' + } + if not personUrl: + personUrl = getUserUrl(wf) + if not personUrl: + # try single user instance + asHeader = { + 'Accept': 'application/ld+json; profile="' + profileStr + '"' + } + personUrl = httpPrefix + '://' + searchDomainFull + profileJson = \ + getJson(session, personUrl, asHeader, None, + projectVersion, httpPrefix, domain) + if not profileJson: + asHeader = { + 'Accept': 'application/ld+json; profile="' + profileStr + '"' + } + profileJson = \ + getJson(session, personUrl, asHeader, None, + projectVersion, httpPrefix, domain) + if not profileJson: + print('DEBUG: No actor returned from ' + personUrl) + return None + avatarUrl = '' + if profileJson.get('icon'): + if profileJson['icon'].get('url'): + avatarUrl = profileJson['icon']['url'] + if not avatarUrl: + avatarUrl = getPersonAvatarUrl(baseDir, personUrl, + personCache, True) + displayName = searchNickname + if profileJson.get('name'): + displayName = profileJson['name'] + profileDescription = '' + if profileJson.get('summary'): + profileDescription = profileJson['summary'] + outboxUrl = None + if not profileJson.get('outbox'): + if debug: + pprint(profileJson) + print('DEBUG: No outbox found') + return None + outboxUrl = profileJson['outbox'] + profileBackgroundImage = '' + if profileJson.get('image'): + if profileJson['image'].get('url'): + profileBackgroundImage = profileJson['image']['url'] + + profileStyle = profileStyle.replace('image.png', + profileBackgroundImage) + if httpPrefix != 'https': + profileStyle = profileStyle.replace('https://', + httpPrefix + '://') + # url to return to + backUrl = path + if not backUrl.endswith('/inbox'): + backUrl += '/inbox' + + profileDescriptionShort = profileDescription + if '\n' in profileDescription: + if len(profileDescription.split('\n')) > 2: + profileDescriptionShort = '' + else: + if '
' in profileDescription: + if len(profileDescription.split('
')) > 2: + profileDescriptionShort = '' + # keep the profile description short + if len(profileDescriptionShort) > 256: + profileDescriptionShort = '' + # remove formatting from profile description used on title + avatarDescription = '' + if profileJson.get('summary'): + if isinstance(profileJson['summary'], str): + avatarDescription = \ + profileJson['summary'].replace('
', '\n') + avatarDescription = avatarDescription.replace('

', '') + avatarDescription = avatarDescription.replace('

', '') + if '<' in avatarDescription: + avatarDescription = removeHtml(avatarDescription) + profileStr = '
\n' + profileStr += '
\n' + if avatarUrl: + profileStr += \ + ' ' + avatarDescription + '\n' + profileStr += '

' + displayName + '

\n' + profileStr += '

@' + searchNickname + '@' + \ + searchDomainFull + '

\n' + profileStr += '

' + profileDescriptionShort + '

\n' + profileStr += '
\n' + profileStr += '
\n' + profileStr += '
\n' + profileStr += '
\n' + profileStr += '
\n' + profileStr += \ + ' \n' + profileStr += \ + ' \n' + profileStr += \ + ' \n' + profileStr += \ + ' \n' + profileStr += '
\n' + profileStr += '
\n' + profileStr += '
\n' + + iconsDir = getIconsDir(baseDir) + i = 0 + for item in parseUserFeed(session, outboxUrl, asHeader, + projectVersion, httpPrefix, domain): + if not item.get('type'): + continue + if item['type'] != 'Create' and item['type'] != 'Announce': + continue + if not item.get('object'): + continue + profileStr += \ + individualPostAsHtml(True, recentPostsCache, maxRecentPosts, + iconsDir, translate, None, baseDir, + session, cachedWebfingers, personCache, + nickname, domain, port, + item, avatarUrl, False, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, False, False, False, False) + i += 1 + if i >= 20: + break + + return htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter() + + +def htmlProfile(rssIconAtTop: bool, + cssCache: {}, iconsAsButtons: bool, + defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, projectVersion: str, + baseDir: str, httpPrefix: str, authorized: bool, + profileJson: {}, selected: str, + session, wfRequest: {}, personCache: {}, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, extraJson=None, + pageNumber=None, maxItemsPerPage=None) -> str: + """Show the profile page as html + """ + nickname = profileJson['preferredUsername'] + if not nickname: + return "" + domain, port = getDomainFromActor(profileJson['id']) + if not domain: + return "" + displayName = \ + addEmojiToDisplayName(baseDir, httpPrefix, + nickname, domain, + profileJson['name'], True) + domainFull = domain + if port: + domainFull = domain + ':' + str(port) + profileDescription = \ + addEmojiToDisplayName(baseDir, httpPrefix, + nickname, domain, + profileJson['summary'], False) + postsButton = 'button' + followingButton = 'button' + followersButton = 'button' + rolesButton = 'button' + skillsButton = 'button' + sharesButton = 'button' + if selected == 'posts': + postsButton = 'buttonselected' + elif selected == 'following': + followingButton = 'buttonselected' + elif selected == 'followers': + followersButton = 'buttonselected' + elif selected == 'roles': + rolesButton = 'buttonselected' + elif selected == 'skills': + skillsButton = 'buttonselected' + elif selected == 'shares': + sharesButton = 'buttonselected' + loginButton = '' + + followApprovalsSection = '' + followApprovals = False + linkToTimelineStart = '' + linkToTimelineEnd = '' + editProfileStr = '' + logoutStr = '' + actor = profileJson['id'] + usersPath = '/users/' + actor.split('/users/')[1] + + donateSection = '' + donateUrl = getDonationUrl(profileJson) + PGPpubKey = getPGPpubKey(profileJson) + PGPfingerprint = getPGPfingerprint(profileJson) + emailAddress = getEmailAddress(profileJson) + xmppAddress = getXmppAddress(profileJson) + matrixAddress = getMatrixAddress(profileJson) + ssbAddress = getSSBAddress(profileJson) + toxAddress = getToxAddress(profileJson) + if donateUrl or xmppAddress or matrixAddress or \ + ssbAddress or toxAddress or PGPpubKey or \ + PGPfingerprint or emailAddress: + donateSection = '
\n' + donateSection += '
\n' + if donateUrl and not isSystemAccount(nickname): + donateSection += \ + '

\n' + if emailAddress: + donateSection += \ + '

' + translate['Email'] + ': ' + emailAddress + '

\n' + if xmppAddress: + donateSection += \ + '

' + translate['XMPP'] + ': '+xmppAddress + '

\n' + if matrixAddress: + donateSection += \ + '

' + translate['Matrix'] + ': ' + matrixAddress + '

\n' + if ssbAddress: + donateSection += \ + '

SSB:

\n' + if toxAddress: + donateSection += \ + '

Tox:

\n' + if PGPfingerprint: + donateSection += \ + '

PGP: ' + \ + PGPfingerprint.replace('\n', '
') + '

\n' + if PGPpubKey: + donateSection += \ + '

' + PGPpubKey.replace('\n', '
') + '

\n' + donateSection += '
\n' + donateSection += '
\n' + + iconsDir = getIconsDir(baseDir) + if not authorized: + loginButton = headerButtonsFrontScreen(translate, nickname, + 'features', authorized, + iconsAsButtons, iconsDir) + else: + editProfileStr = \ + '' + \ + '| ' + translate['Edit'] + '\n' + + logoutStr = \ + '' + \ + '| ' + translate['Logout'] + \
+            '\n' + + linkToTimelineStart = \ + '' + linkToTimelineStart += \ + '' + linkToTimelineEnd = '' + # are there any follow requests? + followRequestsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/followrequests.txt' + if os.path.isfile(followRequestsFilename): + with open(followRequestsFilename, 'r') as f: + for line in f: + if len(line) > 0: + followApprovals = True + followersButton = 'buttonhighlighted' + if selected == 'followers': + followersButton = 'buttonselectedhighlighted' + break + if selected == 'followers': + if followApprovals: + with open(followRequestsFilename, 'r') as f: + for followerHandle in f: + if len(line) > 0: + if '://' in followerHandle: + followerActor = followerHandle + else: + followerActor = \ + httpPrefix + '://' + \ + followerHandle.split('@')[1] + \ + '/users/' + followerHandle.split('@')[0] + basePath = '/users/' + nickname + followApprovalsSection += '
' + followApprovalsSection += \ + '' + followApprovalsSection += \ + '' + \ + followerHandle + '' + followApprovalsSection += \ + '' + followApprovalsSection += \ + '

' + followApprovalsSection += \ + '' + followApprovalsSection += \ + '' + followApprovalsSection += '
' + + profileDescriptionShort = profileDescription + if '\n' in profileDescription: + if len(profileDescription.split('\n')) > 2: + profileDescriptionShort = '' + else: + if '
' in profileDescription: + if len(profileDescription.split('
')) > 2: + profileDescriptionShort = '' + profileDescription = profileDescription.replace('
', '\n') + # keep the profile description short + if len(profileDescriptionShort) > 256: + profileDescriptionShort = '' + # remove formatting from profile description used on title + avatarDescription = '' + if profileJson.get('summary'): + avatarDescription = profileJson['summary'].replace('
', '\n') + avatarDescription = avatarDescription.replace('

', '') + avatarDescription = avatarDescription.replace('

', '') + + # If this is the news account then show a different banner + if isSystemAccount(nickname): + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + profileHeaderStr = \ + '\n' + if loginButton: + profileHeaderStr += '
' + loginButton + '
\n' + + profileHeaderStr += '\n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileHeaderStr += ' \n' + profileFooterStr += ' \n' + profileFooterStr += ' \n' + profileFooterStr += ' \n' + profileFooterStr += '
\n' + iconsDir = getIconsDir(baseDir) + profileHeaderStr += \ + getLeftColumnContent(baseDir, 'news', domainFull, + httpPrefix, translate, + iconsDir, False, + False, None, rssIconAtTop, True, + True) + profileHeaderStr += ' \n' + else: + profileHeaderStr = '
\n' + profileHeaderStr += '
\n' + profileHeaderStr += \ + ' ' + \
+            avatarDescription + '\n' + profileHeaderStr += '

' + displayName + '

\n' + iconsDir = getIconsDir(baseDir) + profileHeaderStr += \ + '

@' + nickname + '@' + domainFull + '
' + profileHeaderStr += \ + '' + \ + '

\n' + profileHeaderStr += '

' + profileDescriptionShort + '

\n' + profileHeaderStr += loginButton + profileHeaderStr += '
\n' + profileHeaderStr += '
\n' + + profileStr = \ + linkToTimelineStart + profileHeaderStr + \ + linkToTimelineEnd + donateSection + if not isSystemAccount(nickname): + profileStr += '
\n' + profileStr += '
' + profileStr += \ + ' ' + profileStr += \ + ' ' + \ + '' + profileStr += \ + ' ' + \ + '' + profileStr += \ + ' ' + \ + '' + profileStr += \ + ' ' + \ + '' + profileStr += \ + ' ' + \ + '' + profileStr += logoutStr + editProfileStr + profileStr += '
' + profileStr += '
' + + profileStr += followApprovalsSection + + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if profileStyle: + profileStyle = \ + profileStyle.replace('image.png', + profileJson['image']['url']) + if isSystemAccount(nickname): + bannerFile, bannerFilename = \ + getBannerFile(baseDir, nickname, domain) + profileStyle = \ + profileStyle.replace('banner.png', + '/users/' + nickname + '/' + bannerFile) + + licenseStr = \ + '' + \ + '' + \
+            translate['Get the source code'] + '' + + if selected == 'posts': + profileStr += \ + htmlProfilePosts(recentPostsCache, maxRecentPosts, + translate, + baseDir, httpPrefix, authorized, + nickname, domain, port, + session, wfRequest, personCache, + projectVersion, + YTReplacementDomain, + showPublishedDateOnly) + licenseStr + if selected == 'following': + profileStr += \ + htmlProfileFollowing(translate, baseDir, httpPrefix, + authorized, nickname, + domain, port, session, + wfRequest, personCache, extraJson, + projectVersion, ["unfollow"], selected, + usersPath, pageNumber, maxItemsPerPage) + if selected == 'followers': + profileStr += \ + htmlProfileFollowing(translate, baseDir, httpPrefix, + authorized, nickname, + domain, port, session, + wfRequest, personCache, extraJson, + projectVersion, ["block"], + selected, usersPath, pageNumber, + maxItemsPerPage) + if selected == 'roles': + profileStr += \ + htmlProfileRoles(translate, nickname, domainFull, extraJson) + if selected == 'skills': + profileStr += \ + htmlProfileSkills(translate, nickname, domainFull, extraJson) + if selected == 'shares': + profileStr += \ + htmlProfileShares(actor, translate, + nickname, domainFull, + extraJson) + licenseStr + + # Footer which is only used for system accounts + profileFooterStr = '' + if isSystemAccount(nickname): + profileFooterStr = '
\n' + iconsDir = getIconsDir(baseDir) + profileFooterStr += \ + getRightColumnContent(baseDir, 'news', domainFull, + httpPrefix, translate, + iconsDir, False, False, + newswire, False, + False, None, False, False, + False, True, authorized, True) + profileFooterStr += '
\n' + + profileStr = \ + htmlHeader(cssFilename, profileStyle) + \ + profileStr + profileFooterStr + htmlFooter() + return profileStr + + +def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int, + translate: {}, + baseDir: str, httpPrefix: str, + authorized: bool, + nickname: str, domain: str, port: int, + session, wfRequest: {}, personCache: {}, + projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool) -> str: + """Shows posts on the profile screen + These should only be public posts + """ + iconsDir = getIconsDir(baseDir) + separatorStr = htmlPostSeparator(baseDir, None) + profileStr = '' + maxItems = 4 + ctr = 0 + currPage = 1 + while ctr < maxItems and currPage < 4: + outboxFeed = \ + personBoxJson({}, session, baseDir, domain, + port, + '/users/' + nickname + '/outbox?page=' + + str(currPage), + httpPrefix, + 10, 'outbox', + authorized, 0, False, 0) + if not outboxFeed: + break + if len(outboxFeed['orderedItems']) == 0: + break + for item in outboxFeed['orderedItems']: + if item['type'] == 'Create': + postStr = \ + individualPostAsHtml(True, recentPostsCache, + maxRecentPosts, + iconsDir, translate, None, + baseDir, session, wfRequest, + personCache, + nickname, domain, port, item, + None, True, False, + httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, + showPublishedDateOnly, + False, False, False, True, False) + if postStr: + profileStr += separatorStr + postStr + ctr += 1 + if ctr >= maxItems: + break + currPage += 1 + return profileStr + + +def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str, + authorized: bool, + nickname: str, domain: str, port: int, + session, wfRequest: {}, personCache: {}, + followingJson: {}, projectVersion: str, + buttons: [], + feedName: str, actor: str, + pageNumber: int, + maxItemsPerPage: int) -> str: + """Shows following on the profile screen + """ + profileStr = '' + + iconsDir = getIconsDir(baseDir) + if authorized and pageNumber: + if authorized and pageNumber > 1: + # page up arrow + profileStr += \ + '
\n' + \ + ' ' + \
+                translate['Page up'] + '\n' + \ + '
\n' + + for item in followingJson['orderedItems']: + profileStr += \ + individualFollowAsHtml(translate, baseDir, session, + wfRequest, personCache, + domain, item, authorized, nickname, + httpPrefix, projectVersion, + buttons) + if authorized and maxItemsPerPage and pageNumber: + if len(followingJson['orderedItems']) >= maxItemsPerPage: + # page down arrow + profileStr += \ + '
\n' + \ + ' ' + \
+                translate['Page down'] + '\n' + \ + '
\n' + return profileStr + + +def htmlProfileRoles(translate: {}, nickname: str, domain: str, + rolesJson: {}) -> str: + """Shows roles on the profile screen + """ + profileStr = '' + for project, rolesList in rolesJson.items(): + profileStr += \ + '
\n

' + project + \ + '

\n
\n' + for role in rolesList: + profileStr += '

' + role + '

\n' + profileStr += '
\n' + if len(profileStr) == 0: + profileStr += \ + '

@' + nickname + '@' + domain + ' has no roles assigned

\n' + else: + profileStr = '
' + profileStr + '
\n' + return profileStr + + +def htmlProfileSkills(translate: {}, nickname: str, domain: str, + skillsJson: {}) -> str: + """Shows skills on the profile screen + """ + profileStr = '' + for skill, level in skillsJson.items(): + profileStr += \ + '
' + skill + \ + '
\n
\n' + if len(profileStr) > 0: + profileStr = '
' + \ + profileStr + '
\n' + return profileStr + + +def htmlProfileShares(actor: str, translate: {}, + nickname: str, domain: str, sharesJson: {}) -> str: + """Shows shares on the profile screen + """ + profileStr = '' + for item in sharesJson['orderedItems']: + profileStr += htmlIndividualShare(actor, item, translate, False, False) + if len(profileStr) > 0: + profileStr = '\n' + return profileStr + + +def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, + domain: str, port: int, httpPrefix: str, + defaultTimeline: str) -> str: + """Shows the edit profile screen + """ + imageFormats = '.png, .jpg, .jpeg, .gif, .webp, .avif' + path = path.replace('/inbox', '').replace('/outbox', '') + path = path.replace('/shares', '') + nickname = getNicknameFromActor(path) + if not nickname: + return '' + domainFull = domain + if port: + if port != 80 and port != 443: + if ':' not in domain: + domainFull = domain + ':' + str(port) + + actorFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '.json' + if not os.path.isfile(actorFilename): + return '' + + # filename of the banner shown at the top + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + + isBot = '' + isGroup = '' + followDMs = '' + removeTwitter = '' + notifyLikes = '' + hideLikeButton = '' + mediaInstanceStr = '' + blogsInstanceStr = '' + newsInstanceStr = '' + displayNickname = nickname + bioStr = '' + donateUrl = '' + emailAddress = '' + PGPpubKey = '' + PGPfingerprint = '' + xmppAddress = '' + matrixAddress = '' + ssbAddress = '' + blogAddress = '' + toxAddress = '' + manuallyApprovesFollowers = '' + actorJson = loadJson(actorFilename) + if actorJson: + donateUrl = getDonationUrl(actorJson) + xmppAddress = getXmppAddress(actorJson) + matrixAddress = getMatrixAddress(actorJson) + ssbAddress = getSSBAddress(actorJson) + blogAddress = getBlogAddress(actorJson) + toxAddress = getToxAddress(actorJson) + emailAddress = getEmailAddress(actorJson) + PGPpubKey = getPGPpubKey(actorJson) + PGPfingerprint = getPGPfingerprint(actorJson) + if actorJson.get('name'): + displayNickname = actorJson['name'] + if actorJson.get('summary'): + bioStr = \ + actorJson['summary'].replace('

', '').replace('

', '') + if actorJson.get('manuallyApprovesFollowers'): + if actorJson['manuallyApprovesFollowers']: + manuallyApprovesFollowers = 'checked' + else: + manuallyApprovesFollowers = '' + if actorJson.get('type'): + if actorJson['type'] == 'Service': + isBot = 'checked' + isGroup = '' + elif actorJson['type'] == 'Group': + isGroup = 'checked' + isBot = '' + if os.path.isfile(baseDir + '/accounts/' + + nickname + '@' + domain + '/.followDMs'): + followDMs = 'checked' + if os.path.isfile(baseDir + '/accounts/' + + nickname + '@' + domain + '/.removeTwitter'): + removeTwitter = 'checked' + if os.path.isfile(baseDir + '/accounts/' + + nickname + '@' + domain + '/.notifyLikes'): + notifyLikes = 'checked' + if os.path.isfile(baseDir + '/accounts/' + + nickname + '@' + domain + '/.hideLikeButton'): + hideLikeButton = 'checked' + + mediaInstance = getConfigParam(baseDir, "mediaInstance") + if mediaInstance: + if mediaInstance is True: + mediaInstanceStr = 'checked' + blogsInstanceStr = '' + newsInstanceStr = '' + + newsInstance = getConfigParam(baseDir, "newsInstance") + if newsInstance: + if newsInstance is True: + newsInstanceStr = 'checked' + blogsInstanceStr = '' + mediaInstanceStr = '' + + blogsInstance = getConfigParam(baseDir, "blogsInstance") + if blogsInstance: + if blogsInstance is True: + blogsInstanceStr = 'checked' + mediaInstanceStr = '' + newsInstanceStr = '' + + filterStr = '' + filterFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/filters.txt' + if os.path.isfile(filterFilename): + with open(filterFilename, 'r') as filterfile: + filterStr = filterfile.read() + + switchStr = '' + switchFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/replacewords.txt' + if os.path.isfile(switchFilename): + with open(switchFilename, 'r') as switchfile: + switchStr = switchfile.read() + + autoTags = '' + autoTagsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/autotags.txt' + if os.path.isfile(autoTagsFilename): + with open(autoTagsFilename, 'r') as autoTagsFile: + autoTags = autoTagsFile.read() + + autoCW = '' + autoCWFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/autocw.txt' + if os.path.isfile(autoCWFilename): + with open(autoCWFilename, 'r') as autoCWFile: + autoCW = autoCWFile.read() + + blockedStr = '' + blockedFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/blocking.txt' + if os.path.isfile(blockedFilename): + with open(blockedFilename, 'r') as blockedfile: + blockedStr = blockedfile.read() + + allowedInstancesStr = '' + allowedInstancesFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/allowedinstances.txt' + if os.path.isfile(allowedInstancesFilename): + with open(allowedInstancesFilename, 'r') as allowedInstancesFile: + allowedInstancesStr = allowedInstancesFile.read() + + gitProjectsStr = '' + gitProjectsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/gitprojects.txt' + if os.path.isfile(gitProjectsFilename): + with open(gitProjectsFilename, 'r') as gitProjectsFile: + gitProjectsStr = gitProjectsFile.read() + + skills = getSkills(baseDir, nickname, domain) + skillsStr = '' + skillCtr = 1 + if skills: + for skillDesc, skillValue in skills.items(): + skillsStr += \ + '

' + skillsStr += \ + '

' + skillCtr += 1 + + skillsStr += \ + '

' + skillsStr += \ + '

' + + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + editProfileCSS = getCSS(baseDir, cssFilename, cssCache) + if editProfileCSS: + if httpPrefix != 'https': + editProfileCSS = \ + editProfileCSS.replace('https://', httpPrefix + '://') + + moderatorsStr = '' + themesDropdown = '' + instanceStr = '' + + adminNickname = getConfigParam(baseDir, 'admin') + if adminNickname: + if path.startswith('/users/' + adminNickname + '/'): + instanceDescription = \ + getConfigParam(baseDir, 'instanceDescription') + instanceDescriptionShort = \ + getConfigParam(baseDir, 'instanceDescriptionShort') + instanceTitle = \ + getConfigParam(baseDir, 'instanceTitle') + instanceStr += '
' + instanceStr += \ + ' ' + if instanceTitle: + instanceStr += \ + '
' + else: + instanceStr += \ + '
' + instanceStr += \ + ' ' + if instanceDescriptionShort: + instanceStr += \ + '
' + else: + instanceStr += \ + '
' + instanceStr += \ + ' ' + if instanceDescription: + instanceStr += \ + ' ' + else: + instanceStr += \ + ' ' + instanceStr += \ + ' ' + instanceStr += \ + ' ' + instanceStr += '
' + + moderators = '' + moderatorsFile = baseDir + '/accounts/moderators.txt' + if os.path.isfile(moderatorsFile): + with open(moderatorsFile, "r") as f: + moderators = f.read() + moderatorsStr = '
' + moderatorsStr += ' ' + translate['Moderators'] + '
' + moderatorsStr += ' ' + \ + translate['A list of moderator nicknames. One per line.'] + moderatorsStr += \ + ' ' + moderatorsStr += '
' + + editors = '' + editorsFile = baseDir + '/accounts/editors.txt' + if os.path.isfile(editorsFile): + with open(editorsFile, "r") as f: + editors = f.read() + editorsStr = '
' + editorsStr += ' ' + translate['Site Editors'] + '
' + editorsStr += ' ' + \ + translate['A list of editor nicknames. One per line.'] + editorsStr += \ + ' ' + editorsStr += '
' + + themes = getThemesList() + themesDropdown = '
' + themesDropdown += ' ' + translate['Theme'] + '
' + grayscaleFilename = \ + baseDir + '/accounts/.grayscale' + grayscale = '' + if os.path.isfile(grayscaleFilename): + grayscale = 'checked' + themesDropdown += \ + ' ' + translate['Grayscale'] + '
' + themesDropdown += '
' + if os.path.isfile(baseDir + '/fonts/custom.woff') or \ + os.path.isfile(baseDir + '/fonts/custom.woff2') or \ + os.path.isfile(baseDir + '/fonts/custom.otf') or \ + os.path.isfile(baseDir + '/fonts/custom.ttf'): + themesDropdown += \ + ' ' + \ + translate['Remove the custom font'] + '
' + themesDropdown += '
' + themeName = getConfigParam(baseDir, 'theme') + themesDropdown = \ + themesDropdown.replace('
\n' + return tlStr + + +def htmlTimeline(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, + itemsPerPage: int, session, baseDir: str, + wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, timelineJson: {}, + boxName: str, allowDeletion: bool, + httpPrefix: str, projectVersion: str, + manuallyApproveFollowers: bool, + minimal: bool, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, moderator: bool, + editor: bool, + positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the timeline as html + """ + timelineStartTime = time.time() + + accountDir = baseDir + '/accounts/' + nickname + '@' + domain + + # should the calendar icon be highlighted? + newCalendarEvent = False + calendarImage = 'calendar.png' + calendarPath = '/calendar' + calendarFile = accountDir + '/.newCalendar' + if os.path.isfile(calendarFile): + newCalendarEvent = True + calendarImage = 'calendar_notify.png' + with open(calendarFile, 'r') as calfile: + calendarPath = calfile.read().replace('##sent##', '') + calendarPath = calendarPath.replace('\n', '').replace('\r', '') + + # should the DM button be highlighted? + newDM = False + dmFile = accountDir + '/.newDM' + if os.path.isfile(dmFile): + newDM = True + if boxName == 'dm': + os.remove(dmFile) + + # should the Replies button be highlighted? + newReply = False + replyFile = accountDir + '/.newReply' + if os.path.isfile(replyFile): + newReply = True + if boxName == 'tlreplies': + os.remove(replyFile) + + # should the Shares button be highlighted? + newShare = False + newShareFile = accountDir + '/.newShare' + if os.path.isfile(newShareFile): + newShare = True + if boxName == 'tlshares': + os.remove(newShareFile) + + # should the Moderation/reports button be highlighted? + newReport = False + newReportFile = accountDir + '/.newReport' + if os.path.isfile(newReportFile): + newReport = True + if boxName == 'moderation': + os.remove(newReportFile) + + # directory where icons are found + # This changes depending upon theme + iconsDir = getIconsDir(baseDir) + + separatorStr = '' + if boxName != 'tlmedia': + separatorStr = htmlPostSeparator(baseDir, None) + + # the css filename + cssFilename = baseDir + '/epicyon-profile.css' + if os.path.isfile(baseDir + '/epicyon.css'): + cssFilename = baseDir + '/epicyon.css' + + # filename of the banner shown at the top + bannerFile, bannerFilename = getBannerFile(baseDir, nickname, domain) + + # benchmark 1 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff)) + + profileStyle = getCSS(baseDir, cssFilename, cssCache) + if not profileStyle: + print('ERROR: css file not found ' + cssFilename) + return None + + # replace any https within the css with whatever prefix is needed + if httpPrefix != 'https': + profileStyle = \ + profileStyle.replace('https://', + httpPrefix + '://') + + # is the user a moderator? + if not moderator: + moderator = isModerator(baseDir, nickname) + + # is the user a site editor? + if not editor: + editor = isEditor(baseDir, nickname) + + # benchmark 2 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff)) + + # the appearance of buttons - highlighted or not + inboxButton = 'button' + blogsButton = 'button' + newsButton = 'button' + dmButton = 'button' + if newDM: + dmButton = 'buttonhighlighted' + repliesButton = 'button' + if newReply: + repliesButton = 'buttonhighlighted' + mediaButton = 'button' + bookmarksButton = 'button' + eventsButton = 'button' + sentButton = 'button' + sharesButton = 'button' + if newShare: + sharesButton = 'buttonhighlighted' + moderationButton = 'button' + if newReport: + moderationButton = 'buttonhighlighted' + if boxName == 'inbox': + inboxButton = 'buttonselected' + elif boxName == 'tlblogs': + blogsButton = 'buttonselected' + elif boxName == 'tlnews': + newsButton = 'buttonselected' + elif boxName == 'dm': + dmButton = 'buttonselected' + if newDM: + dmButton = 'buttonselectedhighlighted' + elif boxName == 'tlreplies': + repliesButton = 'buttonselected' + if newReply: + repliesButton = 'buttonselectedhighlighted' + elif boxName == 'tlmedia': + mediaButton = 'buttonselected' + elif boxName == 'outbox': + sentButton = 'buttonselected' + elif boxName == 'moderation': + moderationButton = 'buttonselected' + if newReport: + moderationButton = 'buttonselectedhighlighted' + elif boxName == 'tlshares': + sharesButton = 'buttonselected' + if newShare: + sharesButton = 'buttonselectedhighlighted' + elif boxName == 'tlbookmarks' or boxName == 'bookmarks': + bookmarksButton = 'buttonselected' + elif boxName == 'tlevents': + eventsButton = 'buttonselected' + + # get the full domain, including any port number + fullDomain = domain + if port != 80 and port != 443: + if ':' not in domain: + fullDomain = domain + ':' + str(port) + + usersPath = '/users/' + nickname + actor = httpPrefix + '://' + fullDomain + usersPath + + showIndividualPostIcons = True + + # show an icon for new follow approvals + followApprovals = '' + followRequestsFilename = \ + baseDir + '/accounts/' + \ + nickname + '@' + domain + '/followrequests.txt' + if os.path.isfile(followRequestsFilename): + with open(followRequestsFilename, 'r') as f: + for line in f: + if len(line) > 0: + # show follow approvals icon + followApprovals = \ + '' + \ + '' + \
+                        translate['Approve follow requests'] + \
+                        '\n' + break + + # benchmark 3 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff)) + + # moderation / reports button + moderationButtonStr = '' + if moderator and not minimal: + moderationButtonStr = \ + '' + + # shares, bookmarks and events buttons + sharesButtonStr = '' + bookmarksButtonStr = '' + eventsButtonStr = '' + if not minimal: + sharesButtonStr = \ + '' + + bookmarksButtonStr = \ + '' + + eventsButtonStr = \ + '' + + tlStr = htmlHeader(cssFilename, profileStyle) + + # benchmark 4 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff)) + + # if this is a news instance and we are viewing the news timeline + newsHeader = False + if defaultTimeline == 'tlnews' and boxName == 'tlnews': + newsHeader = True + + newPostButtonStr = '' + # start of headericons div + if not newsHeader: + if not iconsAsButtons: + newPostButtonStr += '
' + + # what screen to go to when a new post is created + if boxName == 'dm': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + translate['Create a new DM'] + \
+                '\n' + else: + newPostButtonStr += \ + '' + \ + '' + elif boxName == 'tlblogs' or boxName == 'tlnews': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + \
+                translate['Create a new post'] + \
+                '\n' + else: + newPostButtonStr += \ + '' + \ + '' + elif boxName == 'tlevents': + if not iconsAsButtons: + newPostButtonStr += \ + '| ' + \
+                translate['Create a new event'] + \
+                '\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 += \ + '' + \ + '' + + # This creates a link to the profile page when viewed + # in lynx, but should be invisible in a graphical web browser + tlStr += \ + '\n' + + # banner and row of buttons + tlStr += \ + '\n' + tlStr += '\n' + + if fullWidthTimelineButtonHeader: + tlStr += \ + headerButtonsTimeline(defaultTimeline, boxName, pageNumber, + translate, usersPath, mediaButton, + blogsButton, newsButton, inboxButton, + dmButton, newDM, repliesButton, + newReply, minimal, sentButton, + sharesButtonStr, bookmarksButtonStr, + eventsButtonStr, moderationButtonStr, + newPostButtonStr, baseDir, nickname, + domain, iconsDir, timelineStartTime, + newCalendarEvent, calendarPath, + calendarImage, followApprovals, + iconsAsButtons) + + # start the timeline + tlStr += '\n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + tlStr += ' \n' + + domainFull = domain + if port: + if port != 80 and port != 443: + domainFull = domain + ':' + str(port) + + # left column + leftColumnStr = \ + getLeftColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + editor, False, None, rssIconAtTop, + True, False) + tlStr += ' \n' + # center column containing posts + tlStr += ' \n' + + # right column + rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull, + httpPrefix, translate, iconsDir, + moderator, editor, + newswire, positiveVoting, + False, None, True, + showPublishAsIcon, + rssIconAtTop, publishButtonAtTop, + authorized, True) + tlStr += ' \n' + tlStr += ' \n' + + # benchmark 9 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 9 = ' + str(timeDiff)) + + tlStr += ' \n' + tlStr += '
' + \ + leftColumnStr + ' \n' + + if not fullWidthTimelineButtonHeader: + tlStr += \ + headerButtonsTimeline(defaultTimeline, boxName, pageNumber, + translate, usersPath, mediaButton, + blogsButton, newsButton, inboxButton, + dmButton, newDM, repliesButton, + newReply, minimal, sentButton, + sharesButtonStr, bookmarksButtonStr, + eventsButtonStr, moderationButtonStr, + newPostButtonStr, baseDir, nickname, + domain, iconsDir, timelineStartTime, + newCalendarEvent, calendarPath, + calendarImage, followApprovals, + iconsAsButtons) + + # second row of buttons for moderator actions + if moderator and boxName == 'moderation': + tlStr += \ + '
' + tlStr += '
\n' + idx = 'Nickname or URL. Block using *@domain or nickname@domain' + tlStr += \ + ' ' + translate[idx] + '
\n' + tlStr += '
\n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += \ + ' \n' + tlStr += '
\n
\n' + + # benchmark 6 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 6 = ' + str(timeDiff)) + + if boxName == 'tlshares': + maxSharesPerAccount = itemsPerPage + return (tlStr + + htmlSharesTimeline(translate, pageNumber, itemsPerPage, + baseDir, actor, nickname, domain, port, + maxSharesPerAccount, httpPrefix) + + htmlFooter()) + + # benchmark 7 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff)) + + # benchmark 8 + timeDiff = int((time.time() - timelineStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE TIMING ' + boxName + ' 8 = ' + str(timeDiff)) + + # page up arrow + if pageNumber > 1: + tlStr += \ + '
\n' + \ + ' ' + \
+            translate['Page up'] + '\n' + \ + '
\n' + + # show the posts + itemCtr = 0 + if timelineJson: + # if this is the media timeline then add an extra gallery container + if boxName == 'tlmedia': + if pageNumber > 1: + tlStr += '
' + tlStr += '
\n' + + # show each post in the timeline + for item in timelineJson['orderedItems']: + timelinePostStartTime = time.time() + + if item['type'] == 'Create' or \ + item['type'] == 'Announce' or \ + item['type'] == 'Update': + # is the actor who sent this post snoozed? + if isPersonSnoozed(baseDir, nickname, domain, item['actor']): + continue + + # is the post in the memory cache of recent ones? + currTlStr = None + if boxName != 'tlmedia' and \ + recentPostsCache.get('index'): + postId = \ + removeIdEnding(item['id']).replace('/', '#') + if postId in recentPostsCache['index']: + if not item.get('muted'): + if recentPostsCache['html'].get(postId): + currTlStr = recentPostsCache['html'][postId] + currTlStr = \ + preparePostFromHtmlCache(currTlStr, + boxName, + pageNumber) + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST CACHE TIMING ' + + boxName + ' = ' + str(timeDiff)) + + if not currTlStr: + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST DISK TIMING START ' + + boxName + ' = ' + str(timeDiff)) + + # read the post from disk + currTlStr = \ + individualPostAsHtml(False, recentPostsCache, + maxRecentPosts, + iconsDir, translate, pageNumber, + baseDir, session, wfRequest, + personCache, + nickname, domain, port, + item, None, True, + allowDeletion, + httpPrefix, projectVersion, + boxName, + YTReplacementDomain, + showPublishedDateOnly, + boxName != 'dm', + showIndividualPostIcons, + manuallyApproveFollowers, + False, True) + # benchmark cache post + timeDiff = \ + int((time.time() - + timelinePostStartTime) * 1000) + if timeDiff > 100: + print('TIMELINE POST DISK TIMING ' + + boxName + ' = ' + str(timeDiff)) + + if currTlStr: + itemCtr += 1 + if separatorStr: + tlStr += separatorStr + tlStr += currTlStr + if boxName == 'tlmedia': + tlStr += '
\n' + + # page down arrow + if itemCtr > 2: + tlStr += \ + '
\n' + \ + ' ' + \
+            translate['Page down'] + '\n' + \ + '
\n' + + # end of column-center + tlStr += '
' + \ + rightColumnStr + '
\n' + tlStr += htmlFooter() + return tlStr + + +def htmlShares(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the shares timeline as html + """ + manuallyApproveFollowers = \ + followerApprovalActive(baseDir, nickname, domain) + + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, None, + 'tlshares', allowDeletion, + httpPrefix, projectVersion, manuallyApproveFollowers, + False, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInbox(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the inbox as html + """ + manuallyApproveFollowers = \ + followerApprovalActive(baseDir, nickname, domain) + + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, + 'inbox', allowDeletion, + httpPrefix, projectVersion, manuallyApproveFollowers, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlBookmarks(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, bookmarksJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the bookmarks as html + """ + manuallyApproveFollowers = \ + followerApprovalActive(baseDir, nickname, domain) + + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, bookmarksJson, + 'tlbookmarks', allowDeletion, + httpPrefix, projectVersion, manuallyApproveFollowers, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlEvents(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, bookmarksJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the events as html + """ + manuallyApproveFollowers = \ + followerApprovalActive(baseDir, nickname, domain) + + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, bookmarksJson, + 'tlevents', allowDeletion, + httpPrefix, projectVersion, manuallyApproveFollowers, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInboxDMs(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the DM timeline as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'dm', allowDeletion, + httpPrefix, projectVersion, False, minimal, + YTReplacementDomain, showPublishedDateOnly, + newswire, False, False, positiveVoting, + showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInboxReplies(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the replies timeline as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'tlreplies', + allowDeletion, httpPrefix, projectVersion, False, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInboxMedia(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the media timeline as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'tlmedia', + allowDeletion, httpPrefix, projectVersion, False, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the blogs timeline as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'tlblogs', + allowDeletion, httpPrefix, projectVersion, False, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, False, False, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlInboxNews(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, moderator: bool, editor: bool, + positiveVoting: bool, showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the news timeline as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'tlnews', + allowDeletion, httpPrefix, projectVersion, False, + minimal, YTReplacementDomain, + showPublishedDateOnly, + newswire, moderator, editor, + positiveVoting, showPublishAsIcon, + fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlModeration(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, inboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the moderation feed as html + """ + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, inboxJson, 'moderation', + allowDeletion, httpPrefix, projectVersion, True, False, + YTReplacementDomain, showPublishedDateOnly, + newswire, False, False, positiveVoting, + showPublishAsIcon, fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) + + +def htmlOutbox(cssCache: {}, defaultTimeline: str, + recentPostsCache: {}, maxRecentPosts: int, + translate: {}, pageNumber: int, itemsPerPage: int, + session, baseDir: str, wfRequest: {}, personCache: {}, + nickname: str, domain: str, port: int, outboxJson: {}, + allowDeletion: bool, + httpPrefix: str, projectVersion: str, + minimal: bool, YTReplacementDomain: str, + showPublishedDateOnly: bool, + newswire: {}, positiveVoting: bool, + showPublishAsIcon: bool, + fullWidthTimelineButtonHeader: bool, + iconsAsButtons: bool, + rssIconAtTop: bool, + publishButtonAtTop: bool, + authorized: bool) -> str: + """Show the Outbox as html + """ + manuallyApproveFollowers = \ + followerApprovalActive(baseDir, nickname, domain) + return htmlTimeline(cssCache, defaultTimeline, + recentPostsCache, maxRecentPosts, + translate, pageNumber, + itemsPerPage, session, baseDir, wfRequest, personCache, + nickname, domain, port, outboxJson, 'outbox', + allowDeletion, httpPrefix, projectVersion, + manuallyApproveFollowers, minimal, + YTReplacementDomain, showPublishedDateOnly, + newswire, False, False, positiveVoting, + showPublishAsIcon, fullWidthTimelineButtonHeader, + iconsAsButtons, rssIconAtTop, publishButtonAtTop, + authorized) diff --git a/webapp_utils.py b/webapp_utils.py index d74779d8..2dcf172c 100644 --- a/webapp_utils.py +++ b/webapp_utils.py @@ -740,3 +740,73 @@ def htmlPostSeparator(baseDir: str, column: str) -> str: '' + \ '
\n' return separatorStr + + +def headerButtonsFrontScreen(translate: {}, + nickname: str, boxName: str, + authorized: bool, + iconsAsButtons: bool, + iconsDir: bool) -> str: + """Returns the header buttons for the front page of a news instance + """ + headerStr = '' + if nickname == 'news': + buttonFeatures = 'buttonMobile' + buttonNewswire = 'buttonMobile' + buttonLinks = 'buttonMobile' + if boxName == 'features': + buttonFeatures = 'buttonselected' + elif boxName == 'newswire': + buttonNewswire = 'buttonselected' + elif boxName == 'links': + buttonLinks = 'buttonselected' + + headerStr += \ + ' ' + \ + '' + if not authorized: + headerStr += \ + ' ' + \ + '' + if iconsAsButtons: + headerStr += \ + ' ' + \ + '' + headerStr += \ + ' ' + \ + '' + else: + headerStr += \ + ' ' + \ + '| ' + translate['Newswire'] + '\n' + headerStr += \ + ' ' + \ + '| ' + translate['Links'] + '\n' + else: + if not authorized: + headerStr += \ + ' ' + \ + '' + + if headerStr: + headerStr = \ + '\n
\n' + \ + headerStr + \ + '
\n' + return headerStr