__filename__ = "webapp_utils.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
import os
from collections import OrderedDict
from session import getJson
from utils import isAccountDir
from utils import removeHtml
from utils import getProtocolPrefixes
from utils import loadJson
from utils import getCachedPostFilename
from utils import getConfigParam
from utils import acctDir
from utils import getNicknameFromActor
from utils import isfloat
from utils import getAudioExtensions
from utils import getVideoExtensions
from utils import getImageExtensions
from utils import localActorUrl
from cache import storePersonInCache
from content import addHtmlTags
from content import replaceEmojiFromTags
from person import getPersonAvatarUrl
from posts import isModerator
from blocking import isBlocked
def getBrokenLinkSubstitute() -> str:
"""Returns html used to show a default image if the link to
an image is broken
"""
return " onerror=\"this.onerror=null; this.src='" + \
"/icons/avatar_default.png'\""
def htmlFollowingList(cssCache: {}, baseDir: str,
followingFilename: str) -> str:
"""Returns a list of handles being followed
"""
with open(followingFilename, 'r') as followingFile:
msg = followingFile.read()
followingList = msg.split('\n')
followingList.sort()
if followingList:
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
followingListHtml = htmlHeaderWithExternalStyle(cssFilename,
instanceTitle)
for followingAddress in followingList:
if followingAddress:
followingListHtml += \
'
', '')
# print('TAG: displayName after tags: ' + displayName)
# convert the emoji dictionary to a list
emojiTagsList = []
for tagName, tag in emojiTags.items():
emojiTagsList.append(tag)
# print('TAG: emoji tags list: ' + str(emojiTagsList))
if not inProfileName:
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'post header')
else:
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'profile')
# print('TAG: displayName after tags 2: ' + displayName)
# remove any stray emoji
while ':' in displayName:
if '://' in displayName:
break
emojiStr = displayName.split(':')[1]
prevDisplayName = displayName
displayName = displayName.replace(':' + emojiStr + ':', '').strip()
if prevDisplayName == displayName:
break
# print('TAG: displayName after tags 3: ' + displayName)
# print('TAG: displayName after tag replacements: ' + displayName)
return displayName
def _isImageMimeType(mimeType: str) -> bool:
"""Is the given mime type an image?
"""
if mimeType == 'image/svg+xml':
return True
if not mimeType.startswith('image/'):
return False
extensions = getImageExtensions()
ext = mimeType.split('/')[1]
if ext in extensions:
return True
return False
def _isVideoMimeType(mimeType: str) -> bool:
"""Is the given mime type a video?
"""
if not mimeType.startswith('video/'):
return False
extensions = getVideoExtensions()
ext = mimeType.split('/')[1]
if ext in extensions:
return True
return False
def _isAudioMimeType(mimeType: str) -> bool:
"""Is the given mime type an audio file?
"""
if mimeType == 'audio/mpeg':
return True
if not mimeType.startswith('audio/'):
return False
extensions = getAudioExtensions()
ext = mimeType.split('/')[1]
if ext in extensions:
return True
return False
def _isAttachedImage(attachmentFilename: str) -> bool:
"""Is the given attachment filename an image?
"""
if '.' not in attachmentFilename:
return False
imageExt = (
'png', 'jpg', 'jpeg', 'webp', 'avif', 'svg', 'gif'
)
ext = attachmentFilename.split('.')[-1]
if ext in imageExt:
return True
return False
def _isAttachedVideo(attachmentFilename: str) -> bool:
"""Is the given attachment filename a video?
"""
if '.' not in attachmentFilename:
return False
videoExt = (
'mp4', 'webm', 'ogv'
)
ext = attachmentFilename.split('.')[-1]
if ext in videoExt:
return True
return False
def getPostAttachmentsAsHtml(postJsonObject: {}, boxName: str, translate: {},
isMuted: bool, avatarLink: str,
replyStr: str, announceStr: str, likeStr: str,
bookmarkStr: str, deleteStr: str,
muteStr: str) -> (str, str):
"""Returns a string representing any attachments
"""
attachmentStr = ''
galleryStr = ''
if not postJsonObject['object'].get('attachment'):
return attachmentStr, galleryStr
if not isinstance(postJsonObject['object']['attachment'], list):
return attachmentStr, galleryStr
attachmentCtr = 0
attachmentStr = ''
mediaStyleAdded = False
for attach in postJsonObject['object']['attachment']:
if not (attach.get('mediaType') and attach.get('url')):
continue
mediaType = attach['mediaType']
imageDescription = ''
if attach.get('name'):
imageDescription = attach['name'].replace('"', "'")
if _isImageMimeType(mediaType):
imageUrl = attach['url']
if _isAttachedImage(attach['url']) and 'svg' not in mediaType:
if not attachmentStr:
attachmentStr += '
\n'
mediaStyleAdded = True
if attachmentCtr > 0:
attachmentStr += ' '
if boxName == 'tlmedia':
galleryStr += '
\n'
if not isMuted:
galleryStr += ' \n'
galleryStr += \
' \n'
galleryStr += ' \n'
if postJsonObject['object'].get('url'):
imagePostUrl = postJsonObject['object']['url']
else:
imagePostUrl = postJsonObject['object']['id']
if imageDescription and not isMuted:
galleryStr += \
'
\n'
attachmentCtr += 1
if mediaStyleAdded:
attachmentStr += '
'
return attachmentStr, galleryStr
def htmlPostSeparator(baseDir: str, column: str) -> str:
"""Returns the html for a timeline post separator image
"""
theme = getConfigParam(baseDir, 'theme')
filename = 'separator.png'
separatorClass = "postSeparatorImage"
if column:
separatorClass = "postSeparatorImage" + column.title()
filename = 'separator_' + column + '.png'
separatorImageFilename = baseDir + '/theme/' + theme + '/icons/' + filename
separatorStr = ''
if os.path.isfile(separatorImageFilename):
separatorStr = \
'
' + \
'
\n'
return separatorStr
def htmlHighlightLabel(label: str, highlight: bool) -> str:
"""If the given 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 getAvatarImageUrl(session,
baseDir: str, httpPrefix: str,
postActor: str, personCache: {},
avatarUrl: str, allowDownloads: bool,
signingPrivateKeyPem: str) -> str:
"""Returns the avatar image url
"""
# get the avatar image url for the post actor
if not avatarUrl:
avatarUrl = \
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
avatarUrl = \
updateAvatarImageCache(signingPrivateKeyPem,
session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
else:
updateAvatarImageCache(signingPrivateKeyPem,
session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
if not avatarUrl:
avatarUrl = postActor + '/avatar.png'
return avatarUrl
def htmlHideFromScreenReader(htmlStr: str) -> str:
"""Returns html which is hidden from screen readers
"""
return '' + htmlStr + ''
def htmlKeyboardNavigation(banner: str, links: {}, accessKeys: {},
subHeading: str = None,
usersPath: str = None, translate: {} = None,
followApprovals: bool = False) -> str:
"""Given a set of links return the html for keyboard navigation
"""
htmlStr = '
\n'
if banner:
htmlStr += '
\n' + banner + '\n
\n'
if subHeading:
htmlStr += ' \n'
# show new follower approvals
if usersPath and translate and followApprovals:
htmlStr += '
\n'
# show the list of links
for title, url in links.items():
accessKeyStr = ''
if accessKeys.get(title):
accessKeyStr = 'accesskey="' + accessKeys[title] + '"'
htmlStr += '\n'
htmlStr += '
\n'
return htmlStr
def beginEditSection(label: str) -> str:
"""returns the html for begining a dropdown section on edit profile screen
"""
return \
' ' + label + '\n' + \
'
'
def endEditSection() -> str:
"""returns the html for ending a dropdown section on edit profile screen
"""
return '
\n'
def editTextField(label: str, name: str, value: str = "",
placeholder: str = "", required: bool = False) -> str:
"""Returns html for editing a text field
"""
if value is None:
value = ''
placeholderStr = ''
if placeholder:
placeholderStr = ' placeholder="' + placeholder + '"'
requiredStr = ''
if required:
requiredStr = ' required'
return \
' \n' + \
' \n'
def editNumberField(label: str, name: str, value: int = 1,
minValue: int = 1, maxValue: int = 999999,
placeholder: int = 1) -> str:
"""Returns html for editing an integer number field
"""
if value is None:
value = ''
placeholderStr = ''
if placeholder:
placeholderStr = ' placeholder="' + str(placeholder) + '"'
return \
' \n' + \
' \n'
def editCurrencyField(label: str, name: str, value: str = "0.00",
placeholder: str = "0.00",
required: bool = False) -> str:
"""Returns html for editing a currency field
"""
if value is None:
value = '0.00'
placeholderStr = ''
if placeholder:
if placeholder.isdigit():
placeholderStr = ' placeholder="' + str(placeholder) + '"'
requiredStr = ''
if required:
requiredStr = ' required'
return \
' \n' + \
' \n'
def editCheckBox(label: str, name: str, checked: bool = False) -> str:
"""Returns html for editing a checkbox field
"""
checkedStr = ''
if checked:
checkedStr = ' checked'
return \
' ' + label + ' \n'
def editTextArea(label: str, name: str, value: str = "",
height: int = 600,
placeholder: str = "",
spellcheck: bool = False) -> str:
"""Returns html for editing a textarea field
"""
if value is None:
value = ''
text = ''
if label:
text = ' \n'
text += \
' \n'
return text
def htmlSearchResultShare(baseDir: str, sharedItem: {}, translate: {},
httpPrefix: str, domainFull: str,
contactNickname: str, itemID: str,
actor: str, sharesFileType: str,
category: str) -> str:
"""Returns the html for an individual shared item
"""
sharedItemsForm = '