epicyon/webinterface.py

7906 lines
327 KiB
Python
Raw Normal View History

2020-04-05 09:17:19 +00:00
__filename__ = "webinterface.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.1.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net"
__status__ = "Production"
2019-07-24 22:38:42 +00:00
import time
import os
2020-04-15 09:39:30 +00:00
import urllib.parse
2019-11-02 14:19:51 +00:00
from collections import OrderedDict
2019-07-31 13:11:09 +00:00
from datetime import datetime
2019-10-10 18:25:42 +00:00
from datetime import date
2019-08-21 12:47:41 +00:00
from dateutil.parser import parse
2019-07-24 22:38:42 +00:00
from shutil import copyfile
from pprint import pprint
2019-07-21 22:38:44 +00:00
from person import personBoxJson
2019-11-06 11:39:41 +00:00
from person import isPersonSnoozed
from pgp import getEmailAddress
from pgp import getPGPpubKey
2020-07-06 10:14:41 +00:00
from pgp import getPGPfingerprint
2019-12-17 14:57:16 +00:00
from xmpp import getXmppAddress
2020-02-26 14:35:17 +00:00
from ssb import getSSBAddress
2020-03-22 14:42:26 +00:00
from tox import getToxAddress
2019-12-17 15:25:34 +00:00
from matrix import getMatrixAddress
from donate import getDonationUrl
2020-08-23 11:13:35 +00:00
from utils import removeIdEnding
2020-06-11 12:26:15 +00:00
from utils import getProtocolPrefixes
2020-04-11 12:37:20 +00:00
from utils import searchBoxPosts
2020-08-26 17:41:38 +00:00
from utils import isEventPost
2020-02-24 23:14:49 +00:00
from utils import isBlogPost
from utils import isNewsPost
from utils import updateRecentPostsCache
2019-07-21 12:41:31 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import locatePost
2019-08-08 11:24:26 +00:00
from utils import noOfAccounts
2019-08-10 11:31:42 +00:00
from utils import isPublicPost
from utils import isPublicPostFromUrl
from utils import getDisplayName
2019-10-19 17:50:05 +00:00
from utils import getCachedPostDirectory
from utils import getCachedPostFilename
2019-10-22 11:55:06 +00:00
from utils import loadJson
2020-10-06 08:58:44 +00:00
from utils import getConfigParam
2019-07-29 19:46:30 +00:00
from follow import isFollowingActor
2019-07-30 22:34:04 +00:00
from webfinger import webfingerHandle
2019-08-25 17:22:24 +00:00
from posts import isDM
from posts import getPersonBox
2019-07-30 22:34:04 +00:00
from posts import getUserUrl
from posts import parseUserFeed
from posts import populateRepliesJson
2019-08-12 13:22:17 +00:00
from posts import isModerator
2019-09-28 16:10:45 +00:00
from posts import downloadAnnounce
2019-07-30 22:34:04 +00:00
from session import getJson
2019-07-31 12:44:08 +00:00
from auth import createPassword
2019-08-01 09:05:09 +00:00
from like import likedByPerson
2019-08-30 20:48:52 +00:00
from like import noOfLikes
2019-11-17 14:01:49 +00:00
from bookmarks import bookmarkedByPerson
2019-08-01 12:18:22 +00:00
from announce import announcedByPerson
from blocking import isBlocked
2019-12-13 09:46:46 +00:00
from blocking import isBlockedHashtag
2020-09-14 09:41:44 +00:00
from content import htmlReplaceEmailQuote
2020-08-02 17:27:56 +00:00
from content import htmlReplaceQuoteMarks
from content import removeTextFormatting
2020-02-19 18:51:08 +00:00
from content import switchWords
2019-08-05 19:13:15 +00:00
from content import getMentionsFromHtml
2019-09-23 10:05:16 +00:00
from content import addHtmlTags
2019-09-29 16:28:02 +00:00
from content import replaceEmojiFromTags
2019-11-04 20:39:14 +00:00
from content import removeLongWords
from content import removeHtml
2019-08-09 08:46:38 +00:00
from skills import getSkills
2019-08-18 13:35:33 +00:00
from cache import getPersonFromCache
2019-09-14 18:17:09 +00:00
from cache import storePersonInCache
2019-11-03 09:48:01 +00:00
from shares import getValidSharedItemID
from happening import todaysEventsCheck
from happening import thisWeeksEventsCheck
from happening import getCalendarEvents
2020-02-23 10:20:10 +00:00
from happening import getTodaysEvents
2020-05-02 19:24:17 +00:00
from git import isGitPatch
2020-05-28 09:11:21 +00:00
from theme import getThemesList
2020-06-29 16:25:28 +00:00
from petnames import getPetName
2020-07-03 19:20:31 +00:00
from followingCalendar import receivingCalendarEvents
2020-08-06 20:16:42 +00:00
from devices import E2EEdecryptMessageFromDevice
2019-07-20 21:13:36 +00:00
2020-04-05 09:17:19 +00:00
2020-07-11 20:44:20 +00:00
def getAltPath(actor: str, domainFull: str, callingDomain: str) -> str:
"""Returns alternate path from the actor
eg. https://clearnetdomain/path becomes http://oniondomain/path
2020-07-11 20:17:55 +00:00
"""
postActor = actor
if callingDomain not in actor and domainFull in actor:
if callingDomain.endswith('.onion') or \
callingDomain.endswith('.i2p'):
postActor = \
'http://' + callingDomain + actor.split(domainFull)[1]
print('Changed POST domain from ' + actor + ' to ' + postActor)
return postActor
def getContentWarningButton(postID: str, translate: {},
content: str) -> str:
"""Returns the markup for a content warning button
"""
2020-09-30 21:13:39 +00:00
return ' <details><summary><b>' + \
2020-06-26 17:03:25 +00:00
translate['SHOW MORE'] + '</b></summary>' + \
2020-06-25 12:58:12 +00:00
'<div id="' + postID + '">' + content + \
2020-09-30 20:41:33 +00:00
'</div></details>\n'
def getBlogAddress(actorJson: {}) -> str:
"""Returns blog address for the given actor
"""
if not actorJson.get('attachment'):
return ''
for propertyValue in actorJson['attachment']:
if not propertyValue.get('name'):
continue
if not propertyValue['name'].lower().startswith('blog'):
continue
if not propertyValue.get('type'):
continue
if not propertyValue.get('value'):
continue
if propertyValue['type'] != 'PropertyValue':
continue
propertyValue['value'] = propertyValue['value'].strip()
2020-06-11 12:26:15 +00:00
prefixes = getProtocolPrefixes()
2020-06-11 12:16:45 +00:00
prefixFound = False
for prefix in prefixes:
if propertyValue['value'].startswith(prefix):
prefixFound = True
break
if not prefixFound:
continue
if '.' not in propertyValue['value']:
continue
if ' ' in propertyValue['value']:
continue
if ',' in propertyValue['value']:
continue
return propertyValue['value']
return ''
def setBlogAddress(actorJson: {}, blogAddress: str) -> None:
"""Sets an blog address for the given actor
"""
if not actorJson.get('attachment'):
actorJson['attachment'] = []
# remove any existing value
propertyFound = None
for propertyValue in actorJson['attachment']:
if not propertyValue.get('name'):
continue
if not propertyValue.get('type'):
continue
if not propertyValue['name'].lower().startswith('blog'):
continue
propertyFound = propertyValue
break
if propertyFound:
actorJson['attachment'].remove(propertyFound)
2020-06-11 12:26:15 +00:00
prefixes = getProtocolPrefixes()
2020-06-11 12:16:45 +00:00
prefixFound = False
for prefix in prefixes:
if blogAddress.startswith(prefix):
prefixFound = True
break
if not prefixFound:
return
if '.' not in blogAddress:
return
if ' ' in blogAddress:
return
if ',' in blogAddress:
return
for propertyValue in actorJson['attachment']:
if not propertyValue.get('name'):
continue
if not propertyValue.get('type'):
continue
if not propertyValue['name'].lower().startswith('blog'):
continue
if propertyValue['type'] != 'PropertyValue':
continue
propertyValue['value'] = blogAddress
return
newBlogAddress = {
"name": "Blog",
"type": "PropertyValue",
"value": blogAddress
}
actorJson['attachment'].append(newBlogAddress)
2020-04-05 09:17:19 +00:00
def updateAvatarImageCache(session, baseDir: str, httpPrefix: str,
actor: str, avatarUrl: str,
personCache: {}, allowDownloads: bool,
force=False) -> str:
2019-09-14 17:12:03 +00:00
"""Updates the cached avatar for the given actor
"""
if not avatarUrl:
return None
2020-04-05 09:17:19 +00:00
actorStr = actor.replace('/', '-')
avatarImagePath = baseDir + '/cache/avatars/' + actorStr
if avatarUrl.endswith('.png') or \
'.png?' in avatarUrl:
sessionHeaders = {
2020-03-22 20:36:19 +00:00
'Accept': 'image/png'
}
2020-04-05 09:17:19 +00:00
avatarImageFilename = avatarImagePath + '.png'
elif (avatarUrl.endswith('.jpg') or
avatarUrl.endswith('.jpeg') or
'.jpg?' in avatarUrl or
'.jpeg?' in avatarUrl):
sessionHeaders = {
2020-03-22 20:36:19 +00:00
'Accept': 'image/jpeg'
}
2020-04-05 09:17:19 +00:00
avatarImageFilename = avatarImagePath + '.jpg'
elif avatarUrl.endswith('.gif') or '.gif?' in avatarUrl:
2020-04-05 09:17:19 +00:00
sessionHeaders = {
2020-03-22 20:36:19 +00:00
'Accept': 'image/gif'
}
2020-04-05 09:17:19 +00:00
avatarImageFilename = avatarImagePath + '.gif'
2019-11-14 15:11:20 +00:00
elif avatarUrl.endswith('.webp') or '.webp?' in avatarUrl:
2020-04-05 09:17:19 +00:00
sessionHeaders = {
2020-03-22 20:36:19 +00:00
'Accept': 'image/webp'
}
2020-04-05 09:17:19 +00:00
avatarImageFilename = avatarImagePath + '.webp'
elif avatarUrl.endswith('.avif') or '.avif?' in avatarUrl:
sessionHeaders = {
'Accept': 'image/avif'
}
avatarImageFilename = avatarImagePath + '.avif'
2019-09-14 17:16:03 +00:00
else:
return None
if (not os.path.isfile(avatarImageFilename) or force) and allowDownloads:
2019-09-14 17:12:03 +00:00
try:
2020-04-05 09:17:19 +00:00
print('avatar image url: ' + avatarUrl)
result = session.get(avatarUrl,
headers=sessionHeaders,
params=None)
if result.status_code < 200 or \
result.status_code > 202:
print('Avatar image download failed with status ' +
2020-02-23 15:32:47 +00:00
str(result.status_code))
2019-09-14 19:39:51 +00:00
# remove partial download
if os.path.isfile(avatarImageFilename):
os.remove(avatarImageFilename)
else:
with open(avatarImageFilename, 'wb') as f:
f.write(result.content)
2020-04-05 09:17:19 +00:00
print('avatar image downloaded for ' + actor)
return avatarImageFilename.replace(baseDir + '/cache', '')
2020-03-22 21:16:02 +00:00
except Exception as e:
2020-04-05 09:17:19 +00:00
print('Failed to download avatar image: ' + str(avatarUrl))
2019-09-14 17:12:03 +00:00
print(e)
2020-04-05 09:17:19 +00:00
prof = 'https://www.w3.org/ns/activitystreams'
2020-08-13 16:19:35 +00:00
if '/channel/' not in actor or '/accounts/' not in actor:
2020-04-05 09:17:19 +00:00
sessionHeaders = {
'Accept': 'application/activity+json; profile="' + prof + '"'
2020-02-23 15:32:47 +00:00
}
2019-10-18 12:51:37 +00:00
else:
2020-04-05 09:17:19 +00:00
sessionHeaders = {
'Accept': 'application/ld+json; profile="' + prof + '"'
2020-02-23 15:32:47 +00:00
}
2020-04-05 09:17:19 +00:00
personJson = \
getJson(session, actor, sessionHeaders, None, __version__,
httpPrefix, None)
2019-09-14 17:12:03 +00:00
if personJson:
2019-09-14 19:06:08 +00:00
if not personJson.get('id'):
return None
if not personJson.get('publicKey'):
return None
if not personJson['publicKey'].get('publicKeyPem'):
return None
2020-04-05 09:17:19 +00:00
if personJson['id'] != actor:
2019-09-14 19:06:08 +00:00
return None
if not personCache.get(actor):
return None
2020-04-05 09:17:19 +00:00
if personCache[actor]['actor']['publicKey']['publicKeyPem'] != \
2020-02-23 15:32:47 +00:00
personJson['publicKey']['publicKeyPem']:
2020-04-05 09:17:19 +00:00
print("ERROR: " +
"public keys don't match when downloading actor for " +
actor)
2019-09-14 19:06:08 +00:00
return None
storePersonInCache(baseDir, actor, personJson, personCache,
allowDownloads)
return getPersonAvatarUrl(baseDir, actor, personCache,
allowDownloads)
2019-09-14 17:52:55 +00:00
return None
2020-04-05 09:17:19 +00:00
return avatarImageFilename.replace(baseDir + '/cache', '')
2019-09-14 17:12:03 +00:00
def getPersonAvatarUrl(baseDir: str, personUrl: str, personCache: {},
allowDownloads: bool) -> str:
2019-08-18 13:30:40 +00:00
"""Returns the avatar url for the person
"""
personJson = \
getPersonFromCache(baseDir, personUrl, personCache, allowDownloads)
2019-10-31 20:46:37 +00:00
if not personJson:
return None
2020-08-29 09:09:15 +00:00
2019-10-31 20:46:37 +00:00
# get from locally stored image
2020-04-05 09:17:19 +00:00
actorStr = personJson['id'].replace('/', '-')
2020-05-04 18:33:13 +00:00
avatarImagePath = baseDir + '/cache/avatars/' + actorStr
2020-08-29 19:54:30 +00:00
imageExtension = ('png', 'jpg', 'jpeg', 'gif', 'webp', 'avif')
2020-08-29 19:54:30 +00:00
for ext in imageExtension:
if os.path.isfile(avatarImagePath + '.' + ext):
return '/avatars/' + actorStr + '.' + ext
elif os.path.isfile(avatarImagePath.lower() + '.' + ext):
return '/avatars/' + actorStr.lower() + '.' + ext
2020-03-22 21:16:02 +00:00
2019-10-31 20:46:37 +00:00
if personJson.get('icon'):
if personJson['icon'].get('url'):
return personJson['icon']['url']
2019-08-18 13:30:40 +00:00
return None
2020-04-05 09:17:19 +00:00
2020-06-28 21:54:49 +00:00
def htmlFollowingList(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'
with open(cssFilename, 'r') as cssFile:
profileCSS = cssFile.read()
followingListHtml = htmlHeader(cssFilename, profileCSS)
for followingAddress in followingList:
if followingAddress:
followingListHtml += \
2020-06-28 22:02:45 +00:00
'<h3>@' + followingAddress + '</h3>'
2020-06-28 21:54:49 +00:00
followingListHtml += htmlFooter()
msg = followingListHtml
return msg
return ''
2020-06-29 09:48:46 +00:00
def htmlFollowingDataList(baseDir: str, nickname: str,
domain: str, domainFull: str) -> str:
2020-06-29 09:34:22 +00:00
"""Returns a datalist of handles being followed
"""
listStr = '<datalist id="followingHandles">\n'
followingFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/following.txt'
if os.path.isfile(followingFilename):
with open(followingFilename, 'r') as followingFile:
msg = followingFile.read()
2020-06-29 15:23:52 +00:00
# add your own handle, so that you can send DMs
# to yourself as reminders
2020-06-29 09:48:46 +00:00
msg += nickname + '@' + domainFull + '\n'
2020-06-29 15:23:52 +00:00
# include petnames
petnamesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/petnames.txt'
if os.path.isfile(petnamesFilename):
followingList = []
with open(petnamesFilename, 'r') as petnamesFile:
petStr = petnamesFile.read()
# extract each petname and append it
petnamesList = petStr.split('\n')
for pet in petnamesList:
followingList.append(pet.split(' ')[0])
# add the following.txt entries
followingList += msg.split('\n')
else:
# no petnames list exists - just use following.txt
followingList = msg.split('\n')
2020-06-29 09:34:22 +00:00
followingList.sort()
if followingList:
for followingAddress in followingList:
if followingAddress:
listStr += \
'<option>@' + followingAddress + '</option>\n'
listStr += '</datalist>\n'
return listStr
2020-04-05 09:17:19 +00:00
def htmlSearchEmoji(translate: {}, baseDir: str, httpPrefix: str,
2020-02-23 15:32:47 +00:00
searchStr: str) -> str:
2019-08-19 19:02:28 +00:00
"""Search results for emoji
"""
2019-11-03 14:46:30 +00:00
# emoji.json is generated so that it can be customized and the changes
2020-03-22 21:16:02 +00:00
# will be retained even if default_emoji.json is subsequently updated
2020-04-05 09:17:19 +00:00
if not os.path.isfile(baseDir + '/emoji/emoji.json'):
copyfile(baseDir + '/emoji/default_emoji.json',
baseDir + '/emoji/emoji.json')
2020-05-22 11:32:38 +00:00
searchStr = searchStr.lower().replace(':', '').strip('\n').strip('\r')
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
emojiCSS = cssFile.read()
if httpPrefix != 'https':
emojiCSS = emojiCSS.replace('https://',
httpPrefix + '://')
emojiLookupFilename = baseDir + '/emoji/emoji.json'
2019-08-19 19:02:28 +00:00
# create header
2020-04-05 09:17:19 +00:00
emojiForm = htmlHeader(cssFilename, emojiCSS)
emojiForm += '<center><h1>' + \
translate['Emoji Search'] + \
'</h1></center>'
2019-08-19 19:02:28 +00:00
# does the lookup file exist?
if not os.path.isfile(emojiLookupFilename):
2020-04-05 09:17:19 +00:00
emojiForm += '<center><h5>' + \
translate['No results'] + '</h5></center>'
emojiForm += htmlFooter()
2019-08-19 19:02:28 +00:00
return emojiForm
2019-10-22 11:55:06 +00:00
2020-04-05 09:17:19 +00:00
emojiJson = loadJson(emojiLookupFilename)
2019-10-22 11:55:06 +00:00
if emojiJson:
2020-04-05 09:17:19 +00:00
results = {}
for emojiName, filename in emojiJson.items():
2019-08-19 19:10:55 +00:00
if searchStr in emojiName:
2020-04-05 09:17:19 +00:00
results[emojiName] = filename + '.png'
for emojiName, filename in emojiJson.items():
2019-08-19 19:10:55 +00:00
if emojiName in searchStr:
2020-04-05 09:17:19 +00:00
results[emojiName] = filename + '.png'
headingShown = False
emojiForm += '<center>'
msgStr1 = translate['Copy the text then paste it into your post']
msgStr2 = ':<img loading="lazy" class="searchEmoji" src="/emoji/'
for emojiName, filename in results.items():
if os.path.isfile(baseDir + '/emoji/' + filename):
2019-08-19 21:18:04 +00:00
if not headingShown:
2020-04-05 09:17:19 +00:00
emojiForm += \
'<center><h5>' + msgStr1 + \
2020-02-23 15:32:47 +00:00
'</h5></center>'
2020-04-05 09:17:19 +00:00
headingShown = True
emojiForm += \
'<h3>:' + emojiName + msgStr2 + \
filename + '"/></h3>'
emojiForm += '</center>'
emojiForm += htmlFooter()
2019-08-19 19:02:28 +00:00
return emojiForm
2020-04-05 09:17:19 +00:00
def getIconsDir(baseDir: str) -> str:
"""Returns the directory where icons exist
"""
2020-04-05 09:17:19 +00:00
iconsDir = 'icons'
theme = getConfigParam(baseDir, 'theme')
if theme:
2020-04-05 09:17:19 +00:00
if os.path.isdir(baseDir + '/img/icons/' + theme):
iconsDir = 'icons/' + theme
return iconsDir
2020-04-05 09:17:19 +00:00
def htmlSearchSharedItems(translate: {},
baseDir: str, searchStr: str,
pageNumber: int,
resultsPerPage: int,
httpPrefix: str,
2020-07-11 20:31:25 +00:00
domainFull: str, actor: str,
callingDomain: str) -> str:
2019-08-14 09:45:51 +00:00
"""Search results for shared items
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
currPage = 1
ctr = 0
sharedItemsForm = ''
2020-04-15 11:10:30 +00:00
searchStrLower = urllib.parse.unquote(searchStr)
2020-05-22 11:32:38 +00:00
searchStrLower = searchStrLower.lower().strip('\n').strip('\r')
2020-04-05 09:17:19 +00:00
searchStrLowerList = searchStrLower.split('+')
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-12-10 14:48:08 +00:00
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
sharedItemsCSS = cssFile.read()
if httpPrefix != 'https':
sharedItemsCSS = \
sharedItemsCSS.replace('https://',
httpPrefix + '://')
sharedItemsForm = htmlHeader(cssFilename, sharedItemsCSS)
sharedItemsForm += \
'<center><h1>' + translate['Shared Items Search'] + \
'</h1></center>'
resultsExist = False
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2019-08-13 21:32:18 +00:00
for handle in dirs:
if '@' not in handle:
continue
2020-04-05 09:17:19 +00:00
contactNickname = handle.split('@')[0]
sharesFilename = baseDir + '/accounts/' + handle + \
'/shares.json'
2019-08-13 21:32:18 +00:00
if not os.path.isfile(sharesFilename):
continue
2020-04-05 09:17:19 +00:00
sharesJson = loadJson(sharesFilename)
if not sharesJson:
continue
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
for name, sharedItem in sharesJson.items():
matched = True
2019-08-13 22:11:11 +00:00
for searchSubstr in searchStrLowerList:
2020-04-05 09:17:19 +00:00
subStrMatched = False
searchSubstr = searchSubstr.strip()
2019-08-13 22:11:11 +00:00
if searchSubstr in sharedItem['location'].lower():
2020-04-05 09:17:19 +00:00
subStrMatched = True
2019-08-13 22:11:11 +00:00
elif searchSubstr in sharedItem['summary'].lower():
2020-04-05 09:17:19 +00:00
subStrMatched = True
2019-08-13 22:11:11 +00:00
elif searchSubstr in sharedItem['displayName'].lower():
2020-04-05 09:17:19 +00:00
subStrMatched = True
2019-08-13 22:11:11 +00:00
elif searchSubstr in sharedItem['category'].lower():
2020-04-05 09:17:19 +00:00
subStrMatched = True
2019-08-13 22:11:11 +00:00
if not subStrMatched:
2020-04-05 09:17:19 +00:00
matched = False
2019-08-13 22:11:11 +00:00
break
2019-08-13 21:32:18 +00:00
if matched:
2020-04-05 09:17:19 +00:00
if currPage == pageNumber:
2020-09-19 09:44:22 +00:00
sharedItemsForm += '<div class="container">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
'<p class="share-title">' + \
2020-09-19 09:44:22 +00:00
sharedItem['displayName'] + '</p>\n'
2019-08-25 21:16:38 +00:00
if sharedItem.get('imageUrl'):
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
'<a href="' + \
2020-09-19 09:44:22 +00:00
sharedItem['imageUrl'] + '">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
'<img loading="lazy" src="' + \
sharedItem['imageUrl'] + \
2020-09-19 09:44:22 +00:00
'" alt="Item image"></a>\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-09-19 09:44:22 +00:00
'<p>' + sharedItem['summary'] + '</p>\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
'<p><b>' + translate['Type'] + \
':</b> ' + sharedItem['itemType'] + ' '
sharedItemsForm += \
'<b>' + translate['Category'] + \
':</b> ' + sharedItem['category'] + ' '
sharedItemsForm += \
'<b>' + translate['Location'] + \
2020-09-19 09:44:22 +00:00
':</b> ' + sharedItem['location'] + '</p>\n'
2020-04-05 09:17:19 +00:00
contactActor = \
httpPrefix + '://' + domainFull + \
'/users/' + contactNickname
sharedItemsForm += \
'<p><a href="' + actor + \
'?replydm=sharedesc:' + \
sharedItem['displayName'] + \
'?mention=' + contactActor + \
'"><button class="button">' + \
2020-09-19 09:44:22 +00:00
translate['Contact'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
if actor.endswith('/users/' + contactNickname):
sharedItemsForm += \
' <a href="' + actor + '?rmshare=' + \
name + '"><button class="button">' + \
2020-09-19 09:44:22 +00:00
translate['Remove'] + '</button></a>\n'
sharedItemsForm += '</p></div>\n'
2020-04-05 09:17:19 +00:00
if not resultsExist and currPage > 1:
2020-07-11 20:31:25 +00:00
postActor = \
2020-07-11 20:44:20 +00:00
getAltPath(actor, domainFull,
2020-07-11 20:31:25 +00:00
callingDomain)
2019-08-14 09:45:51 +00:00
# previous page link, needs to be a POST
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-07-11 20:31:25 +00:00
'<form method="POST" action="' + \
postActor + \
2020-04-05 09:17:19 +00:00
'/searchhandle?page=' + \
2020-09-19 09:44:22 +00:00
str(pageNumber - 1) + '">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <input type="hidden" ' + \
2020-09-19 09:44:22 +00:00
'name="actor" value="' + actor + '">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <input type="hidden" ' + \
'name="searchtext" value="' + \
2020-09-19 09:44:22 +00:00
searchStrLower + '"><br>\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + \
2020-09-19 09:44:22 +00:00
'" type="submit" name="submitSearch">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <img loading="lazy" ' + \
'class="pageicon" src="/' + iconsDir + \
'/pageup.png" title="' + \
translate['Page up'] + \
'" alt="' + translate['Page up'] + \
2020-09-19 09:44:22 +00:00
'"/></a>\n'
2020-10-01 13:01:49 +00:00
sharedItemsForm += ' </center>\n'
2020-09-19 09:44:22 +00:00
sharedItemsForm += '</form>\n'
2020-04-05 09:17:19 +00:00
resultsExist = True
ctr += 1
if ctr >= resultsPerPage:
currPage += 1
if currPage > pageNumber:
2020-07-11 20:31:25 +00:00
postActor = \
2020-07-11 20:44:20 +00:00
getAltPath(actor, domainFull,
2020-07-11 20:31:25 +00:00
callingDomain)
2019-08-14 09:45:51 +00:00
# next page link, needs to be a POST
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-07-11 20:31:25 +00:00
'<form method="POST" action="' + \
postActor + \
2020-04-05 09:17:19 +00:00
'/searchhandle?page=' + \
2020-09-19 09:44:22 +00:00
str(pageNumber + 1) + '">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <input type="hidden" ' + \
2020-09-19 09:44:22 +00:00
'name="actor" value="' + actor + '">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <input type="hidden" ' + \
'name="searchtext" value="' + \
2020-09-19 09:44:22 +00:00
searchStrLower + '"><br>\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + \
2020-09-19 09:44:22 +00:00
'" type="submit" name="submitSearch">\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
' <img loading="lazy" ' + \
'class="pageicon" src="/' + iconsDir + \
'/pagedown.png" title="' + \
translate['Page down'] + \
'" alt="' + translate['Page down'] + \
2020-09-19 09:44:22 +00:00
'"/></a>\n'
2020-10-01 13:01:49 +00:00
sharedItemsForm += ' </center>\n'
2020-09-19 09:44:22 +00:00
sharedItemsForm += '</form>\n'
2019-08-14 09:45:51 +00:00
break
2020-04-05 09:17:19 +00:00
ctr = 0
2019-08-13 21:32:18 +00:00
if not resultsExist:
2020-04-05 09:17:19 +00:00
sharedItemsForm += \
2020-09-19 09:44:22 +00:00
'<center><h5>' + translate['No results'] + '</h5></center>\n'
2020-04-05 09:17:19 +00:00
sharedItemsForm += htmlFooter()
2020-03-22 21:16:02 +00:00
return sharedItemsForm
2019-08-13 21:32:18 +00:00
2019-08-13 17:25:39 +00:00
2020-04-05 09:17:19 +00:00
def htmlModerationInfo(translate: {}, baseDir: str, httpPrefix: str) -> str:
msgStr1 = \
'These are globally blocked for all accounts on this instance'
msgStr2 = \
'Any blocks or suspensions made by moderators will be shown here.'
infoForm = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
with open(cssFilename, 'r') as cssFile:
infoCSS = cssFile.read()
if httpPrefix != 'https':
infoCSS = infoCSS.replace('https://',
httpPrefix + '://')
infoForm = htmlHeader(cssFilename, infoCSS)
infoForm += \
'<center><h1>' + \
translate['Moderation Information'] + \
'</h1></center>'
infoShown = False
suspendedFilename = baseDir + '/accounts/suspended.txt'
2019-08-13 17:25:39 +00:00
if os.path.isfile(suspendedFilename):
with open(suspendedFilename, "r") as f:
2020-04-05 09:17:19 +00:00
suspendedStr = f.read()
infoForm += '<div class="container">'
infoForm += ' <br><b>' + \
translate['Suspended accounts'] + '</b>'
infoForm += ' <br>' + \
translate['These are currently suspended']
infoForm += \
' <textarea id="message" ' + \
'name="suspended" style="height:200px">' + \
suspendedStr + '</textarea>'
infoForm += '</div>'
infoShown = True
blockingFilename = baseDir + '/accounts/blocking.txt'
2019-08-13 17:25:39 +00:00
if os.path.isfile(blockingFilename):
with open(blockingFilename, "r") as f:
2020-04-05 09:17:19 +00:00
blockedStr = f.read()
infoForm += '<div class="container">'
infoForm += \
' <br><b>' + \
translate['Blocked accounts and hashtags'] + '</b>'
infoForm += \
' <br>' + \
translate[msgStr1]
infoForm += \
' <textarea id="message" ' + \
2020-10-05 16:32:23 +00:00
'name="blocked" style="height:700px">' + \
2020-04-05 09:17:19 +00:00
blockedStr + '</textarea>'
infoForm += '</div>'
infoShown = True
2019-08-13 17:25:39 +00:00
if not infoShown:
2020-04-05 09:17:19 +00:00
infoForm += \
'<center><p>' + \
translate[msgStr2] + \
2020-02-23 15:32:47 +00:00
'</p></center>'
2020-04-05 09:17:19 +00:00
infoForm += htmlFooter()
2020-03-22 21:16:02 +00:00
return infoForm
2019-08-12 13:22:17 +00:00
2020-04-05 09:17:19 +00:00
def htmlHashtagSearch(nickname: str, domain: str, port: int,
recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, hashtag: str, pageNumber: int,
postsPerPage: int,
session, wfRequest: {}, personCache: {},
httpPrefix: str, projectVersion: str,
YTReplacementDomain: str) -> str:
2019-08-10 10:54:52 +00:00
"""Show a page containing search results for a hashtag
"""
if hashtag.startswith('#'):
2020-04-05 09:17:19 +00:00
hashtag = hashtag[1:]
2020-04-15 09:39:30 +00:00
hashtag = urllib.parse.unquote(hashtag)
2020-04-05 09:17:19 +00:00
hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt'
2020-05-31 17:36:04 +00:00
if not os.path.isfile(hashtagIndexFile):
if hashtag != hashtag.lower():
hashtag = hashtag.lower()
hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt'
2019-08-10 10:54:52 +00:00
if not os.path.isfile(hashtagIndexFile):
2020-04-15 09:23:44 +00:00
print('WARN: hashtag file not found ' + hashtagIndexFile)
2019-08-10 10:54:52 +00:00
return None
2020-04-15 09:23:44 +00:00
iconsDir = getIconsDir(baseDir)
2019-12-13 10:33:33 +00:00
# check that the directory for the nickname exists
if nickname:
2020-04-05 09:17:19 +00:00
if not os.path.isdir(baseDir + '/accounts/' +
nickname + '@' + domain):
nickname = None
2019-12-13 10:33:33 +00:00
2019-08-10 10:54:52 +00:00
# read the index
with open(hashtagIndexFile, "r") as f:
2020-04-05 09:17:19 +00:00
lines = f.readlines()
2019-12-17 09:58:22 +00:00
# read the css
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
hashtagSearchCSS = cssFile.read()
if httpPrefix != 'https':
hashtagSearchCSS = \
hashtagSearchCSS.replace('https://',
httpPrefix + '://')
2019-08-10 10:54:52 +00:00
2019-12-17 09:58:22 +00:00
# ensure that the page number is in bounds
if not pageNumber:
2020-04-05 09:17:19 +00:00
pageNumber = 1
elif pageNumber < 1:
pageNumber = 1
2019-12-17 09:58:22 +00:00
# get the start end end within the index file
2020-04-05 09:17:19 +00:00
startIndex = int((pageNumber - 1) * postsPerPage)
endIndex = startIndex + postsPerPage
noOfLines = len(lines)
if endIndex >= noOfLines and noOfLines > 0:
endIndex = noOfLines - 1
2019-12-17 09:58:22 +00:00
# add the page title
2020-04-05 09:17:19 +00:00
hashtagSearchForm = htmlHeader(cssFilename, hashtagSearchCSS)
2020-08-12 12:55:45 +00:00
if nickname:
hashtagSearchForm += '<center>\n' + \
'<h1><a href="/users/' + nickname + '/search">#' + \
hashtag + '</a></h1>\n' + '</center>\n'
else:
hashtagSearchForm += '<center>\n' + \
'<h1>#' + hashtag + '</h1>\n' + '</center>\n'
2019-12-17 09:58:22 +00:00
2020-09-26 19:14:04 +00:00
# RSS link for hashtag feed
hashtagSearchForm += '<center><a href="/tags/rss2/' + hashtag + '">'
2020-09-26 19:16:14 +00:00
hashtagSearchForm += \
2020-09-26 19:51:22 +00:00
'<img style="width:3%;min-width:50px" ' + \
'loading="lazy" alt="RSS 2.0" ' + \
2020-09-26 19:14:04 +00:00
'title="RSS 2.0" src="/' + \
iconsDir + '/rss.png" /></a></center>'
2020-04-05 09:17:19 +00:00
if startIndex > 0:
2019-08-10 10:54:52 +00:00
# previous page link
2020-04-05 09:17:19 +00:00
hashtagSearchForm += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="/tags/' + hashtag + '?page=' + \
2020-04-05 09:17:19 +00:00
str(pageNumber - 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pageup.png" title="' + \
translate['Page up'] + \
'" alt="' + translate['Page up'] + \
2020-10-01 13:01:49 +00:00
'"></a>\n </center>\n'
2020-04-05 09:17:19 +00:00
index = startIndex
while index <= endIndex:
2020-05-22 11:32:38 +00:00
postId = lines[index].strip('\n').strip('\r')
2019-12-12 18:56:30 +00:00
if ' ' not in postId:
2020-04-05 09:17:19 +00:00
nickname = getNicknameFromActor(postId)
2019-12-12 18:56:30 +00:00
if not nickname:
2020-04-05 09:17:19 +00:00
index += 1
2019-12-12 18:56:30 +00:00
continue
else:
2020-04-05 09:17:19 +00:00
postFields = postId.split(' ')
if len(postFields) != 3:
2020-09-26 18:23:43 +00:00
index += 1
2019-12-12 19:24:18 +00:00
continue
2020-04-05 09:17:19 +00:00
nickname = postFields[1]
postId = postFields[2]
postFilename = locatePost(baseDir, nickname, domain, postId)
2019-08-10 10:54:52 +00:00
if not postFilename:
2020-04-05 09:17:19 +00:00
index += 1
2019-08-10 10:54:52 +00:00
continue
2020-04-05 09:17:19 +00:00
postJsonObject = loadJson(postFilename)
2019-10-22 11:55:06 +00:00
if postJsonObject:
2019-08-10 11:31:42 +00:00
if not isPublicPost(postJsonObject):
2020-04-05 09:17:19 +00:00
index += 1
2020-03-22 21:16:02 +00:00
continue
2020-04-05 09:17:19 +00:00
showIndividualPostIcons = False
2019-12-13 10:33:33 +00:00
if nickname:
2020-04-05 09:17:19 +00:00
showIndividualPostIcons = True
allowDeletion = False
hashtagSearchForm += \
individualPostAsHtml(True, recentPostsCache,
2020-04-05 09:17:19 +00:00
maxRecentPosts,
iconsDir, translate, None,
baseDir, session, wfRequest,
personCache,
nickname, domain, port,
postJsonObject,
None, True, allowDeletion,
httpPrefix, projectVersion,
'search',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
showIndividualPostIcons,
showIndividualPostIcons,
False, False, False)
index += 1
if endIndex < noOfLines - 1:
2019-08-10 10:54:52 +00:00
# next page link
2020-04-05 09:17:19 +00:00
hashtagSearchForm += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="/tags/' + hashtag + \
2020-04-05 09:17:19 +00:00
'?page=' + str(pageNumber + 1) + \
'"><img loading="lazy" class="pageicon" src="/' + iconsDir + \
'/pagedown.png" title="' + translate['Page down'] + \
2020-10-01 11:00:56 +00:00
'" alt="' + translate['Page down'] + '"></a>' + \
2020-10-01 13:01:49 +00:00
' </center>'
2020-04-05 09:17:19 +00:00
hashtagSearchForm += htmlFooter()
2019-08-10 10:54:52 +00:00
return hashtagSearchForm
2020-04-05 09:17:19 +00:00
2020-09-26 18:23:43 +00:00
def rss2TagHeader(hashtag: str, httpPrefix: str, domainFull: str) -> str:
rssStr = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
rssStr += "<rss version=\"2.0\">"
rssStr += '<channel>'
rssStr += ' <title>#' + hashtag + '</title>'
rssStr += ' <link>' + httpPrefix + '://' + domainFull + \
'/tags/rss2/' + hashtag + '</link>'
return rssStr
def rss2TagFooter() -> str:
rssStr = '</channel>'
rssStr += '</rss>'
return rssStr
def rssHashtagSearch(nickname: str, domain: str, port: int,
recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, hashtag: str,
postsPerPage: int,
session, wfRequest: {}, personCache: {},
httpPrefix: str, projectVersion: str,
YTReplacementDomain: str) -> str:
"""Show an rss feed for a hashtag
"""
if hashtag.startswith('#'):
hashtag = hashtag[1:]
hashtag = urllib.parse.unquote(hashtag)
hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt'
if not os.path.isfile(hashtagIndexFile):
if hashtag != hashtag.lower():
hashtag = hashtag.lower()
hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt'
if not os.path.isfile(hashtagIndexFile):
print('WARN: hashtag file not found ' + hashtagIndexFile)
return None
# check that the directory for the nickname exists
if nickname:
if not os.path.isdir(baseDir + '/accounts/' +
nickname + '@' + domain):
nickname = None
# read the index
lines = []
with open(hashtagIndexFile, "r") as f:
lines = f.readlines()
if not lines:
return None
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
maxFeedLength = 10
hashtagFeed = \
rss2TagHeader(hashtag, httpPrefix, domainFull)
for index in range(len(lines)):
postId = lines[index].strip('\n').strip('\r')
if ' ' not in postId:
nickname = getNicknameFromActor(postId)
if not nickname:
index += 1
if index >= maxFeedLength:
break
continue
else:
postFields = postId.split(' ')
if len(postFields) != 3:
index += 1
if index >= maxFeedLength:
break
continue
nickname = postFields[1]
postId = postFields[2]
postFilename = locatePost(baseDir, nickname, domain, postId)
if not postFilename:
index += 1
if index >= maxFeedLength:
break
continue
postJsonObject = loadJson(postFilename)
if postJsonObject:
if not isPublicPost(postJsonObject):
index += 1
if index >= maxFeedLength:
break
continue
# add to feed
2020-09-26 18:45:25 +00:00
if postJsonObject['object'].get('content') and \
2020-09-26 18:49:06 +00:00
postJsonObject['object'].get('attributedTo') and \
2020-09-26 18:23:43 +00:00
postJsonObject['object'].get('published'):
published = postJsonObject['object']['published']
pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ")
rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT")
hashtagFeed += ' <item>'
2020-09-26 18:49:06 +00:00
hashtagFeed += \
' <author>' + \
postJsonObject['object']['attributedTo'] + \
'</author>'
2020-09-26 18:45:25 +00:00
if postJsonObject['object'].get('summary'):
hashtagFeed += \
' <title>' + \
postJsonObject['object']['summary'] + \
'</title>'
2020-09-26 18:23:43 +00:00
hashtagFeed += \
2020-09-26 18:57:34 +00:00
' <description><![CDATA[' + \
2020-09-26 18:45:25 +00:00
postJsonObject['object']['content'] + \
2020-09-26 18:57:34 +00:00
']]></description>'
2020-09-26 18:23:43 +00:00
hashtagFeed += \
' <pubDate>' + rssDateStr + '</pubDate>'
2020-09-26 18:53:10 +00:00
if postJsonObject['object'].get('attachment'):
for attach in postJsonObject['object']['attachment']:
if not attach.get('url'):
continue
hashtagFeed += \
' <link>' + attach['url'] + '</link>'
2020-09-26 18:23:43 +00:00
hashtagFeed += ' </item>'
index += 1
if index >= maxFeedLength:
break
return hashtagFeed + rss2TagFooter()
2020-04-05 09:17:19 +00:00
def htmlSkillsSearch(translate: {}, baseDir: str,
httpPrefix: str,
skillsearch: str, instanceOnly: bool,
2019-09-07 10:08:18 +00:00
postsPerPage: int) -> str:
2019-08-27 22:50:40 +00:00
"""Show a page containing search results for a skill
"""
if skillsearch.startswith('*'):
2020-04-05 09:17:19 +00:00
skillsearch = skillsearch[1:].strip()
2019-08-27 22:50:40 +00:00
2020-05-22 11:32:38 +00:00
skillsearch = skillsearch.lower().strip('\n').strip('\r')
2019-08-27 22:50:40 +00:00
2020-04-05 09:17:19 +00:00
results = []
2019-08-27 22:50:40 +00:00
# search instance accounts
2020-04-05 09:17:19 +00:00
for subdir, dirs, files in os.walk(baseDir + '/accounts/'):
2019-08-27 22:50:40 +00:00
for f in files:
if not f.endswith('.json'):
continue
if '@' not in f:
continue
if f.startswith('inbox@'):
continue
2020-04-05 09:17:19 +00:00
actorFilename = os.path.join(subdir, f)
actorJson = loadJson(actorFilename)
2019-10-22 11:55:06 +00:00
if actorJson:
2019-08-28 08:58:16 +00:00
if actorJson.get('id') and \
actorJson.get('skills') and \
actorJson.get('name') and \
actorJson.get('icon'):
2020-04-05 09:17:19 +00:00
actor = actorJson['id']
for skillName, skillLevel in actorJson['skills'].items():
skillName = skillName.lower()
if not (skillName in skillsearch or
2020-03-22 20:36:19 +00:00
skillsearch in skillName):
continue
2020-04-05 09:17:19 +00:00
skillLevelStr = str(skillLevel)
if skillLevel < 100:
skillLevelStr = '0' + skillLevelStr
if skillLevel < 10:
skillLevelStr = '0' + skillLevelStr
indexStr = \
skillLevelStr + ';' + actor + ';' + \
actorJson['name'] + \
';' + actorJson['icon']['url']
2020-03-22 20:36:19 +00:00
if indexStr not in results:
results.append(indexStr)
if not instanceOnly:
# search actor cache
2020-04-05 09:17:19 +00:00
for subdir, dirs, files in os.walk(baseDir + '/cache/actors/'):
for f in files:
if not f.endswith('.json'):
continue
if '@' not in f:
continue
if f.startswith('inbox@'):
continue
2020-04-05 09:17:19 +00:00
actorFilename = os.path.join(subdir, f)
cachedActorJson = loadJson(actorFilename)
2019-10-22 11:55:06 +00:00
if cachedActorJson:
if cachedActorJson.get('actor'):
2020-04-05 09:17:19 +00:00
actorJson = cachedActorJson['actor']
if actorJson.get('id') and \
actorJson.get('skills') and \
actorJson.get('name') and \
actorJson.get('icon'):
2020-04-05 09:17:19 +00:00
actor = actorJson['id']
for skillName, skillLevel in \
actorJson['skills'].items():
skillName = skillName.lower()
if not (skillName in skillsearch or
2020-03-22 20:36:19 +00:00
skillsearch in skillName):
continue
2020-04-05 09:17:19 +00:00
skillLevelStr = str(skillLevel)
if skillLevel < 100:
skillLevelStr = '0' + skillLevelStr
if skillLevel < 10:
skillLevelStr = '0' + skillLevelStr
indexStr = \
skillLevelStr + ';' + actor + ';' + \
actorJson['name'] + \
';' + actorJson['icon']['url']
2020-03-22 20:36:19 +00:00
if indexStr not in results:
results.append(indexStr)
2019-08-27 22:50:40 +00:00
results.sort(reverse=True)
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
skillSearchCSS = cssFile.read()
if httpPrefix != 'https':
skillSearchCSS = \
skillSearchCSS.replace('https://',
httpPrefix + '://')
skillSearchForm = htmlHeader(cssFilename, skillSearchCSS)
skillSearchForm += \
'<center><h1>' + translate['Skills search'] + ': ' + \
skillsearch + '</h1></center>'
if len(results) == 0:
skillSearchForm += \
'<center><h5>' + translate['No results'] + \
'</h5></center>'
2019-08-27 22:50:40 +00:00
else:
2020-04-05 09:17:19 +00:00
skillSearchForm += '<center>'
ctr = 0
2019-08-27 22:50:40 +00:00
for skillMatch in results:
2020-04-05 09:17:19 +00:00
skillMatchFields = skillMatch.split(';')
if len(skillMatchFields) != 4:
2020-03-22 20:36:19 +00:00
continue
2020-04-05 09:17:19 +00:00
actor = skillMatchFields[1]
actorName = skillMatchFields[2]
avatarUrl = skillMatchFields[3]
skillSearchForm += \
'<div class="search-result""><a href="' + \
actor + '/skills">'
skillSearchForm += \
'<img loading="lazy" src="' + avatarUrl + \
'"/><span class="search-result-text">' + actorName + \
2020-03-22 20:36:19 +00:00
'</span></a></div>'
2020-04-05 09:17:19 +00:00
ctr += 1
if ctr >= postsPerPage:
2020-03-22 20:36:19 +00:00
break
2020-04-05 09:17:19 +00:00
skillSearchForm += '</center>'
skillSearchForm += htmlFooter()
2019-08-27 22:50:40 +00:00
return skillSearchForm
2020-04-05 09:17:19 +00:00
2020-04-11 12:37:20 +00:00
def htmlHistorySearch(translate: {}, baseDir: str,
httpPrefix: str,
nickname: str, domain: str,
historysearch: str,
postsPerPage: int, pageNumber: int,
projectVersion: str,
recentPostsCache: {},
maxRecentPosts: int,
session,
wfRequest,
personCache: {},
port: int,
YTReplacementDomain: str) -> str:
2020-04-11 12:37:20 +00:00
"""Show a page containing search results for your post history
"""
if historysearch.startswith('!'):
historysearch = historysearch[1:].strip()
2020-05-22 11:32:38 +00:00
historysearch = historysearch.lower().strip('\n').strip('\r')
2020-04-11 12:37:20 +00:00
2020-04-11 12:51:05 +00:00
boxFilenames = \
2020-04-11 12:37:20 +00:00
searchBoxPosts(baseDir, nickname, domain,
historysearch, postsPerPage)
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
with open(cssFilename, 'r') as cssFile:
historySearchCSS = cssFile.read()
if httpPrefix != 'https':
historySearchCSS = \
historySearchCSS.replace('https://',
httpPrefix + '://')
historySearchForm = htmlHeader(cssFilename, historySearchCSS)
# add the page title
historySearchForm += \
'<center><h1>' + translate['Your Posts'] + '</h1></center>'
2020-04-11 12:51:05 +00:00
if len(boxFilenames) == 0:
2020-04-11 12:37:20 +00:00
historySearchForm += \
'<center><h5>' + translate['No results'] + \
'</h5></center>'
return historySearchForm
iconsDir = getIconsDir(baseDir)
# ensure that the page number is in bounds
if not pageNumber:
pageNumber = 1
elif pageNumber < 1:
pageNumber = 1
# get the start end end within the index file
startIndex = int((pageNumber - 1) * postsPerPage)
endIndex = startIndex + postsPerPage
2020-04-11 12:51:05 +00:00
noOfBoxFilenames = len(boxFilenames)
if endIndex >= noOfBoxFilenames and noOfBoxFilenames > 0:
endIndex = noOfBoxFilenames - 1
2020-04-11 12:37:20 +00:00
index = startIndex
while index <= endIndex:
2020-04-11 12:51:05 +00:00
postFilename = boxFilenames[index]
2020-04-11 12:37:20 +00:00
if not postFilename:
index += 1
continue
postJsonObject = loadJson(postFilename)
if not postJsonObject:
2020-04-11 12:51:05 +00:00
index += 1
2020-04-11 12:37:20 +00:00
continue
showIndividualPostIcons = True
allowDeletion = False
historySearchForm += \
individualPostAsHtml(True, recentPostsCache,
2020-04-11 12:37:20 +00:00
maxRecentPosts,
iconsDir, translate, None,
baseDir, session, wfRequest,
personCache,
nickname, domain, port,
postJsonObject,
None, True, allowDeletion,
httpPrefix, projectVersion,
'search',
YTReplacementDomain,
2020-04-11 12:37:20 +00:00
showIndividualPostIcons,
showIndividualPostIcons,
False, False, False)
index += 1
historySearchForm += htmlFooter()
return historySearchForm
2020-04-05 09:17:19 +00:00
def scheduledPostsExist(baseDir: str, nickname: str, domain: str) -> bool:
2020-01-14 10:23:17 +00:00
"""Returns true if there are posts scheduled to be delivered
"""
2020-04-05 09:17:19 +00:00
scheduleIndexFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/schedule.index'
2020-01-14 10:23:17 +00:00
if not os.path.isfile(scheduleIndexFilename):
return False
if '#users#' in open(scheduleIndexFilename).read():
return True
return False
2020-04-05 09:17:19 +00:00
2020-10-01 17:26:21 +00:00
def htmlEditLinks(translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str) -> str:
"""Shows the edit links screen
"""
if '/users/' not in path:
return ''
pathOriginal = path
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'
with open(cssFilename, 'r') as cssFile:
editCSS = cssFile.read()
if httpPrefix != 'https':
editCSS = \
editCSS.replace('https://', httpPrefix + '://')
editLinksForm = htmlHeader(cssFilename, editCSS)
editLinksForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + path + '/linksdata">\n'
editLinksForm += \
' <div class="vertical-center">\n'
editLinksForm += \
' <p class="new-post-text">' + translate['Edit Links'] + '</p>'
editLinksForm += \
' <div class="container">\n'
editLinksForm += \
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
translate['Go Back'] + '</button></a>\n'
editLinksForm += \
' <input type="submit" name="submitLinks" value="' + \
translate['Submit'] + '">\n'
editLinksForm += \
' </div>\n'
linksFilename = baseDir + '/accounts/links.txt'
linksStr = ''
if os.path.isfile(linksFilename):
with open(linksFilename, 'r') as fp:
linksStr = fp.read()
2020-10-01 20:04:14 +00:00
editLinksForm += \
2020-10-01 17:26:21 +00:00
'<div class="container">'
editLinksForm += \
' ' + \
2020-10-01 20:04:14 +00:00
translate['One link per line. Description followed by the link.'] + \
'<br>'
2020-10-01 17:26:21 +00:00
editLinksForm += \
' <textarea id="message" name="editedLinks" style="height:500px">' + \
linksStr + '</textarea>'
editLinksForm += \
'</div>'
editLinksForm += htmlFooter()
return editLinksForm
2020-10-04 09:22:27 +00:00
def htmlEditNewswire(translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str) -> str:
"""Shows the edit newswire screen
"""
if '/users/' not in path:
return ''
pathOriginal = path
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'
with open(cssFilename, 'r') as cssFile:
editCSS = cssFile.read()
if httpPrefix != 'https':
editCSS = \
editCSS.replace('https://', httpPrefix + '://')
editNewswireForm = htmlHeader(cssFilename, editCSS)
editNewswireForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + path + '/newswiredata">\n'
editNewswireForm += \
' <div class="vertical-center">\n'
editNewswireForm += \
' <p class="new-post-text">' + translate['Edit newswire'] + '</p>'
editNewswireForm += \
' <div class="container">\n'
editNewswireForm += \
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
translate['Go Back'] + '</button></a>\n'
editNewswireForm += \
' <input type="submit" name="submitNewswire" value="' + \
translate['Submit'] + '">\n'
editNewswireForm += \
' </div>\n'
newswireFilename = baseDir + '/accounts/newswire.txt'
newswireStr = ''
if os.path.isfile(newswireFilename):
with open(newswireFilename, 'r') as fp:
newswireStr = fp.read()
editNewswireForm += \
'<div class="container">'
2020-10-06 16:18:22 +00:00
2020-10-04 09:22:27 +00:00
editNewswireForm += \
' ' + \
translate['Add RSS feed links below.'] + \
'<br>'
editNewswireForm += \
' <textarea id="message" name="editedNewswire" ' + \
'style="height:500px">' + newswireStr + '</textarea>'
2020-10-04 09:22:27 +00:00
editNewswireForm += \
'</div>'
editNewswireForm += htmlFooter()
return editNewswireForm
2020-04-05 09:17:19 +00:00
def htmlEditProfile(translate: {}, baseDir: str, path: str,
domain: str, port: int, httpPrefix: str) -> str:
2019-08-02 09:52:12 +00:00
"""Shows the edit profile screen
"""
imageFormats = '.png, .jpg, .jpeg, .gif, .webp, .avif'
2020-04-05 09:17:19 +00:00
pathOriginal = path
path = path.replace('/inbox', '').replace('/outbox', '')
path = path.replace('/shares', '')
nickname = getNicknameFromActor(path)
2019-09-02 09:43:43 +00:00
if not nickname:
return ''
2020-04-05 09:17:19 +00:00
domainFull = domain
2019-08-02 09:52:12 +00:00
if port:
2020-04-05 09:17:19 +00:00
if port != 80 and port != 443:
if ':' not in domain:
2020-04-05 09:17:19 +00:00
domainFull = domain + ':' + str(port)
2019-08-02 09:52:12 +00:00
2020-04-05 09:17:19 +00:00
actorFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '.json'
2019-08-02 09:52:12 +00:00
if not os.path.isfile(actorFilename):
return ''
2020-04-05 09:17:19 +00:00
isBot = ''
isGroup = ''
followDMs = ''
removeTwitter = ''
2020-08-27 09:19:32 +00:00
notifyLikes = ''
2020-08-28 13:36:21 +00:00
hideLikeButton = ''
2020-04-05 09:17:19 +00:00
mediaInstanceStr = ''
blogsInstanceStr = ''
2020-10-07 09:10:42 +00:00
newsInstanceStr = ''
2020-04-05 09:17:19 +00:00
displayNickname = nickname
bioStr = ''
donateUrl = ''
emailAddress = ''
PGPpubKey = ''
2020-07-06 10:14:41 +00:00
PGPfingerprint = ''
2020-04-05 09:17:19 +00:00
xmppAddress = ''
matrixAddress = ''
ssbAddress = ''
2020-05-04 11:28:43 +00:00
blogAddress = ''
2020-04-05 09:17:19 +00:00
toxAddress = ''
manuallyApprovesFollowers = ''
actorJson = loadJson(actorFilename)
2019-10-22 11:55:06 +00:00
if actorJson:
2020-04-05 09:17:19 +00:00
donateUrl = getDonationUrl(actorJson)
xmppAddress = getXmppAddress(actorJson)
matrixAddress = getMatrixAddress(actorJson)
ssbAddress = getSSBAddress(actorJson)
2020-05-04 11:28:43 +00:00
blogAddress = getBlogAddress(actorJson)
2020-04-05 09:17:19 +00:00
toxAddress = getToxAddress(actorJson)
emailAddress = getEmailAddress(actorJson)
PGPpubKey = getPGPpubKey(actorJson)
2020-07-06 10:14:41 +00:00
PGPfingerprint = getPGPfingerprint(actorJson)
if actorJson.get('name'):
2020-04-05 09:17:19 +00:00
displayNickname = actorJson['name']
2019-08-02 09:52:12 +00:00
if actorJson.get('summary'):
2020-04-05 09:17:19 +00:00
bioStr = \
actorJson['summary'].replace('<p>', '').replace('</p>', '')
2019-08-02 09:52:12 +00:00
if actorJson.get('manuallyApprovesFollowers'):
if actorJson['manuallyApprovesFollowers']:
2020-04-05 09:17:19 +00:00
manuallyApprovesFollowers = 'checked'
2019-08-02 09:52:12 +00:00
else:
2020-04-05 09:17:19 +00:00
manuallyApprovesFollowers = ''
2019-08-07 20:13:44 +00:00
if actorJson.get('type'):
2020-04-05 09:17:19 +00:00
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'
2020-08-27 09:19:32 +00:00
if os.path.isfile(baseDir + '/accounts/' +
nickname + '@' + domain + '/.notifyLikes'):
notifyLikes = 'checked'
2020-08-28 13:36:21 +00:00
if os.path.isfile(baseDir + '/accounts/' +
nickname + '@' + domain + '/.hideLikeButton'):
hideLikeButton = 'checked'
2020-04-05 09:17:19 +00:00
mediaInstance = getConfigParam(baseDir, "mediaInstance")
2019-11-28 17:03:57 +00:00
if mediaInstance:
2020-04-05 09:17:19 +00:00
if mediaInstance is True:
mediaInstanceStr = 'checked'
blogsInstanceStr = ''
2020-10-07 09:10:42 +00:00
newsInstanceStr = ''
newsInstance = getConfigParam(baseDir, "newsInstance")
if newsInstance:
if newsInstance is True:
newsInstanceStr = 'checked'
blogsInstanceStr = ''
mediaInstanceStr = ''
2020-09-30 16:28:17 +00:00
blogsInstance = getConfigParam(baseDir, "blogsInstance")
if blogsInstance:
if blogsInstance is True:
blogsInstanceStr = 'checked'
mediaInstanceStr = ''
2020-10-07 09:10:42 +00:00
newsInstanceStr = ''
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
filterStr = ''
filterFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/filters.txt'
2019-08-02 11:43:14 +00:00
if os.path.isfile(filterFilename):
with open(filterFilename, 'r') as filterfile:
2020-04-05 09:17:19 +00:00
filterStr = filterfile.read()
2019-08-02 11:43:14 +00:00
2020-04-05 09:17:19 +00:00
switchStr = ''
switchFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/replacewords.txt'
2020-02-19 19:06:23 +00:00
if os.path.isfile(switchFilename):
with open(switchFilename, 'r') as switchfile:
2020-04-05 09:17:19 +00:00
switchStr = switchfile.read()
2020-02-19 19:06:23 +00:00
autoTags = ''
autoTagsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/autotags.txt'
if os.path.isfile(autoTagsFilename):
with open(autoTagsFilename, 'r') as autoTagsFile:
autoTags = autoTagsFile.read()
2020-09-13 18:56:41 +00:00
autoCW = ''
autoCWFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/autocw.txt'
if os.path.isfile(autoCWFilename):
with open(autoCWFilename, 'r') as autoCWFile:
autoCW = autoCWFile.read()
2020-04-05 09:17:19 +00:00
blockedStr = ''
blockedFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/blocking.txt'
2019-08-02 11:43:14 +00:00
if os.path.isfile(blockedFilename):
with open(blockedFilename, 'r') as blockedfile:
2020-04-05 09:17:19 +00:00
blockedStr = blockedfile.read()
2019-08-02 11:43:14 +00:00
2020-04-05 09:17:19 +00:00
allowedInstancesStr = ''
allowedInstancesFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/allowedinstances.txt'
if os.path.isfile(allowedInstancesFilename):
with open(allowedInstancesFilename, 'r') as allowedInstancesFile:
2020-04-05 09:17:19 +00:00
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()
2020-04-05 09:17:19 +00:00
skills = getSkills(baseDir, nickname, domain)
skillsStr = ''
skillCtr = 1
2019-08-09 08:46:38 +00:00
if skills:
2020-04-05 09:17:19 +00:00
for skillDesc, skillValue in skills.items():
skillsStr += \
'<p><input type="text" placeholder="' + translate['Skill'] + \
' ' + str(skillCtr) + '" name="skillName' + str(skillCtr) + \
'" value="' + skillDesc + '" style="width:40%">'
skillsStr += \
'<input type="range" min="1" max="100" ' + \
'class="slider" name="skillValue' + \
str(skillCtr) + '" value="' + str(skillValue) + '"></p>'
skillCtr += 1
skillsStr += \
'<p><input type="text" placeholder="Skill ' + str(skillCtr) + \
'" name="skillName' + str(skillCtr) + \
'" value="" style="width:40%">'
skillsStr += \
'<input type="range" min="1" max="100" ' + \
'class="slider" name="skillValue' + \
str(skillCtr) + '" value="50"></p>'
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
editProfileCSS = cssFile.read()
if httpPrefix != 'https':
editProfileCSS = \
editProfileCSS.replace('https://', httpPrefix + '://')
instanceStr = ''
moderatorsStr = ''
themesDropdown = ''
adminNickname = getConfigParam(baseDir, 'admin')
if path.startswith('/users/' + adminNickname + '/'):
instanceDescription = \
getConfigParam(baseDir, 'instanceDescription')
instanceDescriptionShort = \
getConfigParam(baseDir, 'instanceDescriptionShort')
instanceTitle = \
getConfigParam(baseDir, 'instanceTitle')
instanceStr = '<div class="container">'
instanceStr += \
' <label class="labels">' + \
translate['Instance Title'] + '</label>'
2020-05-27 10:30:40 +00:00
if instanceTitle:
instanceStr += \
' <input type="text" name="instanceTitle" value="' + \
instanceTitle + '"><br>'
else:
instanceStr += \
' <input type="text" name="instanceTitle" value=""><br>'
2020-04-05 09:17:19 +00:00
instanceStr += \
' <label class="labels">' + \
translate['Instance Short Description'] + '</label>'
2020-05-27 10:30:40 +00:00
if instanceDescriptionShort:
instanceStr += \
' <input type="text" ' + \
'name="instanceDescriptionShort" value="' + \
instanceDescriptionShort + '"><br>'
else:
instanceStr += \
' <input type="text" ' + \
'name="instanceDescriptionShort" value=""><br>'
2020-04-05 09:17:19 +00:00
instanceStr += \
' <label class="labels">' + \
translate['Instance Description'] + '</label>'
2020-05-27 10:30:40 +00:00
if instanceDescription:
instanceStr += \
' <textarea id="message" name="instanceDescription" ' + \
'style="height:200px">' + \
instanceDescription + '</textarea>'
else:
instanceStr += \
' <textarea id="message" name="instanceDescription" ' + \
'style="height:200px"></textarea>'
2020-04-05 09:17:19 +00:00
instanceStr += \
' <label class="labels">' + \
translate['Instance Logo'] + '</label>'
instanceStr += \
2020-02-23 15:32:47 +00:00
' <input type="file" id="instanceLogo" name="instanceLogo"'
2020-04-05 09:17:19 +00:00
instanceStr += ' accept="' + imageFormats + '">'
instanceStr += '</div>'
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
moderators = ''
moderatorsFile = baseDir + '/accounts/moderators.txt'
2019-08-12 21:20:47 +00:00
if os.path.isfile(moderatorsFile):
with open(moderatorsFile, "r") as f:
2020-04-05 09:17:19 +00:00
moderators = f.read()
moderatorsStr = '<div class="container">'
moderatorsStr += ' <b>' + translate['Moderators'] + '</b><br>'
moderatorsStr += ' ' + \
translate['A list of moderator nicknames. One per line.']
moderatorsStr += \
' <textarea id="message" name="moderators" placeholder="' + \
translate['List of moderator nicknames'] + \
'..." style="height:200px">' + moderators + '</textarea>'
moderatorsStr += '</div>'
2020-05-28 09:11:21 +00:00
themes = getThemesList()
2020-04-05 09:17:19 +00:00
themesDropdown = '<div class="container">'
themesDropdown += ' <b>' + translate['Theme'] + '</b><br>'
2020-07-10 18:08:45 +00:00
grayscaleFilename = \
baseDir + '/accounts/.grayscale'
grayscale = ''
if os.path.isfile(grayscaleFilename):
grayscale = 'checked'
themesDropdown += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="grayscale" ' + grayscale + \
'> ' + translate['Grayscale'] + '<br>'
2020-04-05 09:17:19 +00:00
themesDropdown += ' <select id="themeDropdown" ' + \
'name="themeDropdown" class="theme">'
2020-05-28 09:11:21 +00:00
for themeName in themes:
themesDropdown += ' <option value="' + \
themeName.lower() + '">' + \
translate[themeName] + '</option>'
2020-04-05 09:17:19 +00:00
themesDropdown += ' </select><br>'
2020-05-26 20:47:52 +00:00
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 += \
' <input type="checkbox" class="profilecheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="removeCustomFont"> ' + \
2020-05-26 20:47:52 +00:00
translate['Remove the custom font'] + '<br>'
2020-04-05 09:17:19 +00:00
themesDropdown += '</div>'
themeName = getConfigParam(baseDir, 'theme')
themesDropdown = \
themesDropdown.replace('<option value="' + themeName + '">',
'<option value="' + themeName +
'" selected>')
editProfileForm = htmlHeader(cssFilename, editProfileCSS)
editProfileForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
2020-08-12 09:40:18 +00:00
'accept-charset="UTF-8" action="' + path + '/profiledata">\n'
editProfileForm += ' <div class="vertical-center">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <p class="new-post-text">' + translate['Profile for'] + \
' ' + nickname + '@' + domainFull + '</p>'
2020-08-12 09:40:18 +00:00
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
2020-08-12 09:40:18 +00:00
translate['Go Back'] + '</button></a>\n'
editProfileForm += \
' <input type="submit" name="submitProfile" value="' + \
translate['Submit'] + '">\n'
editProfileForm += ' </div>\n'
2020-04-05 09:17:19 +00:00
if scheduledPostsExist(baseDir, nickname, domain):
2020-08-12 09:40:18 +00:00
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="removeScheduledPosts"> ' + \
2020-08-12 09:40:18 +00:00
translate['Remove scheduled posts'] + '<br>\n'
editProfileForm += ' </div>\n'
2020-04-05 09:17:19 +00:00
2020-08-12 09:40:18 +00:00
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Nickname'] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" name="displayNickname" value="' + \
2020-08-12 09:40:18 +00:00
displayNickname + '"><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <label class="labels">' + translate['Your bio'] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <textarea id="message" name="bio" style="height:200px">' + \
2020-08-12 09:40:18 +00:00
bioStr + '</textarea>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += '<label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Donations link'] + '</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" placeholder="https://..." ' + \
2020-08-12 09:40:18 +00:00
'name="donateUrl" value="' + donateUrl + '">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
'<label class="labels">' + translate['XMPP'] + '</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" name="xmppAddress" value="' + \
2020-08-12 09:40:18 +00:00
xmppAddress + '">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += '<label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Matrix'] + '</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" name="matrixAddress" value="' + \
2020-08-12 09:40:18 +00:00
matrixAddress+'">\n'
2020-05-04 11:28:43 +00:00
2020-08-12 09:40:18 +00:00
editProfileForm += '<label class="labels">SSB</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" name="ssbAddress" value="' + \
2020-08-12 09:40:18 +00:00
ssbAddress + '">\n'
2020-05-04 11:28:43 +00:00
2020-08-12 09:40:18 +00:00
editProfileForm += '<label class="labels">Blog</label><br>\n'
2020-05-04 11:28:43 +00:00
editProfileForm += \
' <input type="text" name="blogAddress" value="' + \
2020-08-12 09:40:18 +00:00
blogAddress + '">\n'
2020-05-04 11:28:43 +00:00
2020-08-12 09:40:18 +00:00
editProfileForm += '<label class="labels">Tox</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="text" name="toxAddress" value="' + \
2020-08-12 09:40:18 +00:00
toxAddress + '">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += '<label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Email'] + '</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <input type="text" name="email" value="' + emailAddress + '">\n'
2020-07-06 10:14:41 +00:00
editProfileForm += \
'<label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['PGP Fingerprint'] + '</label><br>\n'
2020-07-06 10:14:41 +00:00
editProfileForm += \
' <input type="text" name="openpgp" value="' + \
2020-08-12 09:40:18 +00:00
PGPfingerprint + '">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
'<label class="labels">' + translate['PGP'] + '</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <textarea id="message" placeholder=' + \
'"-----BEGIN PGP PUBLIC KEY BLOCK-----" name="pgp" ' + \
2020-08-12 09:40:18 +00:00
'style="height:100px">' + PGPpubKey + '</textarea>\n'
2020-06-17 17:02:17 +00:00
editProfileForm += '<a href="/users/' + nickname + \
'/followingaccounts"><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Following'] + '</label></a><br>\n'
editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
idx = 'The files attached below should be no larger than ' + \
'10MB in total uploaded at once.'
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <label class="labels">' + translate[idx] + '</label><br><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <label class="labels">' + translate['Avatar image'] + \
'</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="file" id="avatar" name="avatar"'
2020-08-12 09:40:18 +00:00
editProfileForm += ' accept="' + imageFormats + '">\n'
2020-06-10 16:56:23 +00:00
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Background image'] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <input type="file" id="image" name="image"'
2020-08-12 09:40:18 +00:00
editProfileForm += ' accept="' + imageFormats + '">\n'
2020-06-10 16:56:23 +00:00
2020-04-05 09:17:19 +00:00
editProfileForm += ' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Timeline banner image'] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <input type="file" id="banner" name="banner"'
2020-08-12 09:40:18 +00:00
editProfileForm += ' accept="' + imageFormats + '">\n'
2020-06-10 16:56:23 +00:00
editProfileForm += ' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Search banner image'] + '</label>\n'
2020-06-10 19:02:35 +00:00
editProfileForm += ' <input type="file" id="search_banner" '
editProfileForm += 'name="search_banner"'
2020-08-12 09:40:18 +00:00
editProfileForm += ' accept="' + imageFormats + '">\n'
2020-06-10 16:56:23 +00:00
2020-10-02 14:37:05 +00:00
editProfileForm += ' <br><label class="labels">' + \
translate['Left column image'] + '</label>\n'
editProfileForm += ' <input type="file" id="left_col_image" '
editProfileForm += 'name="left_col_image"'
editProfileForm += ' accept="' + imageFormats + '">\n'
editProfileForm += ' <br><label class="labels">' + \
translate['Right column image'] + '</label>\n'
editProfileForm += ' <input type="file" id="right_col_image" '
editProfileForm += 'name="right_col_image"'
editProfileForm += ' accept="' + imageFormats + '">\n'
2020-08-12 09:40:18 +00:00
editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
'<label class="labels">' + translate['Change Password'] + \
2020-08-12 09:40:18 +00:00
'</label><br>\n'
editProfileForm += ' <input type="text" name="password" ' + \
'value=""><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
'<label class="labels">' + translate['Confirm Password'] + \
2020-08-12 09:40:18 +00:00
'</label><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <input type="text" name="passwordconfirm" value="">\n'
editProfileForm += ' </div>\n'
if path.startswith('/users/' + adminNickname + '/'):
editProfileForm += ' <div class="container">\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="mediaInstance" ' + mediaInstanceStr + '> ' + \
translate['This is a media instance'] + '<br>\n'
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="blogsInstance" ' + blogsInstanceStr + '> ' + \
translate['This is a blogging instance'] + '<br>\n'
2020-10-07 09:10:42 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="newsInstance" ' + newsInstanceStr + '> ' + \
translate['This is a news instance'] + '<br>\n'
editProfileForm += ' </div>\n'
2020-08-12 09:40:18 +00:00
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="approveFollowers" ' + manuallyApprovesFollowers + \
2020-08-12 09:40:18 +00:00
'> ' + translate['Approve follower requests'] + '<br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" ' + \
'class="profilecheckbox" name="isBot" ' + \
2020-08-12 09:40:18 +00:00
isBot + '> ' + translate['This is a bot account'] + '<br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" ' + \
2020-07-03 19:57:42 +00:00
'class="profilecheckbox" name="isGroup" ' + isGroup + '> ' + \
2020-08-12 09:40:18 +00:00
translate['This is a group account'] + '<br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="followDMs" ' + followDMs + '> ' + \
2020-08-12 09:40:18 +00:00
translate['Only people I follow can send me DMs'] + '<br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="removeTwitter" ' + removeTwitter + '> ' + \
2020-08-12 09:40:18 +00:00
translate['Remove Twitter posts'] + '<br>\n'
2020-08-27 09:19:32 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="notifyLikes" ' + notifyLikes + '> ' + \
translate['Notify when posts are liked'] + '<br>\n'
2020-08-28 13:36:21 +00:00
editProfileForm += \
' <input type="checkbox" class="profilecheckbox" ' + \
'name="hideLikeButton" ' + hideLikeButton + '> ' + \
translate["Don't show the Like button"] + '<br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Filtered words'] + '</label></b>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['One per line'] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <textarea id="message" ' + \
'name="filteredWords" style="height:200px">' + \
2020-08-12 09:40:18 +00:00
filterStr + '</textarea>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Word Replacements'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> B</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <textarea id="message" name="switchWords" ' + \
2020-08-12 09:40:18 +00:00
'style="height:200px">' + switchStr + '</textarea>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Autogenerated Hashtags'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> #B</label>\n'
editProfileForm += \
' <textarea id="message" name="autoTags" ' + \
'style="height:200px">' + autoTags + '</textarea>\n'
2020-09-13 18:56:41 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
translate['Autogenerated Content Warnings'] + '</label></b>\n'
editProfileForm += ' <br><label class="labels">A -> B</label>\n'
editProfileForm += \
' <textarea id="message" name="autoCW" ' + \
'style="height:200px">' + autoCW + '</textarea>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Blocked accounts'] + '</label></b>\n'
2020-04-05 09:17:19 +00:00
idx = 'Blocked accounts, one per line, in the form ' + \
'nickname@domain or *@blockeddomain'
editProfileForm += \
2020-08-12 09:40:18 +00:00
' <br><label class="labels">' + translate[idx] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <textarea id="message" name="blocked" style="height:200px">' + \
2020-08-12 09:40:18 +00:00
blockedStr + '</textarea>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Federation list'] + '</label></b>\n'
2020-04-05 09:17:19 +00:00
idx = 'Federate only with a defined set of instances. ' + \
'One domain name per line.'
editProfileForm += \
' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate[idx] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <textarea id="message" name="allowedInstances" ' + \
2020-08-12 09:40:18 +00:00
'style="height:200px">' + allowedInstancesStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Git Projects'] + '</label></b>\n'
idx = 'List of project names that you wish to receive git patches for'
editProfileForm += \
' <br><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate[idx] + '</label>\n'
editProfileForm += \
' <textarea id="message" name="gitProjects" ' + \
2020-08-12 09:40:18 +00:00
'style="height:100px">' + gitProjectsStr + '</textarea>\n'
editProfileForm += \
' <br><b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['YouTube Replacement Domain'] + '</label></b>\n'
YTReplacementDomain = getConfigParam(baseDir, "youtubedomain")
if not YTReplacementDomain:
YTReplacementDomain = ''
editProfileForm += \
' <input type="text" name="ytdomain" value="' + \
2020-08-12 09:40:18 +00:00
YTReplacementDomain + '">\n'
2020-08-12 09:40:18 +00:00
editProfileForm += ' </div>\n'
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Skills'] + '</label></b><br>\n'
2020-04-05 09:17:19 +00:00
idx = 'If you want to participate within organizations then you ' + \
'can indicate some skills that you have and approximate ' + \
'proficiency levels. This helps organizers to construct ' + \
'teams with an appropriate combination of skills.'
editProfileForm += ' <label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate[idx] + '</label>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += skillsStr + themesDropdown + moderatorsStr
2020-08-12 09:40:18 +00:00
editProfileForm += ' </div>\n' + instanceStr
editProfileForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
editProfileForm += ' <b><label class="labels">' + \
2020-08-12 09:40:18 +00:00
translate['Danger Zone'] + '</label></b><br>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += \
' <input type="checkbox" class=dangercheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="deactivateThisAccount"> ' + \
2020-08-12 09:40:18 +00:00
translate['Deactivate this account'] + '<br>\n'
editProfileForm += ' </div>\n'
editProfileForm += ' </div>\n'
editProfileForm += '</form>\n'
2020-04-05 09:17:19 +00:00
editProfileForm += htmlFooter()
2019-08-02 09:52:12 +00:00
return editProfileForm
2020-04-05 09:17:19 +00:00
def htmlGetLoginCredentials(loginParams: str,
lastLoginTime: int) -> (str, str, bool):
2019-07-25 10:56:24 +00:00
"""Receives login credentials via HTTPServer POST
2019-07-24 22:38:42 +00:00
"""
2019-07-25 10:56:24 +00:00
if not loginParams.startswith('username='):
2020-04-05 09:17:19 +00:00
return None, None, None
2019-07-24 22:38:42 +00:00
# minimum time between login attempts
2020-04-05 09:17:19 +00:00
currTime = int(time.time())
if currTime < lastLoginTime+10:
return None, None, None
2019-07-24 22:38:42 +00:00
if '&' not in loginParams:
2020-04-05 09:17:19 +00:00
return None, None, None
loginArgs = loginParams.split('&')
nickname = None
password = None
register = False
2019-07-24 22:38:42 +00:00
for arg in loginArgs:
if '=' in arg:
2020-04-05 09:17:19 +00:00
if arg.split('=', 1)[0] == 'username':
nickname = arg.split('=', 1)[1]
elif arg.split('=', 1)[0] == 'password':
password = arg.split('=', 1)[1]
elif arg.split('=', 1)[0] == 'register':
register = True
return nickname, password, register
def htmlLogin(translate: {}, baseDir: str, autocomplete=True) -> str:
2019-08-10 18:22:28 +00:00
"""Shows the login screen
"""
2020-04-05 09:17:19 +00:00
accounts = noOfAccounts(baseDir)
loginImage = 'login.png'
loginImageFilename = None
if os.path.isfile(baseDir + '/accounts/' + loginImage):
loginImageFilename = baseDir + '/accounts/' + loginImage
2020-04-15 12:30:41 +00:00
elif os.path.isfile(baseDir + '/accounts/login.jpg'):
2020-04-05 09:17:19 +00:00
loginImage = 'login.jpg'
loginImageFilename = baseDir + '/accounts/' + loginImage
2020-04-15 12:30:41 +00:00
elif os.path.isfile(baseDir + '/accounts/login.jpeg'):
2020-04-05 09:17:19 +00:00
loginImage = 'login.jpeg'
loginImageFilename = baseDir + '/accounts/' + loginImage
2020-04-15 12:30:41 +00:00
elif os.path.isfile(baseDir + '/accounts/login.gif'):
2020-04-05 09:17:19 +00:00
loginImage = 'login.gif'
loginImageFilename = baseDir + '/accounts/' + loginImage
2020-04-15 12:30:41 +00:00
elif os.path.isfile(baseDir + '/accounts/login.webp'):
2020-04-05 09:17:19 +00:00
loginImage = 'login.webp'
loginImageFilename = baseDir + '/accounts/' + loginImage
elif os.path.isfile(baseDir + '/accounts/login.avif'):
loginImage = 'login.avif'
loginImageFilename = baseDir + '/accounts/' + loginImage
2019-11-14 13:30:54 +00:00
if not loginImageFilename:
2020-04-05 09:17:19 +00:00
loginImageFilename = baseDir + '/accounts/' + loginImage
copyfile(baseDir + '/img/login.png', loginImageFilename)
2020-07-25 18:56:45 +00:00
2020-07-25 19:07:06 +00:00
if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/login-background.jpg'):
copyfile(baseDir + '/accounts/login-background-custom.jpg',
baseDir + '/accounts/login-background.jpg')
2020-04-05 09:17:19 +00:00
if accounts > 0:
loginText = \
'<p class="login-text">' + \
translate['Welcome. Please enter your login details below.'] + \
'</p>'
2019-08-08 11:24:26 +00:00
else:
2020-04-05 09:17:19 +00:00
loginText = \
'<p class="login-text">' + \
translate['Please enter some credentials'] + '</p>'
loginText += \
'<p class="login-text">' + \
translate['You will become the admin of this site.'] + \
'</p>'
if os.path.isfile(baseDir + '/accounts/login.txt'):
2019-08-10 18:22:28 +00:00
# custom login message
2020-04-05 09:17:19 +00:00
with open(baseDir + '/accounts/login.txt', 'r') as file:
loginText = '<p class="login-text">' + file.read() + '</p>'
2019-07-25 19:56:25 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-login.css'
if os.path.isfile(baseDir + '/login.css'):
cssFilename = baseDir + '/login.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
loginCSS = cssFile.read()
2019-07-25 19:56:25 +00:00
2019-08-08 11:24:26 +00:00
# show the register button
2020-04-05 09:17:19 +00:00
registerButtonStr = ''
if getConfigParam(baseDir, 'registration') == 'open':
if int(getConfigParam(baseDir, 'registrationsRemaining')) > 0:
if accounts > 0:
idx = 'Welcome. Please login or register a new account.'
loginText = \
'<p class="login-text">' + \
translate[idx] + \
'</p>'
registerButtonStr = \
2020-02-23 15:32:47 +00:00
'<button type="submit" name="register">Register</button>'
2020-04-05 09:17:19 +00:00
TOSstr = \
'<p class="login-text"><a href="/terms">' + \
translate['Terms of Service'] + '</a></p>'
TOSstr += \
'<p class="login-text"><a href="/about">' + \
translate['About this Instance'] + '</a></p>'
2019-08-08 13:38:33 +00:00
2020-04-05 09:17:19 +00:00
loginButtonStr = ''
if accounts > 0:
loginButtonStr = \
'<button type="submit" name="submit">' + \
translate['Login'] + '</button>'
2020-04-05 09:17:19 +00:00
autocompleteStr = ''
if not autocomplete:
2020-04-05 09:17:19 +00:00
autocompleteStr = 'autocomplete="off" value=""'
2020-04-05 09:17:19 +00:00
loginForm = htmlHeader(cssFilename, loginCSS)
2020-07-28 09:29:56 +00:00
loginForm += '<br>\n'
2020-07-28 09:50:22 +00:00
loginForm += '<form method="POST" action="/login">\n'
loginForm += ' <div class="imgcontainer">\n'
2020-04-05 09:17:19 +00:00
loginForm += \
' <img loading="lazy" src="' + loginImage + \
2020-07-28 09:50:22 +00:00
'" alt="login image" class="loginimage">\n'
loginForm += loginText + TOSstr + '\n'
loginForm += ' </div>\n'
loginForm += '\n'
loginForm += ' <div class="container">\n'
2020-04-05 09:17:19 +00:00
loginForm += ' <label for="nickname"><b>' + \
2020-07-28 09:50:22 +00:00
translate['Nickname'] + '</b></label>\n'
2020-04-05 09:17:19 +00:00
loginForm += \
' <input type="text" ' + autocompleteStr + ' placeholder="' + \
2020-07-28 09:50:22 +00:00
translate['Enter Nickname'] + '" name="username" required autofocus>\n'
loginForm += '\n'
2020-04-05 09:17:19 +00:00
loginForm += ' <label for="password"><b>' + \
2020-07-28 09:50:22 +00:00
translate['Password'] + '</b></label>\n'
2020-04-05 09:17:19 +00:00
loginForm += \
' <input type="password" ' + autocompleteStr + \
' placeholder="' + translate['Enter Password'] + \
2020-07-28 09:50:22 +00:00
'" name="password" required>\n'
loginForm += loginButtonStr + registerButtonStr + '\n'
loginForm += ' </div>\n'
loginForm += '</form>\n'
2020-04-05 09:17:19 +00:00
loginForm += \
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
'<img loading="lazy" class="license" title="' + \
translate['Get the source code'] + '" alt="' + \
2020-07-28 09:50:22 +00:00
translate['Get the source code'] + '" src="/icons/agpl.png" /></a>\n'
2020-04-05 09:17:19 +00:00
loginForm += htmlFooter()
2019-07-24 22:38:42 +00:00
return loginForm
2020-04-05 09:17:19 +00:00
def htmlTermsOfService(baseDir: str, httpPrefix: str, domainFull: str) -> str:
2019-08-13 09:24:55 +00:00
"""Show the terms of service screen
"""
2020-04-05 09:17:19 +00:00
adminNickname = getConfigParam(baseDir, 'admin')
if not os.path.isfile(baseDir + '/accounts/tos.txt'):
copyfile(baseDir + '/default_tos.txt',
baseDir + '/accounts/tos.txt')
2020-07-25 18:56:45 +00:00
2020-07-25 19:07:06 +00:00
if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/login-background.jpg'):
copyfile(baseDir + '/accounts/login-background-custom.jpg',
baseDir + '/accounts/login-background.jpg')
2020-04-05 09:17:19 +00:00
TOSText = 'Terms of Service go here.'
if os.path.isfile(baseDir + '/accounts/tos.txt'):
with open(baseDir + '/accounts/tos.txt', 'r') as file:
TOSText = file.read()
TOSForm = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
termsCSS = cssFile.read()
if httpPrefix != 'https':
termsCSS = termsCSS.replace('https://', httpPrefix+'://')
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
TOSForm = htmlHeader(cssFilename, termsCSS)
2020-07-28 10:06:05 +00:00
TOSForm += '<div class="container">' + TOSText + '</div>\n'
2019-08-10 15:33:18 +00:00
if adminNickname:
2020-04-05 09:17:19 +00:00
adminActor = httpPrefix + '://' + domainFull + \
'/users/' + adminNickname
TOSForm += \
2020-07-28 10:06:05 +00:00
'<div class="container"><center>\n' + \
2020-04-05 09:17:19 +00:00
'<p class="administeredby">Administered by <a href="' + \
2020-07-28 10:06:05 +00:00
adminActor + '">' + adminNickname + '</a></p>\n' + \
'</center></div>\n'
2020-04-05 09:17:19 +00:00
TOSForm += htmlFooter()
2019-08-08 13:38:33 +00:00
return TOSForm
2020-04-05 09:17:19 +00:00
2020-04-17 16:30:06 +00:00
def htmlAbout(baseDir: str, httpPrefix: str,
domainFull: str, onionDomain: str) -> str:
2019-08-26 16:02:47 +00:00
"""Show the about screen
"""
2020-04-05 09:17:19 +00:00
adminNickname = getConfigParam(baseDir, 'admin')
if not os.path.isfile(baseDir + '/accounts/about.txt'):
copyfile(baseDir + '/default_about.txt',
baseDir + '/accounts/about.txt')
2020-07-25 18:56:45 +00:00
2020-07-25 19:07:06 +00:00
if os.path.isfile(baseDir + '/accounts/login-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/login-background.jpg'):
copyfile(baseDir + '/accounts/login-background-custom.jpg',
baseDir + '/accounts/login-background.jpg')
2020-04-05 09:17:19 +00:00
aboutText = 'Information about this instance goes here.'
if os.path.isfile(baseDir + '/accounts/about.txt'):
2020-07-26 12:57:51 +00:00
with open(baseDir + '/accounts/about.txt', 'r') as aboutFile:
aboutText = aboutFile.read()
2020-04-05 09:17:19 +00:00
aboutForm = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-07-26 12:57:51 +00:00
aboutCSS = cssFile.read()
2020-04-05 09:17:19 +00:00
if httpPrefix != 'http':
2020-07-26 12:57:51 +00:00
aboutCSS = aboutCSS.replace('https://',
2020-04-05 09:17:19 +00:00
httpPrefix + '://')
2019-12-10 14:48:08 +00:00
2020-07-26 12:57:51 +00:00
aboutForm = htmlHeader(cssFilename, aboutCSS)
2020-04-05 09:17:19 +00:00
aboutForm += '<div class="container">' + aboutText + '</div>'
2020-04-17 16:30:06 +00:00
if onionDomain:
aboutForm += \
2020-07-28 10:06:05 +00:00
'<div class="container"><center>\n' + \
'<p class="administeredby">' + \
'http://' + onionDomain + '</p>\n</center></div>\n'
2019-08-26 16:02:47 +00:00
if adminNickname:
2020-07-26 12:57:51 +00:00
adminActor = '/users/' + adminNickname
2020-04-05 09:17:19 +00:00
aboutForm += \
2020-07-28 10:06:05 +00:00
'<div class="container"><center>\n' + \
2020-04-05 09:17:19 +00:00
'<p class="administeredby">Administered by <a href="' + \
2020-07-28 10:06:05 +00:00
adminActor + '">' + adminNickname + '</a></p>\n' + \
'</center></div>\n'
2020-04-05 09:17:19 +00:00
aboutForm += htmlFooter()
2019-08-26 16:02:47 +00:00
return aboutForm
2020-04-05 09:17:19 +00:00
def htmlHashtagBlocked(baseDir: str, translate: {}) -> str:
2019-08-14 10:32:15 +00:00
"""Show the screen for a blocked hashtag
"""
2020-04-05 09:17:19 +00:00
blockedHashtagForm = ''
cssFilename = baseDir + '/epicyon-suspended.css'
if os.path.isfile(baseDir + '/suspended.css'):
cssFilename = baseDir + '/suspended.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
blockedHashtagCSS = cssFile.read()
2020-06-19 09:13:41 +00:00
blockedHashtagForm = htmlHeader(cssFilename, blockedHashtagCSS)
2020-07-28 10:06:05 +00:00
blockedHashtagForm += '<div><center>\n'
2020-09-28 10:54:41 +00:00
blockedHashtagForm += \
' <p class="screentitle">' + \
translate['Hashtag Blocked'] + '</p>\n'
2020-04-05 09:17:19 +00:00
blockedHashtagForm += \
2020-09-28 11:08:40 +00:00
' <p>See <a href="/terms">' + \
translate['Terms of Service'] + '</a></p>\n'
2020-07-28 10:06:05 +00:00
blockedHashtagForm += '</center></div>\n'
2020-04-05 09:17:19 +00:00
blockedHashtagForm += htmlFooter()
2019-08-14 10:32:15 +00:00
return blockedHashtagForm
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
2019-08-13 09:24:55 +00:00
def htmlSuspended(baseDir: str) -> str:
"""Show the screen for suspended accounts
"""
2020-04-05 09:17:19 +00:00
suspendedForm = ''
cssFilename = baseDir + '/epicyon-suspended.css'
if os.path.isfile(baseDir + '/suspended.css'):
cssFilename = baseDir + '/suspended.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
suspendedCSS = cssFile.read()
suspendedForm = htmlHeader(cssFilename, suspendedCSS)
2020-07-28 10:06:05 +00:00
suspendedForm += '<div><center>\n'
suspendedForm += ' <p class="screentitle">Account Suspended</p>\n'
suspendedForm += ' <p>See <a href="/terms">Terms of Service</a></p>\n'
suspendedForm += '</center></div>\n'
2020-04-05 09:17:19 +00:00
suspendedForm += htmlFooter()
2019-08-13 09:24:55 +00:00
return suspendedForm
2020-04-05 09:17:19 +00:00
def htmlNewPost(mediaInstance: bool, translate: {},
baseDir: str, httpPrefix: str,
path: str, inReplyTo: str,
mentions: [],
reportUrl: str, pageNumber: int,
2020-06-29 09:48:46 +00:00
nickname: str, domain: str,
domainFull: str) -> str:
2019-08-19 19:50:07 +00:00
"""New post screen
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
replyStr = ''
2020-04-05 09:17:19 +00:00
showPublicOnDropdown = True
2020-08-24 10:30:33 +00:00
messageBoxHeight = 400
2019-07-28 11:35:57 +00:00
if not path.endswith('/newshare'):
2019-08-11 11:25:27 +00:00
if not path.endswith('/newreport'):
if not inReplyTo or path.endswith('/newreminder'):
2020-04-05 09:17:19 +00:00
newPostText = '<p class="new-post-text">' + \
2020-07-28 10:06:05 +00:00
translate['Write your post text below.'] + '</p>\n'
2019-08-11 11:25:27 +00:00
else:
2020-04-05 09:17:19 +00:00
newPostText = \
'<p class="new-post-text">' + \
translate['Write your reply to'] + \
' <a href="' + inReplyTo + '">' + \
2020-07-28 10:06:05 +00:00
translate['this post'] + '</a></p>\n'
2020-04-05 09:17:19 +00:00
replyStr = '<input type="hidden" ' + \
2020-09-19 09:44:22 +00:00
'name="replyTo" value="' + inReplyTo + '">\n'
2020-04-05 09:17:19 +00:00
# if replying to a non-public post then also make
# this post non-public
if not isPublicPostFromUrl(baseDir, nickname, domain,
inReplyTo):
newPostPath = path
if '?' in newPostPath:
2020-04-05 09:17:19 +00:00
newPostPath = newPostPath.split('?')[0]
if newPostPath.endswith('/newpost'):
2020-04-05 09:17:19 +00:00
path = path.replace('/newpost', '/newfollowers')
elif newPostPath.endswith('/newunlisted'):
2020-04-05 09:17:19 +00:00
path = path.replace('/newunlisted', '/newfollowers')
showPublicOnDropdown = False
2019-07-31 13:51:10 +00:00
else:
2020-04-05 09:17:19 +00:00
newPostText = \
'<p class="new-post-text">' + \
2020-07-28 10:06:05 +00:00
translate['Write your report below.'] + '</p>\n'
# custom report header with any additional instructions
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/accounts/report.txt'):
with open(baseDir + '/accounts/report.txt', 'r') as file:
customReportText = file.read()
if '</p>' not in customReportText:
2020-04-05 09:17:19 +00:00
customReportText = \
'<p class="login-subtext">' + \
2020-07-28 10:06:05 +00:00
customReportText + '</p>\n'
2020-04-05 09:17:19 +00:00
repStr = '<p class="login-subtext">'
customReportText = \
customReportText.replace('<p>', repStr)
newPostText += customReportText
idx = 'This message only goes to moderators, even if it ' + \
'mentions other fediverse addresses.'
newPostText += \
2020-07-28 10:06:05 +00:00
'<p class="new-post-subtext">' + translate[idx] + '</p>\n' + \
'<p class="new-post-subtext">' + translate['Also see'] + \
2020-04-05 09:17:19 +00:00
' <a href="/terms">' + \
2020-07-28 10:06:05 +00:00
translate['Terms of Service'] + '</a></p>\n'
2019-07-28 11:35:57 +00:00
else:
2020-04-05 09:17:19 +00:00
newPostText = \
'<p class="new-post-text">' + \
2020-07-28 10:06:05 +00:00
translate['Enter the details for your shared item below.'] + \
'</p>\n'
2019-11-25 20:46:52 +00:00
2019-11-28 12:38:37 +00:00
if path.endswith('/newquestion'):
2020-04-05 09:17:19 +00:00
newPostText = \
'<p class="new-post-text">' + \
2020-07-28 10:06:05 +00:00
translate['Enter the choices for your question below.'] + \
'</p>\n'
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/accounts/newpost.txt'):
with open(baseDir + '/accounts/newpost.txt', 'r') as file:
newPostText = \
2020-07-28 10:06:05 +00:00
'<p class="new-post-text">' + file.read() + '</p>\n'
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
newPostCSS = cssFile.read()
if httpPrefix != 'https':
newPostCSS = newPostCSS.replace('https://',
httpPrefix + '://')
2019-07-25 21:39:09 +00:00
if '?' in path:
2020-04-05 09:17:19 +00:00
path = path.split('?')[0]
pathBase = path.replace('/newreport', '').replace('/newpost', '')
pathBase = pathBase.replace('/newblog', '').replace('/newshare', '')
pathBase = pathBase.replace('/newunlisted', '')
2020-08-22 13:40:48 +00:00
pathBase = pathBase.replace('/newevent', '')
pathBase = pathBase.replace('/newreminder', '')
2020-04-05 09:17:19 +00:00
pathBase = pathBase.replace('/newfollowers', '').replace('/newdm', '')
newPostImageSection = ' <div class="container">'
2020-08-22 19:26:55 +00:00
if not path.endswith('/newevent'):
2020-08-22 19:23:48 +00:00
newPostImageSection += \
' <label class="labels">' + \
translate['Image description'] + '</label>\n'
else:
newPostImageSection += \
' <label class="labels">' + \
translate['Event banner image description'] + '</label>\n'
2020-07-28 10:06:05 +00:00
newPostImageSection += \
' <input type="text" name="imageDescription">\n'
2020-08-22 19:40:21 +00:00
if path.endswith('/newevent'):
newPostImageSection += \
' <label class="labels">' + \
translate['Banner image'] + '</label>\n'
newPostImageSection += \
' <input type="file" id="attachpic" name="attachpic"'
newPostImageSection += \
' accept=".png, .jpg, .jpeg, .gif, .webp, .avif">\n'
2020-08-22 19:40:21 +00:00
else:
newPostImageSection += \
' <input type="file" id="attachpic" name="attachpic"'
newPostImageSection += \
' accept=".png, .jpg, .jpeg, .gif, ' + \
'.webp, .avif, .mp4, .webm, .ogv, .mp3, .ogg">\n'
2020-07-28 10:06:05 +00:00
newPostImageSection += ' </div>\n'
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_public.png'
scopeDescription = translate['Public']
placeholderSubject = \
translate['Subject or Content Warning (optional)'] + '...'
placeholderMentions = ''
if inReplyTo:
# mentionsAndContent = getMentionsString(content)
placeholderMentions = \
translate['Replying to'] + '...'
2020-04-05 09:17:19 +00:00
placeholderMessage = translate['Write something'] + '...'
extraFields = ''
endpoint = 'newpost'
2020-02-24 13:32:19 +00:00
if path.endswith('/newblog'):
2020-04-05 09:17:19 +00:00
placeholderSubject = translate['Title']
scopeIcon = 'scope_blog.png'
scopeDescription = translate['Blog']
endpoint = 'newblog'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newunlisted'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_unlisted.png'
scopeDescription = translate['Unlisted']
endpoint = 'newunlisted'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newfollowers'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_followers.png'
scopeDescription = translate['Followers']
endpoint = 'newfollowers'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newdm'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_dm.png'
scopeDescription = translate['DM']
endpoint = 'newdm'
elif path.endswith('/newreminder'):
scopeIcon = 'scope_reminder.png'
scopeDescription = translate['Reminder']
endpoint = 'newreminder'
2020-08-22 13:40:48 +00:00
elif path.endswith('/newevent'):
scopeIcon = 'scope_event.png'
scopeDescription = translate['Event']
endpoint = 'newevent'
placeholderSubject = translate['Event name']
2020-08-22 13:40:48 +00:00
placeholderMessage = translate['Describe the event'] + '...'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newreport'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_report.png'
scopeDescription = translate['Report']
endpoint = 'newreport'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newquestion'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_question.png'
scopeDescription = translate['Question']
placeholderMessage = translate['Enter your question'] + '...'
endpoint = 'newquestion'
2020-07-28 10:06:05 +00:00
extraFields = '<div class="container">\n'
2020-04-05 09:17:19 +00:00
extraFields += ' <label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['Possible answers'] + ':</label><br>\n'
2019-11-26 12:17:52 +00:00
for questionCtr in range(8):
2020-04-05 09:17:19 +00:00
extraFields += \
' <input type="text" class="questionOption" placeholder="' + \
str(questionCtr + 1) + \
2020-07-28 10:06:05 +00:00
'" name="questionOption' + str(questionCtr) + '"><br>\n'
2020-04-05 09:17:19 +00:00
extraFields += \
' <label class="labels">' + \
translate['Duration of listing in days'] + \
':</label> <input type="number" name="duration" ' + \
2020-07-28 10:06:05 +00:00
'min="1" max="365" step="1" value="14"><br>\n'
2020-04-05 09:17:19 +00:00
extraFields += '</div>'
2020-02-24 13:32:19 +00:00
elif path.endswith('/newshare'):
2020-04-05 09:17:19 +00:00
scopeIcon = 'scope_share.png'
scopeDescription = translate['Shared Item']
placeholderSubject = translate['Name of the shared item'] + '...'
placeholderMessage = \
translate['Description of the item being shared'] + '...'
endpoint = 'newshare'
2020-07-28 10:06:05 +00:00
extraFields = '<div class="container">\n'
2020-04-05 09:17:19 +00:00
extraFields += \
' <label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['Type of shared item. eg. hat'] + ':</label>\n'
extraFields += \
' <input type="text" class="itemType" name="itemType">\n'
2020-04-05 09:17:19 +00:00
extraFields += \
' <br><label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['Category of shared item. eg. clothing'] + ':</label>\n'
extraFields += \
' <input type="text" class="category" name="category">\n'
2020-04-05 09:17:19 +00:00
extraFields += \
' <br><label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['Duration of listing in days'] + ':</label>\n'
2020-04-05 09:17:19 +00:00
extraFields += ' <input type="number" name="duration" ' + \
2020-07-28 10:06:05 +00:00
'min="1" max="365" step="1" value="14">\n'
extraFields += '</div>\n'
extraFields += '<div class="container">\n'
2020-04-05 09:17:19 +00:00
extraFields += \
'<label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['City or location of the shared item'] + ':</label>\n'
extraFields += '<input type="text" name="location">\n'
extraFields += '</div>\n'
2020-04-05 09:17:19 +00:00
dateAndLocation = ''
if endpoint != 'newshare' and \
endpoint != 'newreport' and \
endpoint != 'newquestion':
2020-08-22 16:59:45 +00:00
dateAndLocation = '<div class="container">\n'
2020-01-12 13:05:36 +00:00
2020-08-22 13:40:48 +00:00
if endpoint == 'newevent':
2020-08-22 18:02:43 +00:00
# event status
dateAndLocation += '<label class="labels">' + \
translate['Status of the event'] + ':</label><br>\n'
dateAndLocation += '<input type="radio" id="tentative" ' + \
2020-08-22 19:11:21 +00:00
'name="eventStatus" value="tentative">\n'
2020-08-22 18:02:43 +00:00
dateAndLocation += '<label class="labels" for="tentative">' + \
translate['Tentative'] + '</label><br>\n'
dateAndLocation += '<input type="radio" id="confirmed" ' + \
2020-08-22 19:11:21 +00:00
'name="eventStatus" value="confirmed" checked>\n'
2020-08-22 18:02:43 +00:00
dateAndLocation += '<label class="labels" for="confirmed">' + \
translate['Confirmed'] + '</label><br>\n'
dateAndLocation += '<input type="radio" id="cancelled" ' + \
'name="eventStatus" value="cancelled">\n'
dateAndLocation += '<label class="labels" for="cancelled">' + \
translate['Cancelled'] + '</label><br>\n'
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
2020-08-22 21:04:50 +00:00
# maximum attendees
2020-08-22 21:12:46 +00:00
dateAndLocation += '<label class="labels" ' + \
'for="maximumAttendeeCapacity">' + \
2020-08-22 21:04:50 +00:00
translate['Maximum attendees'] + ':</label>\n'
dateAndLocation += '<input type="number" ' + \
'id="maximumAttendeeCapacity" ' + \
2020-08-22 21:09:06 +00:00
'name="maximumAttendeeCapacity" min="1" max="999999" ' + \
'value="100">\n'
2020-08-22 21:04:50 +00:00
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
2020-08-22 16:59:45 +00:00
# event joining options
dateAndLocation += '<label class="labels">' + \
2020-08-22 18:02:43 +00:00
translate['Joining'] + ':</label><br>\n'
2020-08-22 16:59:45 +00:00
dateAndLocation += '<input type="radio" id="free" ' + \
'name="joinMode" value="free" checked>\n'
dateAndLocation += '<label class="labels" for="free">' + \
translate['Anyone can join'] + '</label><br>\n'
dateAndLocation += '<input type="radio" id="restricted" ' + \
'name="joinMode" value="restricted">\n'
dateAndLocation += '<label class="labels" for="female">' + \
translate['Apply to join'] + '</label><br>\n'
dateAndLocation += '<input type="radio" id="invite" ' + \
'name="joinMode" value="invite">\n'
dateAndLocation += '<label class="labels" for="other">' + \
translate['Invitation only'] + '</label>\n'
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
2020-08-22 13:40:48 +00:00
# Event posts don't allow replies - they're just an announcement.
# They also have a few more checkboxes
dateAndLocation += \
'<p><input type="checkbox" class="profilecheckbox" ' + \
'name="privateEvent"><label class="labels"> ' + \
translate['This is a private event.'] + '</label></p>\n'
dateAndLocation += \
'<p><input type="checkbox" class="profilecheckbox" ' + \
'name="anonymousParticipationEnabled">' + \
'<label class="labels"> ' + \
translate['Allow anonymous participation.'] + '</label></p>\n'
else:
dateAndLocation += \
'<p><input type="checkbox" class="profilecheckbox" ' + \
'name="commentsEnabled" checked><label class="labels"> ' + \
translate['Allow replies.'] + '</label></p>\n'
2020-08-21 19:51:35 +00:00
2020-08-22 13:40:48 +00:00
if not inReplyTo and endpoint != 'newevent':
2020-04-05 09:17:19 +00:00
dateAndLocation += \
'<p><input type="checkbox" class="profilecheckbox" ' + \
2020-07-03 19:57:42 +00:00
'name="schedulePost"><label class="labels"> ' + \
2020-07-28 10:06:05 +00:00
translate['This is a scheduled post.'] + '</label></p>\n'
2020-04-05 09:17:19 +00:00
2020-08-22 13:40:48 +00:00
if endpoint != 'newevent':
2020-08-22 16:43:25 +00:00
dateAndLocation += \
'<p><img loading="lazy" alt="" title="" ' + \
'class="emojicalendar" src="/' + \
iconsDir + '/calendar.png"/>\n'
2020-08-22 13:40:48 +00:00
# select a date and time for this post
dateAndLocation += '<label class="labels">' + \
translate['Date'] + ': </label>\n'
dateAndLocation += '<input type="date" name="eventDate">\n'
dateAndLocation += '<label class="labelsright">' + \
translate['Time'] + ':'
dateAndLocation += \
'<input type="time" name="eventTime"></label></p>\n'
else:
2020-08-22 19:07:19 +00:00
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
2020-08-22 16:43:25 +00:00
dateAndLocation += \
'<p><img loading="lazy" alt="" title="" ' + \
'class="emojicalendar" src="/' + \
iconsDir + '/calendar.png"/>\n'
2020-08-22 13:40:48 +00:00
# select start time for the event
dateAndLocation += '<label class="labels">' + \
translate['Start Date'] + ': </label>\n'
dateAndLocation += '<input type="date" name="eventDate">\n'
dateAndLocation += '<label class="labelsright">' + \
translate['Time'] + ':'
dateAndLocation += \
2020-08-22 15:11:41 +00:00
'<input type="time" name="eventTime"></label></p>\n'
2020-08-22 13:40:48 +00:00
# select end time for the event
dateAndLocation += \
2020-08-22 15:13:43 +00:00
'<br><img loading="lazy" alt="" title="" ' + \
2020-08-22 13:40:48 +00:00
'class="emojicalendar" src="/' + \
iconsDir + '/calendar.png"/>\n'
dateAndLocation += '<label class="labels">' + \
translate['End Date'] + ': </label>\n'
dateAndLocation += '<input type="date" name="endDate">\n'
dateAndLocation += '<label class="labelsright">' + \
2020-08-22 13:58:25 +00:00
translate['Time'] + ':'
2020-08-22 13:40:48 +00:00
dateAndLocation += \
2020-08-22 15:13:43 +00:00
'<input type="time" name="endTime"></label>\n'
2020-08-22 13:40:48 +00:00
2020-08-24 10:30:33 +00:00
if endpoint == 'newevent':
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
dateAndLocation += '<br><label class="labels">' + \
translate['Moderation policy or code of conduct'] + \
': </label>\n'
dateAndLocation += \
' <textarea id="message" ' + \
'name="repliesModerationOption" style="height:' + \
str(messageBoxHeight) + 'px"></textarea>\n'
2020-07-28 10:06:05 +00:00
dateAndLocation += '</div>\n'
dateAndLocation += '<div class="container">\n'
2020-04-05 09:17:19 +00:00
dateAndLocation += '<br><label class="labels">' + \
2020-07-28 10:06:05 +00:00
translate['Location'] + ': </label>\n'
dateAndLocation += '<input type="text" name="location">\n'
2020-08-22 13:40:48 +00:00
if endpoint == 'newevent':
2020-08-23 17:50:49 +00:00
dateAndLocation += '<br><label class="labels">' + \
translate['Ticket URL'] + ': </label>\n'
dateAndLocation += '<input type="text" name="ticketUrl">\n'
2020-08-22 13:40:48 +00:00
dateAndLocation += '<br><label class="labels">' + \
translate['Categories'] + ': </label>\n'
dateAndLocation += '<input type="text" name="category">\n'
2020-07-28 10:06:05 +00:00
dateAndLocation += '</div>\n'
2020-04-05 09:17:19 +00:00
newPostForm = htmlHeader(cssFilename, newPostCSS)
# only show the share option if this is not a reply
2020-04-05 09:17:19 +00:00
shareOptionOnDropdown = ''
questionOptionOnDropdown = ''
if not replyStr:
2020-04-05 09:17:19 +00:00
shareOptionOnDropdown = \
2020-06-25 22:15:01 +00:00
' <a href="' + pathBase + \
'/newshare"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/scope_share.png"/><b>' + translate['Shares'] + \
2020-06-25 22:15:01 +00:00
'</b><br>' + translate['Describe a shared item'] + '</li></a>\n'
2020-04-05 09:17:19 +00:00
questionOptionOnDropdown = \
2020-06-25 22:15:01 +00:00
' <a href="' + pathBase + \
'/newquestion"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/scope_question.png"/><b>' + translate['Question'] + \
2020-06-25 22:15:01 +00:00
'</b><br>' + translate['Ask a question'] + '</li></a>\n'
2020-04-05 09:17:19 +00:00
mentionsStr = ''
2019-08-05 19:13:15 +00:00
for m in mentions:
2020-04-05 09:17:19 +00:00
mentionNickname = getNicknameFromActor(m)
2019-08-05 19:13:15 +00:00
if not mentionNickname:
continue
2020-04-05 09:17:19 +00:00
mentionDomain, mentionPort = getDomainFromActor(m)
2019-08-05 19:13:15 +00:00
if not mentionDomain:
continue
2019-09-22 17:54:33 +00:00
if mentionPort:
2020-04-05 09:17:19 +00:00
mentionsHandle = \
'@' + mentionNickname + '@' + \
mentionDomain + ':' + str(mentionPort)
2019-08-05 19:13:15 +00:00
else:
2020-04-05 09:17:19 +00:00
mentionsHandle = '@' + mentionNickname + '@' + mentionDomain
2019-09-22 17:54:33 +00:00
if mentionsHandle not in mentionsStr:
2020-04-05 09:17:19 +00:00
mentionsStr += mentionsHandle + ' '
2019-08-05 19:13:15 +00:00
2020-02-23 15:32:47 +00:00
# build suffixes so that any replies or mentions are
# preserved when switching between scopes
2020-04-05 09:17:19 +00:00
dropdownNewPostSuffix = '/newpost'
dropdownNewBlogSuffix = '/newblog'
dropdownUnlistedSuffix = '/newunlisted'
dropdownFollowersSuffix = '/newfollowers'
dropdownDMSuffix = '/newdm'
2020-08-22 10:21:06 +00:00
dropdownEventSuffix = '/newevent'
dropdownReminderSuffix = '/newreminder'
2020-04-05 09:17:19 +00:00
dropdownReportSuffix = '/newreport'
if inReplyTo or mentions:
2020-04-05 09:17:19 +00:00
dropdownNewPostSuffix = ''
dropdownNewBlogSuffix = ''
dropdownUnlistedSuffix = ''
dropdownFollowersSuffix = ''
dropdownDMSuffix = ''
2020-08-22 10:21:06 +00:00
dropdownEventSuffix = ''
dropdownReminderSuffix = ''
2020-04-05 09:17:19 +00:00
dropdownReportSuffix = ''
if inReplyTo:
2020-04-05 09:17:19 +00:00
dropdownNewPostSuffix += '?replyto=' + inReplyTo
dropdownNewBlogSuffix += '?replyto=' + inReplyTo
dropdownUnlistedSuffix += '?replyto=' + inReplyTo
dropdownFollowersSuffix += '?replyfollowers=' + inReplyTo
dropdownDMSuffix += '?replydm=' + inReplyTo
for mentionedActor in mentions:
2020-04-05 09:17:19 +00:00
dropdownNewPostSuffix += '?mention=' + mentionedActor
dropdownNewBlogSuffix += '?mention=' + mentionedActor
dropdownUnlistedSuffix += '?mention=' + mentionedActor
dropdownFollowersSuffix += '?mention=' + mentionedActor
dropdownDMSuffix += '?mention=' + mentionedActor
dropdownReportSuffix += '?mention=' + mentionedActor
2020-06-25 20:02:16 +00:00
dropDownContent = ''
if not reportUrl:
2020-06-25 17:33:26 +00:00
dropDownContent += "<div class='msgscope-collapse collapse "
dropDownContent += "right desktoponly' id='msgscope'>\n"
dropDownContent += " <ul class='nav msgscope-nav msgscope-right'>\n"
dropDownContent += " <li class=' ' style='position: relative;'>\n"
dropDownContent += " <div class='toggle-msgScope button-msgScope'>\n"
dropDownContent += " <input id='toggleMsgScope' "
dropDownContent += "name='toggleMsgScope' type='checkbox'/>\n"
dropDownContent += " <label for='toggleMsgScope'>\n"
dropDownContent += " <div class='lined-thin'>\n"
dropDownContent += ' <img loading="lazy" alt="" title="" src="/'
dropDownContent += iconsDir + '/' + scopeIcon
dropDownContent += '"/><b class="scope-desc">'
dropDownContent += scopeDescription + '</b>\n'
dropDownContent += " <span class='caret'/>\n"
dropDownContent += " </div>\n"
dropDownContent += " </label>\n"
dropDownContent += " <div class='toggle-inside'>\n"
dropDownContent += " <ul aria-labelledby='dropdownMsgScope' "
dropDownContent += "class='dropdown-menutoggle'>\n"
if showPublicOnDropdown:
2020-06-25 22:15:01 +00:00
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownNewPostSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/scope_public.png"/><b>' + \
translate['Public'] + '</b><br>' + \
2020-06-25 22:15:01 +00:00
translate['Visible to anyone'] + '</li></a>\n'
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownNewBlogSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/edit.png"/><b>' + \
translate['Blog'] + '</b><br>' + \
2020-06-25 22:15:01 +00:00
translate['Publicly visible post'] + '</li></a>\n'
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownUnlistedSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir+'/scope_unlisted.png"/><b>' + \
translate['Unlisted'] + '</b><br>' + \
2020-06-25 22:15:01 +00:00
translate['Not on public timeline'] + '</li></a>\n'
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownFollowersSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/scope_followers.png"/><b>' + \
translate['Followers'] + '</b><br>' + \
2020-06-25 22:15:01 +00:00
translate['Only to followers'] + '</li></a>\n'
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownDMSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
2020-04-05 09:17:19 +00:00
iconsDir + '/scope_dm.png"/><b>' + translate['DM'] + \
2020-06-25 17:33:26 +00:00
'</b><br>' + translate['Only to mentioned people'] + \
2020-06-25 22:15:01 +00:00
'</li></a>\n'
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownReminderSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_reminder.png"/><b>' + translate['Reminder'] + \
2020-06-25 17:33:26 +00:00
'</b><br>' + translate['Scheduled note to yourself'] + \
2020-06-25 22:15:01 +00:00
'</li></a>\n'
2020-08-22 10:21:06 +00:00
dropDownContent += " " \
'<a href="' + pathBase + dropdownEventSuffix + \
'"><li><img loading="lazy" alt="" title="" src="/' + \
iconsDir + '/scope_event.png"/><b>' + translate['Event'] + \
'</b><br>' + translate['Create an event'] + \
'</li></a>\n'
2020-06-25 22:15:01 +00:00
dropDownContent += " " \
2020-06-25 17:33:26 +00:00
'<a href="' + pathBase + dropdownReportSuffix + \
2020-06-25 22:15:01 +00:00
'"><li><img loading="lazy" alt="" title="" src="/' + iconsDir + \
2020-04-05 09:17:19 +00:00
'/scope_report.png"/><b>' + translate['Report'] + \
2020-06-25 22:15:01 +00:00
'</b><br>' + translate['Send to moderators'] + '</li></a>\n'
2020-04-05 09:17:19 +00:00
dropDownContent += questionOptionOnDropdown + shareOptionOnDropdown
2020-06-25 17:33:26 +00:00
dropDownContent += ' </ul>\n'
dropDownContent += ' </div>\n'
dropDownContent += ' </div>\n'
dropDownContent += ' </li>\n'
dropDownContent += ' </ul>\n'
2020-06-25 22:08:28 +00:00
dropDownContent += '</div>\n'
else:
2020-04-05 09:17:19 +00:00
mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr
newPostForm += \
'<form enctype="multipart/form-data" method="POST" ' + \
'accept-charset="UTF-8" action="' + \
2020-06-26 13:07:01 +00:00
path + '?' + endpoint + '?page=' + str(pageNumber) + '">\n'
newPostForm += ' <div class="vertical-center">\n'
2020-04-05 09:17:19 +00:00
newPostForm += \
2020-06-26 13:07:01 +00:00
' <label for="nickname"><b>' + newPostText + '</b></label>\n'
2020-06-25 17:33:26 +00:00
newPostForm += ' <div class="container">\n'
2020-06-26 13:55:57 +00:00
newPostForm += ' <table style="width:100%" border="0"><tr>\n'
2020-06-26 13:16:23 +00:00
newPostForm += '<td>' + dropDownContent + '</td>\n'
2020-04-05 09:17:19 +00:00
newPostForm += \
2020-06-26 13:16:23 +00:00
' <td><a href="' + pathBase + \
2020-04-05 09:17:19 +00:00
'/searchemoji"><img loading="lazy" class="emojisearch" ' + \
'src="/emoji/1F601.png" title="' + \
translate['Search for emoji'] + '" alt="' + \
2020-06-26 13:16:23 +00:00
translate['Search for emoji'] + '"/></a></td>\n'
2020-06-28 20:47:13 +00:00
newPostForm += ' </tr>\n'
2020-06-28 20:49:13 +00:00
newPostForm += '</table>\n'
2020-06-25 17:33:26 +00:00
newPostForm += ' </div>\n'
newPostForm += ' <div class="container"><center>\n'
2020-04-05 09:17:19 +00:00
newPostForm += \
' <a href="' + pathBase + \
'/inbox"><button class="cancelbtn">' + \
translate['Go Back'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
newPostForm += \
' <input type="submit" name="submitPost" value="' + \
2020-06-25 17:33:26 +00:00
translate['Submit'] + '">\n'
newPostForm += ' </center></div>\n'
2020-04-05 09:17:19 +00:00
newPostForm += replyStr
if mediaInstance and not replyStr:
2020-04-05 09:17:19 +00:00
newPostForm += newPostImageSection
2020-04-05 09:17:19 +00:00
newPostForm += \
' <label class="labels">' + placeholderSubject + '</label><br>'
newPostForm += ' <input type="text" name="subject">'
newPostForm += ''
selectedStr = ' selected'
2020-06-26 10:23:14 +00:00
if inReplyTo or endpoint == 'newdm':
if inReplyTo:
newPostForm += \
' <label class="labels">' + placeholderMentions + \
2020-06-29 09:34:22 +00:00
'</label><br>\n'
2020-06-26 10:23:14 +00:00
else:
newPostForm += \
2020-06-28 20:54:36 +00:00
' <a href="/users/' + nickname + \
2020-06-28 21:11:52 +00:00
'/followingaccounts" title="' + \
translate['Show a list of addresses to send to'] + '">' \
'<label class="labels">' + \
2020-06-29 09:34:22 +00:00
translate['Send to'] + ':' + '</label> 📄</a><br>\n'
newPostForm += \
2020-06-29 08:54:47 +00:00
' <input type="text" name="mentions" ' + \
2020-06-29 09:34:22 +00:00
'list="followingHandles" value="' + mentionsStr + '" selected>\n'
2020-06-29 09:48:46 +00:00
newPostForm += \
htmlFollowingDataList(baseDir, nickname, domain, domainFull)
newPostForm += ''
selectedStr = ''
2020-04-05 09:17:19 +00:00
newPostForm += \
' <br><label class="labels">' + placeholderMessage + '</label>'
if mediaInstance:
2020-04-05 09:17:19 +00:00
messageBoxHeight = 200
2020-02-24 13:35:20 +00:00
2020-04-05 09:17:19 +00:00
if endpoint == 'newquestion':
messageBoxHeight = 100
elif endpoint == 'newblog':
messageBoxHeight = 800
2020-02-24 13:35:20 +00:00
2020-04-05 09:17:19 +00:00
newPostForm += \
' <textarea id="message" name="message" style="height:' + \
str(messageBoxHeight) + 'px"' + selectedStr + '></textarea>\n'
2020-04-05 09:17:19 +00:00
newPostForm += extraFields+dateAndLocation
if not mediaInstance or replyStr:
2020-04-05 09:17:19 +00:00
newPostForm += newPostImageSection
2020-06-25 17:33:26 +00:00
newPostForm += ' </div>\n'
newPostForm += '</form>\n'
2019-08-24 18:00:15 +00:00
if not reportUrl:
2020-04-05 09:17:19 +00:00
newPostForm = \
newPostForm.replace('<body>', '<body onload="focusOnMessage()">')
2019-08-24 18:00:15 +00:00
2020-04-05 09:17:19 +00:00
newPostForm += htmlFooter()
2019-07-25 21:39:09 +00:00
return newPostForm
2020-04-05 09:17:19 +00:00
2020-07-12 18:33:20 +00:00
def getFontFromCss(css: str) -> (str, str):
"""Returns the font name and format
"""
2020-07-12 20:49:09 +00:00
if ' url(' not in css:
2020-07-12 18:33:20 +00:00
return None, None
2020-07-18 17:38:58 +00:00
fontName = css.split(" url(")[1].split(")")[0].replace("'", '')
2020-07-12 18:33:20 +00:00
fontFormat = css.split(" format('")[1].split("')")[0]
return fontName, fontFormat
2020-06-19 09:13:41 +00:00
def htmlHeader(cssFilename: str, css: str, lang='en') -> str:
htmlStr = '<!DOCTYPE html>\n'
htmlStr += '<html lang="' + lang + '">\n'
2020-07-12 18:53:58 +00:00
htmlStr += ' <head>\n'
htmlStr += ' <meta charset="utf-8">\n'
2020-07-12 18:33:20 +00:00
fontName, fontFormat = getFontFromCss(css)
if fontName:
2020-07-12 18:53:58 +00:00
htmlStr += ' <link rel="preload" as="font" type="' + \
2020-07-12 18:42:42 +00:00
fontFormat + '" href="' + fontName + '" crossorigin>\n'
2020-07-12 18:53:58 +00:00
htmlStr += ' <style>\n' + css + '</style>\n'
2020-08-13 18:45:41 +00:00
htmlStr += ' <link rel="manifest" href="/manifest.json">\n'
2020-08-13 18:51:33 +00:00
htmlStr += ' <meta name="theme-color" content="grey">\n'
2020-10-01 09:59:57 +00:00
htmlStr += ' <title>Epicyon</title>\n'
2020-07-12 18:53:58 +00:00
htmlStr += ' </head>\n'
2020-06-19 09:13:41 +00:00
htmlStr += ' <body>\n'
2019-07-20 21:13:36 +00:00
return htmlStr
2020-04-05 09:17:19 +00:00
2019-07-20 21:13:36 +00:00
def htmlFooter() -> str:
2020-04-05 09:17:19 +00:00
htmlStr = ' </body>\n'
htmlStr += '</html>\n'
2019-07-20 21:13:36 +00:00
return htmlStr
2020-04-05 09:17:19 +00:00
def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, httpPrefix: str,
2020-09-27 19:27:24 +00:00
authorized: bool,
2020-04-05 09:17:19 +00:00
nickname: str, domain: str, port: int,
session, wfRequest: {}, personCache: {},
projectVersion: str,
YTReplacementDomain: str) -> str:
2019-07-22 09:38:02 +00:00
"""Shows posts on the profile screen
2019-09-02 11:59:15 +00:00
These should only be public posts
2019-07-22 09:38:02 +00:00
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
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',
2020-09-27 19:27:24 +00:00
authorized)
2019-09-02 18:58:20 +00:00
if not outboxFeed:
break
2020-04-05 09:17:19 +00:00
if len(outboxFeed['orderedItems']) == 0:
2019-09-02 18:58:20 +00:00
break
for item in outboxFeed['orderedItems']:
2020-04-05 09:17:19 +00:00
if item['type'] == 'Create':
postStr = \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None,
baseDir, session, wfRequest,
personCache,
nickname, domain, port, item,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, False, False, True, False)
2019-09-02 18:58:20 +00:00
if postStr:
2020-04-05 09:17:19 +00:00
profileStr += postStr
ctr += 1
if ctr >= maxItems:
2019-09-02 18:58:20 +00:00
break
2020-04-05 09:17:19 +00:00
currPage += 1
2019-07-22 09:38:02 +00:00
return profileStr
2020-04-05 09:17:19 +00:00
def htmlProfileFollowing(translate: {}, baseDir: str, httpPrefix: str,
2020-09-27 19:27:24 +00:00
authorized: bool,
2020-04-05 09:17:19 +00:00
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
"""
2020-04-05 09:17:19 +00:00
profileStr = ''
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
if authorized and pageNumber:
2020-04-05 09:17:19 +00:00
if authorized and pageNumber > 1:
# page up arrow
2020-04-05 09:17:19 +00:00
profileStr += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + '/' + feedName + \
2020-04-05 09:17:19 +00:00
'?page=' + str(pageNumber - 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pageup.png" title="' + \
translate['Page up'] + '" alt="' + \
2020-10-01 11:00:56 +00:00
translate['Page up'] + '"></a>\n' + \
2020-10-01 13:01:49 +00:00
' </center>\n'
2020-02-23 15:32:47 +00:00
for item in followingJson['orderedItems']:
2020-04-05 09:17:19 +00:00
profileStr += \
individualFollowAsHtml(translate, baseDir, session,
wfRequest, personCache,
domain, item, authorized, nickname,
httpPrefix, projectVersion,
2019-08-14 20:12:27 +00:00
buttons)
if authorized and maxItemsPerPage and pageNumber:
2020-04-05 09:17:19 +00:00
if len(followingJson['orderedItems']) >= maxItemsPerPage:
# page down arrow
2020-04-05 09:17:19 +00:00
profileStr += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + '/' + feedName + \
2020-04-05 09:17:19 +00:00
'?page=' + str(pageNumber + 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pagedown.png" title="' + \
translate['Page down'] + '" alt="' + \
2020-10-01 11:00:56 +00:00
translate['Page down'] + '"></a>\n' + \
2020-10-01 13:01:49 +00:00
' </center>\n'
return profileStr
2020-04-05 09:17:19 +00:00
def htmlProfileRoles(translate: {}, nickname: str, domain: str,
rolesJson: {}) -> str:
2019-07-22 17:21:45 +00:00
"""Shows roles on the profile screen
"""
2020-04-05 09:17:19 +00:00
profileStr = ''
for project, rolesList in rolesJson.items():
profileStr += \
2020-07-28 10:06:05 +00:00
'<div class="roles">\n<h2>' + project + \
'</h2>\n<div class="roles-inner">\n'
2019-07-22 17:21:45 +00:00
for role in rolesList:
2020-07-28 10:06:05 +00:00
profileStr += '<h3>' + role + '</h3>\n'
profileStr += '</div></div>\n'
2020-04-05 09:17:19 +00:00
if len(profileStr) == 0:
profileStr += \
2020-07-28 10:06:05 +00:00
'<p>@' + nickname + '@' + domain + ' has no roles assigned</p>\n'
2019-07-22 17:21:45 +00:00
else:
2020-07-28 10:06:05 +00:00
profileStr = '<div>' + profileStr + '</div>\n'
2019-07-22 17:21:45 +00:00
return profileStr
2020-04-05 09:17:19 +00:00
def htmlProfileSkills(translate: {}, nickname: str, domain: str,
2020-02-23 15:32:47 +00:00
skillsJson: {}) -> str:
2019-07-22 20:01:46 +00:00
"""Shows skills on the profile screen
"""
2020-04-05 09:17:19 +00:00
profileStr = ''
for skill, level in skillsJson.items():
profileStr += \
'<div>' + skill + \
'<br><div id="myProgress"><div id="myBar" style="width:' + \
2020-07-28 10:06:05 +00:00
str(level) + '%"></div></div></div>\n<br>\n'
2020-04-05 09:17:19 +00:00
if len(profileStr) > 0:
profileStr = '<center><div class="skill-title">' + \
2020-07-28 10:06:05 +00:00
profileStr + '</div></center>\n'
2019-07-22 20:01:46 +00:00
return profileStr
2020-04-05 09:17:19 +00:00
def htmlIndividualShare(actor: str, item: {}, translate: {},
showContact: bool, removeButton: bool) -> str:
2019-11-02 14:19:51 +00:00
"""Returns an individual shared item as html
"""
2020-07-28 10:06:05 +00:00
profileStr = '<div class="container">\n'
profileStr += '<p class="share-title">' + item['displayName'] + '</p>\n'
2019-11-02 14:19:51 +00:00
if item.get('imageUrl'):
2020-07-28 10:06:05 +00:00
profileStr += '<a href="' + item['imageUrl'] + '">\n'
2020-04-05 09:17:19 +00:00
profileStr += \
'<img loading="lazy" src="' + item['imageUrl'] + \
2020-07-28 10:06:05 +00:00
'" alt="' + translate['Item image'] + '">\n</a>\n'
profileStr += '<p>' + item['summary'] + '</p>\n'
2020-04-05 09:17:19 +00:00
profileStr += \
'<p><b>' + translate['Type'] + ':</b> ' + item['itemType'] + ' '
profileStr += \
'<b>' + translate['Category'] + ':</b> ' + item['category'] + ' '
profileStr += \
2020-07-28 10:06:05 +00:00
'<b>' + translate['Location'] + ':</b> ' + item['location'] + '</p>\n'
2019-11-02 14:19:51 +00:00
if showContact:
2020-04-05 09:17:19 +00:00
contactActor = item['actor']
profileStr += \
'<p><a href="' + actor + \
'?replydm=sharedesc:' + item['displayName'] + \
'?mention=' + contactActor + '"><button class="button">' + \
2020-07-28 10:06:05 +00:00
translate['Contact'] + '</button></a>\n'
if removeButton:
2020-04-05 09:17:19 +00:00
profileStr += \
' <a href="' + actor + '?rmshare=' + item['displayName'] + \
'"><button class="button">' + \
2020-07-28 10:06:05 +00:00
translate['Remove'] + '</button></a>\n'
profileStr += '</div>\n'
2019-11-02 14:19:51 +00:00
return profileStr
2020-04-05 09:17:19 +00:00
def htmlProfileShares(actor: str, translate: {},
nickname: str, domain: str, sharesJson: {}) -> str:
2019-07-23 12:33:09 +00:00
"""Shows shares on the profile screen
"""
2020-04-05 09:17:19 +00:00
profileStr = ''
2019-07-23 12:33:09 +00:00
for item in sharesJson['orderedItems']:
2020-04-05 09:17:19 +00:00
profileStr += htmlIndividualShare(actor, item, translate, False, False)
if len(profileStr) > 0:
2020-07-28 10:06:05 +00:00
profileStr = '<div class="share-title">' + profileStr + '</div>\n'
2019-07-23 12:33:09 +00:00
return profileStr
2020-04-05 09:17:19 +00:00
def sharesTimelineJson(actor: str, pageNumber: int, itemsPerPage: int,
baseDir: str, maxSharesPerAccount: int) -> ({}, bool):
2019-11-02 14:19:51 +00:00
"""Get a page on the shared items timeline as json
maxSharesPerAccount helps to avoid one person dominating the timeline
by sharing a large number of things
"""
2020-04-05 09:17:19 +00:00
allSharesJson = {}
for subdir, dirs, files in os.walk(baseDir + '/accounts'):
2019-11-02 14:19:51 +00:00
for handle in dirs:
if '@' in handle:
2020-04-05 09:17:19 +00:00
accountDir = baseDir + '/accounts/' + handle
sharesFilename = accountDir + '/shares.json'
2019-11-02 14:19:51 +00:00
if os.path.isfile(sharesFilename):
2020-04-05 09:17:19 +00:00
sharesJson = loadJson(sharesFilename)
2019-11-02 14:19:51 +00:00
if not sharesJson:
continue
2020-04-05 09:17:19 +00:00
nickname = handle.split('@')[0]
2019-11-03 09:36:04 +00:00
# actor who owns this share
2020-04-05 09:17:19 +00:00
owner = actor.split('/users/')[0] + '/users/' + nickname
ctr = 0
for itemID, item in sharesJson.items():
2019-11-03 09:36:04 +00:00
# assign owner to the item
2020-04-05 09:17:19 +00:00
item['actor'] = owner
allSharesJson[str(item['published'])] = item
ctr += 1
if ctr >= maxSharesPerAccount:
2019-11-02 14:19:51 +00:00
break
# sort the shared items in descending order of publication date
2020-04-05 09:17:19 +00:00
sharesJson = OrderedDict(sorted(allSharesJson.items(), reverse=True))
lastPage = False
startIndex = itemsPerPage*pageNumber
maxIndex = len(sharesJson.items())
if maxIndex < itemsPerPage:
lastPage = True
if startIndex >= maxIndex - itemsPerPage:
lastPage = True
startIndex = maxIndex - itemsPerPage
if startIndex < 0:
startIndex = 0
ctr = 0
resultJson = {}
for published, item in sharesJson.items():
if ctr >= startIndex + itemsPerPage:
2020-03-22 21:16:02 +00:00
break
2020-04-05 09:17:19 +00:00
if ctr < startIndex:
ctr += 1
2019-11-02 14:19:51 +00:00
continue
2020-04-05 09:17:19 +00:00
resultJson[published] = item
ctr += 1
return resultJson, lastPage
def htmlSharesTimeline(translate: {}, pageNumber: int, itemsPerPage: int,
baseDir: str, actor: str,
nickname: str, domain: str, port: int,
maxSharesPerAccount: int, httpPrefix: str) -> str:
2019-11-02 14:19:51 +00:00
"""Show shared items timeline as html
"""
2020-04-05 09:17:19 +00:00
sharesJson, lastPage = \
sharesTimelineJson(actor, pageNumber, itemsPerPage,
baseDir, maxSharesPerAccount)
domainFull = domain
if port != 80 and port != 443:
2019-11-02 14:19:51 +00:00
if ':' not in domain:
2020-04-05 09:17:19 +00:00
domainFull = domain + ':' + str(port)
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
timelineStr = ''
if pageNumber > 1:
iconsDir = getIconsDir(baseDir)
timelineStr += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + '/tlshares?page=' + \
2020-04-05 09:17:19 +00:00
str(pageNumber - 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pageup.png" title="' + translate['Page up'] + \
2020-10-01 11:00:56 +00:00
'" alt="' + translate['Page up'] + '"></a>\n' + \
2020-10-01 13:01:49 +00:00
' </center>\n'
2020-04-05 09:17:19 +00:00
for published, item in sharesJson.items():
showContactButton = False
if item['actor'] != actor:
showContactButton = True
showRemoveButton = False
if item['actor'] == actor:
showRemoveButton = True
timelineStr += \
htmlIndividualShare(actor, item, translate,
showContactButton, showRemoveButton)
2019-11-02 14:19:51 +00:00
if not lastPage:
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
timelineStr += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + actor + '/tlshares?page=' + \
2020-04-05 09:17:19 +00:00
str(pageNumber + 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pagedown.png" title="' + translate['Page down'] + \
2020-10-01 11:00:56 +00:00
'" alt="' + translate['Page down'] + '"></a>\n' + \
2020-10-01 13:01:49 +00:00
' </center>\n'
2020-03-22 21:16:02 +00:00
2019-11-02 14:19:51 +00:00
return timelineStr
2020-04-05 09:17:19 +00:00
def htmlProfile(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, projectVersion: str,
baseDir: str, httpPrefix: str, authorized: bool,
2020-09-27 19:27:24 +00:00
profileJson: {}, selected: str,
2020-04-05 09:17:19 +00:00
session, wfRequest: {}, personCache: {},
YTReplacementDomain: str,
2020-04-05 09:17:19 +00:00
extraJson=None,
pageNumber=None, maxItemsPerPage=None) -> str:
2019-07-20 21:13:36 +00:00
"""Show the profile page as html
"""
2020-04-05 09:17:19 +00:00
nickname = profileJson['preferredUsername']
2019-07-21 18:18:58 +00:00
if not nickname:
return ""
2020-04-05 09:17:19 +00:00
domain, port = getDomainFromActor(profileJson['id'])
2019-07-21 18:18:58 +00:00
if not domain:
return ""
2020-04-05 09:17:19 +00:00
displayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
profileJson['name'], True)
domainFull = domain
2019-07-21 18:18:58 +00:00
if port:
2020-04-05 09:17:19 +00:00
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)
2020-07-06 10:14:41 +00:00
PGPfingerprint = getPGPfingerprint(profileJson)
2020-04-05 09:17:19 +00:00
emailAddress = getEmailAddress(profileJson)
xmppAddress = getXmppAddress(profileJson)
matrixAddress = getMatrixAddress(profileJson)
ssbAddress = getSSBAddress(profileJson)
toxAddress = getToxAddress(profileJson)
2020-02-23 15:32:47 +00:00
if donateUrl or xmppAddress or matrixAddress or \
2020-07-06 10:14:41 +00:00
ssbAddress or toxAddress or PGPpubKey or \
PGPfingerprint or emailAddress:
2020-04-05 09:17:19 +00:00
donateSection = '<div class="container">\n'
donateSection += ' <center>\n'
2019-12-17 14:57:16 +00:00
if donateUrl:
2020-04-05 09:17:19 +00:00
donateSection += \
' <p><a href="' + donateUrl + \
'"><button class="donateButton">' + translate['Donate'] + \
'</button></a></p>\n'
if emailAddress:
2020-04-05 09:17:19 +00:00
donateSection += \
'<p>' + translate['Email'] + ': <a href="mailto:' + \
emailAddress + '">' + emailAddress + '</a></p>\n'
2019-12-17 14:57:16 +00:00
if xmppAddress:
2020-04-05 09:17:19 +00:00
donateSection += \
'<p>' + translate['XMPP'] + ': <a href="xmpp:' + \
xmppAddress + '">'+xmppAddress + '</a></p>\n'
2019-12-17 15:25:34 +00:00
if matrixAddress:
2020-04-05 09:17:19 +00:00
donateSection += \
'<p>' + translate['Matrix'] + ': ' + matrixAddress + '</p>\n'
2020-02-26 14:35:17 +00:00
if ssbAddress:
2020-04-05 09:17:19 +00:00
donateSection += \
'<p>SSB: <label class="ssbaddr">' + \
ssbAddress + '</label></p>\n'
2020-03-22 14:42:26 +00:00
if toxAddress:
2020-04-05 09:17:19 +00:00
donateSection += \
2020-08-14 12:50:51 +00:00
'<p>Tox: <label class="toxaddr">' + \
2020-04-05 09:17:19 +00:00
toxAddress + '</label></p>\n'
2020-07-06 10:14:41 +00:00
if PGPfingerprint:
donateSection += \
'<p class="pgp">PGP: ' + \
PGPfingerprint.replace('\n', '<br>') + '</p>\n'
if PGPpubKey:
2020-04-05 09:17:19 +00:00
donateSection += \
'<p class="pgp">' + PGPpubKey.replace('\n', '<br>') + '</p>\n'
donateSection += ' </center>\n'
donateSection += '</div>\n'
2019-11-07 09:52:00 +00:00
2019-07-28 15:52:59 +00:00
if not authorized:
2020-04-05 09:17:19 +00:00
loginButton = \
'<br><a href="/login"><button class="loginButton">' + \
translate['Login'] + '</button></a>'
2019-07-29 18:48:23 +00:00
else:
2020-04-05 09:17:19 +00:00
editProfileStr = \
'<a href="' + usersPath + \
'/editprofile"><button class="button"><span>' + \
translate['Edit'] + ' </span></button></a>'
logoutStr = \
'<a href="/logout"><button class="button"><span>' + \
translate['Logout'] + ' </span></button></a>'
linkToTimelineStart = \
'<a href="/users/' + nickname + '/' + defaultTimeline + \
'"><label class="transparent">' + \
translate['Switch to timeline view'] + '</label></a>'
linkToTimelineStart += \
'<a href="/users/' + nickname + '/' + defaultTimeline + \
'" title="' + translate['Switch to timeline view'] + \
'" alt="' + translate['Switch to timeline view'] + '">'
linkToTimelineEnd = '</a>'
2019-07-29 18:48:23 +00:00
# are there any follow requests?
2020-04-05 09:17:19 +00:00
followRequestsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/followrequests.txt'
2019-07-29 18:48:23 +00:00
if os.path.isfile(followRequestsFilename):
2020-04-05 09:17:19 +00:00
with open(followRequestsFilename, 'r') as f:
2019-07-29 18:48:23 +00:00
for line in f:
2020-04-05 09:17:19 +00:00
if len(line) > 0:
followApprovals = True
followersButton = 'buttonhighlighted'
if selected == 'followers':
followersButton = 'buttonselectedhighlighted'
2019-07-29 18:48:23 +00:00
break
2020-04-05 09:17:19 +00:00
if selected == 'followers':
if followApprovals:
2020-04-05 09:17:19 +00:00
with open(followRequestsFilename, 'r') as f:
2019-07-29 18:48:23 +00:00
for followerHandle in f:
2020-04-05 09:17:19 +00:00
if len(line) > 0:
2019-07-29 18:48:23 +00:00
if '://' in followerHandle:
2020-04-05 09:17:19 +00:00
followerActor = followerHandle
2019-07-29 18:48:23 +00:00
else:
2020-04-05 09:17:19 +00:00
followerActor = \
httpPrefix + '://' + \
followerHandle.split('@')[1] + \
'/users/' + followerHandle.split('@')[0]
2020-07-15 14:06:57 +00:00
basePath = '/users/' + nickname
2020-04-05 09:17:19 +00:00
followApprovalsSection += '<div class="container">'
followApprovalsSection += \
'<a href="' + followerActor + '">'
followApprovalsSection += \
'<span class="followRequestHandle">' + \
followerHandle + '</span></a>'
followApprovalsSection += \
'<a href="' + basePath + \
'/followapprove=' + followerHandle + '">'
followApprovalsSection += \
'<button class="followApprove">' + \
translate['Approve'] + '</button></a><br><br>'
2020-04-05 09:17:19 +00:00
followApprovalsSection += \
'<a href="' + basePath + \
'/followdeny=' + followerHandle + '">'
followApprovalsSection += \
'<button class="followDeny">' + \
translate['Deny'] + '</button></a>'
followApprovalsSection += '</div>'
profileDescriptionShort = profileDescription
2019-10-23 14:27:43 +00:00
if '\n' in profileDescription:
2020-04-05 09:17:19 +00:00
if len(profileDescription.split('\n')) > 2:
profileDescriptionShort = ''
2019-10-23 14:27:43 +00:00
else:
if '<br>' in profileDescription:
2020-04-05 09:17:19 +00:00
if len(profileDescription.split('<br>')) > 2:
profileDescriptionShort = ''
profileDescription = profileDescription.replace('<br>', '\n')
2019-10-23 15:09:20 +00:00
# keep the profile description short
2020-04-05 09:17:19 +00:00
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
2019-10-23 15:09:20 +00:00
# remove formatting from profile description used on title
2020-04-05 09:17:19 +00:00
avatarDescription = ''
if profileJson.get('summary'):
2020-04-05 09:17:19 +00:00
avatarDescription = profileJson['summary'].replace('<br>', '\n')
avatarDescription = avatarDescription.replace('<p>', '')
avatarDescription = avatarDescription.replace('</p>', '')
profileHeaderStr = '<div class="hero-image">'
profileHeaderStr += ' <div class="hero-text">'
profileHeaderStr += \
' <img loading="lazy" src="' + profileJson['icon']['url'] + \
'" title="' + avatarDescription + '" alt="' + \
avatarDescription + '" class="title">'
profileHeaderStr += ' <h1>' + displayName + '</h1>'
iconsDir = getIconsDir(baseDir)
2020-04-05 09:17:19 +00:00
profileHeaderStr += \
2020-06-21 16:57:23 +00:00
'<p><b>@' + nickname + '@' + domainFull + '</b><br>'
profileHeaderStr += \
'<a href="/users/' + nickname + \
2020-06-21 19:19:17 +00:00
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
translate['QR Code'] + '">' + \
2020-06-21 16:43:53 +00:00
'<img class="qrcode" src="/' + iconsDir + '/qrcode.png" /></a></p>'
2020-04-05 09:17:19 +00:00
profileHeaderStr += ' <p>' + profileDescriptionShort + '</p>'
profileHeaderStr += loginButton
profileHeaderStr += ' </div>'
profileHeaderStr += '</div>'
profileStr = \
2020-02-23 15:32:47 +00:00
linkToTimelineStart + profileHeaderStr + \
linkToTimelineEnd + donateSection
profileStr += '<div class="container" id="buttonheader">\n'
2020-04-05 09:17:19 +00:00
profileStr += ' <center>'
profileStr += \
' <a href="' + usersPath + '#buttonheader"><button class="' + \
postsButton + '"><span>' + translate['Posts'] + \
' </span></button></a>'
2020-04-05 09:17:19 +00:00
profileStr += \
' <a href="' + usersPath + '/following#buttonheader">' + \
'<button class="' + followingButton + '"><span>' + \
translate['Following'] + ' </span></button></a>'
2020-04-05 09:17:19 +00:00
profileStr += \
' <a href="' + usersPath + '/followers#buttonheader">' + \
'<button class="' + followersButton + \
'"><span>' + translate['Followers'] + ' </span></button></a>'
2020-04-05 09:17:19 +00:00
profileStr += \
' <a href="' + usersPath + '/roles#buttonheader">' + \
'<button class="' + rolesButton + '"><span>' + translate['Roles'] + \
' </span></button></a>'
2020-04-05 09:17:19 +00:00
profileStr += \
' <a href="' + usersPath + '/skills#buttonheader">' + \
'<button class="' + skillsButton + '"><span>' + \
translate['Skills'] + ' </span></button></a>'
profileStr += \
' <a href="' + usersPath + '/shares#buttonheader">' + \
'<button class="' + sharesButton + '"><span>' + \
translate['Shares'] + ' </span></button></a>'
2020-04-05 09:17:19 +00:00
profileStr += editProfileStr + logoutStr
profileStr += ' </center>'
profileStr += '</div>'
2019-07-21 18:18:58 +00:00
2020-04-05 09:17:19 +00:00
profileStr += followApprovalsSection
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = \
cssFile.read().replace('image.png',
2020-02-23 15:32:47 +00:00
profileJson['image']['url'])
2019-07-21 22:38:44 +00:00
2020-04-05 09:17:19 +00:00
licenseStr = \
'<a href="https://gitlab.com/bashrc2/epicyon">' + \
'<img loading="lazy" class="license" alt="' + \
translate['Get the source code'] + '" title="' + \
translate['Get the source code'] + '" src="/icons/agpl.png" /></a>'
if selected == 'posts':
profileStr += \
htmlProfilePosts(recentPostsCache, maxRecentPosts,
translate,
baseDir, httpPrefix, authorized,
2020-09-27 19:27:24 +00:00
nickname, domain, port,
2020-04-05 09:17:19 +00:00
session, wfRequest, personCache,
projectVersion,
YTReplacementDomain) + licenseStr
2020-04-05 09:17:19 +00:00
if selected == 'following':
profileStr += \
htmlProfileFollowing(translate, baseDir, httpPrefix,
2020-09-27 19:27:24 +00:00
authorized, nickname,
2020-04-05 09:17:19 +00:00
domain, port, session,
wfRequest, personCache, extraJson,
projectVersion, ["unfollow"], selected,
2020-07-14 17:50:32 +00:00
usersPath, pageNumber, maxItemsPerPage)
2020-04-05 09:17:19 +00:00
if selected == 'followers':
profileStr += \
htmlProfileFollowing(translate, baseDir, httpPrefix,
2020-09-27 19:27:24 +00:00
authorized, nickname,
2020-04-05 09:17:19 +00:00
domain, port, session,
wfRequest, personCache, extraJson,
projectVersion, ["block"],
2020-07-14 17:50:32 +00:00
selected, usersPath, pageNumber,
2020-04-05 09:17:19 +00:00
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
profileStr = \
htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter()
2019-07-21 18:18:58 +00:00
return profileStr
2019-07-20 21:13:36 +00:00
2020-04-05 09:17:19 +00:00
def individualFollowAsHtml(translate: {},
baseDir: str, session, wfRequest: {},
personCache: {}, domain: str,
followUrl: str,
authorized: bool,
actorNickname: str,
httpPrefix: str,
projectVersion: str,
2019-08-07 21:36:54 +00:00
buttons=[]) -> str:
2020-05-04 18:08:35 +00:00
"""An individual follow entry on the profile screen
"""
2020-04-05 09:17:19 +00:00
nickname = getNicknameFromActor(followUrl)
domain, port = getDomainFromActor(followUrl)
titleStr = '@' + nickname + '@' + domain
avatarUrl = getPersonAvatarUrl(baseDir, followUrl, personCache, True)
2019-08-18 13:30:40 +00:00
if not avatarUrl:
2020-04-05 09:17:19 +00:00
avatarUrl = followUrl + '/avatar.png'
2019-07-22 14:09:21 +00:00
if domain not in followUrl:
2020-04-05 09:17:19 +00:00
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache, projectVersion,
httpPrefix, nickname,
domain, 'outbox')
2019-07-22 14:09:21 +00:00
if avatarUrl2:
2020-04-05 09:17:19 +00:00
avatarUrl = avatarUrl2
if displayName:
2020-04-05 09:17:19 +00:00
titleStr = displayName + ' ' + titleStr
2019-08-07 21:36:54 +00:00
2020-04-05 09:17:19 +00:00
buttonsStr = ''
2019-08-07 21:36:54 +00:00
if authorized:
for b in buttons:
2020-04-05 09:17:19 +00:00
if b == 'block':
buttonsStr += \
'<a href="/users/' + actorNickname + \
'?options=' + followUrl + \
';1;' + avatarUrl + '"><button class="buttonunfollow">' + \
2020-07-28 10:31:36 +00:00
translate['Block'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
if b == 'unfollow':
buttonsStr += \
'<a href="/users/' + actorNickname + \
'?options=' + followUrl + \
';1;' + avatarUrl + '"><button class="buttonunfollow">' + \
2020-07-28 10:31:36 +00:00
translate['Unfollow'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
resultStr = '<div class="container">\n'
resultStr += \
'<a href="/users/' + actorNickname + '?options=' + \
2020-07-28 10:31:36 +00:00
followUrl + ';1;' + avatarUrl + '">\n'
resultStr += '<p><img loading="lazy" src="' + avatarUrl + '" alt=" ">'
resultStr += titleStr + '</a>' + buttonsStr + '</p>\n'
2020-04-05 09:17:19 +00:00
resultStr += '</div>\n'
return resultStr
2020-04-05 09:17:19 +00:00
def addEmbeddedAudio(translate: {}, content: str) -> str:
2019-08-30 11:32:48 +00:00
"""Adds embedded audio for mp3/ogg
"""
if not ('.mp3' in content or '.ogg' in content):
return content
2019-08-30 16:24:40 +00:00
if '<audio ' in content:
return content
2020-04-05 09:17:19 +00:00
extension = '.mp3'
2019-08-30 11:32:48 +00:00
if '.ogg' in content:
2020-04-05 09:17:19 +00:00
extension = '.ogg'
2019-08-30 11:32:48 +00:00
2020-04-05 09:17:19 +00:00
words = content.strip('\n').split(' ')
2019-08-30 11:32:48 +00:00
for w in words:
if extension not in w:
continue
2020-04-05 09:17:19 +00:00
w = w.replace('href="', '').replace('">', '')
2019-08-30 11:32:48 +00:00
if w.endswith('.'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 16:18:34 +00:00
if w.endswith('"'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 11:32:48 +00:00
if w.endswith(';'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 11:32:48 +00:00
if w.endswith(':'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 12:18:39 +00:00
if not w.endswith(extension):
2019-08-30 11:32:48 +00:00
continue
2020-03-22 20:53:47 +00:00
2020-04-05 09:17:19 +00:00
if not (w.startswith('http') or w.startswith('dat:') or
2020-05-17 09:37:59 +00:00
w.startswith('hyper:') or w.startswith('i2p:') or
2020-06-09 11:51:51 +00:00
w.startswith('gnunet:') or
2020-05-17 09:37:59 +00:00
'/' in w):
2019-08-30 11:32:48 +00:00
continue
2020-04-05 09:17:19 +00:00
url = w
2020-07-28 10:31:36 +00:00
content += '<center>\n<audio controls>\n'
2020-04-05 09:17:19 +00:00
content += \
'<source src="' + url + '" type="audio/' + \
extension.replace('.', '') + '">'
content += \
translate['Your browser does not support the audio element.']
2020-07-28 10:31:36 +00:00
content += '</audio>\n</center>\n'
2019-08-30 11:32:48 +00:00
return content
2020-04-05 09:17:19 +00:00
def addEmbeddedVideo(translate: {}, content: str,
width=400, height=300) -> str:
2019-08-30 11:45:21 +00:00
"""Adds embedded video for mp4/webm/ogv
"""
if not ('.mp4' in content or '.webm' in content or '.ogv' in content):
return content
2019-08-30 16:24:40 +00:00
if '<video ' in content:
return content
2020-04-05 09:17:19 +00:00
extension = '.mp4'
2019-08-30 11:45:21 +00:00
if '.webm' in content:
2020-04-05 09:17:19 +00:00
extension = '.webm'
2019-08-30 11:45:21 +00:00
elif '.ogv' in content:
2020-04-05 09:17:19 +00:00
extension = '.ogv'
2019-08-30 11:45:21 +00:00
2020-04-05 09:17:19 +00:00
words = content.strip('\n').split(' ')
2019-08-30 11:45:21 +00:00
for w in words:
if extension not in w:
continue
2020-04-05 09:17:19 +00:00
w = w.replace('href="', '').replace('">', '')
2019-08-30 11:45:21 +00:00
if w.endswith('.'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 16:18:34 +00:00
if w.endswith('"'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 11:45:21 +00:00
if w.endswith(';'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 11:45:21 +00:00
if w.endswith(':'):
2020-04-05 09:17:19 +00:00
w = w[:-1]
2019-08-30 12:18:39 +00:00
if not w.endswith(extension):
2019-08-30 11:45:21 +00:00
continue
2020-04-05 09:17:19 +00:00
if not (w.startswith('http') or w.startswith('dat:') or
2020-05-17 09:37:59 +00:00
w.startswith('hyper:') or w.startswith('i2p:') or
2020-06-09 11:51:51 +00:00
w.startswith('gnunet:') or
2020-05-17 09:37:59 +00:00
'/' in w):
2019-08-30 11:45:21 +00:00
continue
2020-04-05 09:17:19 +00:00
url = w
content += \
2020-07-28 10:31:36 +00:00
'<center>\n<video width="' + str(width) + '" height="' + \
str(height) + '" controls>\n'
2020-04-05 09:17:19 +00:00
content += \
'<source src="' + url + '" type="video/' + \
2020-07-28 10:31:36 +00:00
extension.replace('.', '') + '">\n'
2020-04-05 09:17:19 +00:00
content += \
translate['Your browser does not support the video element.']
2020-07-28 10:31:36 +00:00
content += '</video>\n</center>\n'
2019-08-30 11:45:21 +00:00
return content
2020-04-05 09:17:19 +00:00
def addEmbeddedVideoFromSites(translate: {}, content: str,
width=400, height=300) -> str:
2019-08-20 16:40:39 +00:00
"""Adds embedded videos
"""
if '>vimeo.com/' in content:
2020-04-05 09:17:19 +00:00
url = content.split('>vimeo.com/')[1]
2019-08-20 16:40:39 +00:00
if '<' in url:
2020-04-05 09:17:19 +00:00
url = url.split('<')[0]
content = \
2020-07-28 10:31:36 +00:00
content + "<center>\n<iframe loading=\"lazy\" " + \
2020-04-05 09:17:19 +00:00
"src=\"https://player.vimeo.com/video/" + \
url + "\" width=\"" + str(width) + \
"\" height=\"" + str(height) + \
"\" frameborder=\"0\" allow=\"autoplay; " + \
2020-07-28 10:31:36 +00:00
"fullscreen\" allowfullscreen></iframe>\n</center>\n"
2019-08-20 17:27:23 +00:00
return content
2020-04-05 09:17:19 +00:00
videoSite = 'https://www.youtube.com'
if '"' + videoSite in content:
url = content.split('"' + videoSite)[1]
2019-08-20 17:27:23 +00:00
if '"' in url:
2020-04-05 09:17:19 +00:00
url = url.split('"')[0].replace('/watch?v=', '/embed/')
2019-10-22 08:44:33 +00:00
if '&' in url:
2020-04-05 09:17:19 +00:00
url = url.split('&')[0]
content = \
2020-07-28 10:31:36 +00:00
content + "<center>\n<iframe loading=\"lazy\" src=\"" + \
2020-04-05 09:17:19 +00:00
videoSite + url + "\" width=\"" + str(width) + \
"\" height=\"" + str(height) + \
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
2020-07-28 10:31:36 +00:00
"allowfullscreen></iframe>\n</center>\n"
2019-08-21 11:34:02 +00:00
return content
2020-04-05 09:50:45 +00:00
invidiousSites = ('https://invidio.us',
2020-08-09 20:19:40 +00:00
'https://invidious.snopyta.org',
'http://c7hqkpkpemu6e7emz5b4vy' +
'z7idjgdvgaaa3dyimmeojqbgpea3xqjoid.onion',
'http://axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4' +
2020-04-05 09:50:45 +00:00
'bzzsg2ii4fv2iid.onion')
2019-10-01 11:31:29 +00:00
for videoSite in invidiousSites:
2020-04-05 09:17:19 +00:00
if '"' + videoSite in content:
url = content.split('"' + videoSite)[1]
2019-10-01 11:31:29 +00:00
if '"' in url:
2020-04-05 09:17:19 +00:00
url = url.split('"')[0].replace('/watch?v=', '/embed/')
2019-10-22 08:44:33 +00:00
if '&' in url:
2020-04-05 09:17:19 +00:00
url = url.split('&')[0]
content = \
2020-07-28 10:31:36 +00:00
content + "<center>\n<iframe loading=\"lazy\" src=\"" + \
2020-04-05 09:17:19 +00:00
videoSite + url + "\" width=\"" + \
str(width) + "\" height=\"" + str(height) + \
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
2020-07-28 10:31:36 +00:00
"allowfullscreen></iframe>\n</center>\n"
2019-10-01 11:31:29 +00:00
return content
2019-10-01 11:25:10 +00:00
2020-04-05 09:17:19 +00:00
videoSite = 'https://media.ccc.de'
if '"' + videoSite in content:
url = content.split('"' + videoSite)[1]
2019-08-21 11:34:02 +00:00
if '"' in url:
2020-04-05 09:17:19 +00:00
url = url.split('"')[0]
2019-08-21 11:40:43 +00:00
if not url.endswith('/oembed'):
2020-04-05 09:17:19 +00:00
url = url + '/oembed'
content = \
2020-07-28 10:31:36 +00:00
content + "<center>\n<iframe loading=\"lazy\" src=\"" + \
2020-04-05 09:17:19 +00:00
videoSite + url + "\" width=\"" + \
str(width) + "\" height=\"" + str(height) + \
"\" frameborder=\"0\" allow=\"fullscreen\" " + \
2020-07-28 10:31:36 +00:00
"allowfullscreen></iframe>\n</center>\n"
2019-08-20 17:27:23 +00:00
return content
2019-08-20 18:13:23 +00:00
2019-10-18 16:57:33 +00:00
if '"https://' in content:
2020-04-05 09:17:19 +00:00
# A selection of the current larger peertube sites, mostly
# French and German language
# These have been chosen based on reported numbers of users
# and the content of each has not been reviewed, so mileage could vary
peerTubeSites = ('peertube.mastodon.host', 'open.tube', 'share.tube',
'tube.tr4sk.me', 'videos.elbinario.net',
'hkvideo.live',
'peertube.snargol.com', 'tube.22decembre.eu',
'tube.fabrigli.fr', 'libretube.net', 'libre.video',
'peertube.linuxrocks.online', 'spacepub.space',
'video.ploud.jp', 'video.omniatv.com',
'peertube.servebeer.com',
'tube.tchncs.de', 'tubee.fr', 'video.alternanet.fr',
'devtube.dev-wiki.de', 'video.samedi.pm',
'video.irem.univ-paris-diderot.fr',
'peertube.openstreetmap.fr', 'video.antopie.org',
'scitech.video', 'tube.4aem.com', 'video.ploud.fr',
'peervideo.net', 'video.valme.io',
'videos.pair2jeux.tube',
'vault.mle.party', 'hostyour.tv',
'diode.zone', 'visionon.tv',
'artitube.artifaille.fr', 'peertube.fr',
'peertube.live',
'tube.ac-lyon.fr', 'www.yiny.org', 'betamax.video',
'tube.piweb.be', 'pe.ertu.be', 'peertube.social',
'videos.lescommuns.org', 'peertube.nogafa.org',
'skeptikon.fr', 'video.tedomum.net',
'tube.p2p.legal',
'sikke.fi', 'exode.me', 'peertube.video')
2019-10-18 16:57:33 +00:00
for site in peerTubeSites:
2020-04-05 09:17:19 +00:00
if '"https://' + site in content:
url = content.split('"https://' + site)[1]
2019-10-18 16:57:33 +00:00
if '"' in url:
2020-04-05 09:17:19 +00:00
url = url.split('"')[0].replace('/watch/', '/embed/')
content = \
2020-07-28 10:31:36 +00:00
content + "<center>\n<iframe loading=\"lazy\" " + \
2020-04-05 09:17:19 +00:00
"sandbox=\"allow-same-origin " + \
"allow-scripts\" src=\"https://" + \
site + url + "\" width=\"" + str(width) + \
"\" height=\"" + str(height) + \
"\" frameborder=\"0\" allow=\"autoplay; " + \
2020-07-28 10:31:36 +00:00
"fullscreen\" allowfullscreen></iframe>\n</center>\n"
2019-10-18 16:57:33 +00:00
return content
2019-08-20 16:40:39 +00:00
return content
2020-04-05 09:17:19 +00:00
def addEmbeddedElements(translate: {}, content: str) -> str:
2019-08-30 11:47:30 +00:00
"""Adds embedded elements for various media types
"""
2020-04-05 09:17:19 +00:00
content = addEmbeddedVideoFromSites(translate, content)
content = addEmbeddedAudio(translate, content)
return addEmbeddedVideo(translate, content)
2020-04-05 09:17:19 +00:00
def followerApprovalActive(baseDir: str, nickname: str, domain: str) -> bool:
"""Returns true if the given account requires follower approval
"""
2020-04-05 09:17:19 +00:00
manuallyApprovesFollowers = False
actorFilename = baseDir + '/accounts/' + nickname + '@' + domain + '.json'
if os.path.isfile(actorFilename):
2020-04-05 09:17:19 +00:00
actorJson = loadJson(actorFilename)
2019-10-22 11:55:06 +00:00
if actorJson:
if actorJson.get('manuallyApprovesFollowers'):
2020-04-05 09:17:19 +00:00
manuallyApprovesFollowers = \
actorJson['manuallyApprovesFollowers']
return manuallyApprovesFollowers
2020-04-05 09:17:19 +00:00
def insertQuestion(baseDir: str, translate: {},
nickname: str, domain: str, port: int,
content: str,
postJsonObject: {}, pageNumber: int) -> str:
2019-09-06 15:08:32 +00:00
""" Inserts question selection into a post
"""
if not isQuestion(postJsonObject):
return content
2020-04-05 09:17:19 +00:00
if len(postJsonObject['object']['oneOf']) == 0:
2019-09-06 15:08:32 +00:00
return content
2020-08-23 11:13:35 +00:00
messageId = removeIdEnding(postJsonObject['id'])
2019-11-25 12:33:05 +00:00
if '#' in messageId:
2020-04-05 09:17:19 +00:00
messageId = messageId.split('#', 1)[0]
pageNumberStr = ''
2019-09-06 16:37:33 +00:00
if pageNumber:
2020-04-05 09:17:19 +00:00
pageNumberStr = '?page=' + str(pageNumber)
2019-11-25 13:34:44 +00:00
2020-04-05 09:17:19 +00:00
votesFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/questions.txt'
2019-11-26 20:22:52 +00:00
2020-04-05 09:17:19 +00:00
showQuestionResults = False
2019-11-26 20:22:52 +00:00
if os.path.isfile(votesFilename):
2019-12-10 19:31:45 +00:00
if messageId in open(votesFilename).read():
2020-04-05 09:17:19 +00:00
showQuestionResults = True
2019-11-26 20:22:52 +00:00
2019-12-10 19:31:45 +00:00
if not showQuestionResults:
2019-11-25 13:34:44 +00:00
# show the question options
2020-04-05 09:17:19 +00:00
content += '<div class="question">'
content += \
'<form method="POST" action="/users/' + \
2020-07-28 10:31:36 +00:00
nickname + '/question' + pageNumberStr + '">\n'
2020-04-05 09:17:19 +00:00
content += \
'<input type="hidden" name="messageId" value="' + \
2020-07-28 10:31:36 +00:00
messageId + '">\n<br>\n'
2019-11-25 13:34:44 +00:00
for choice in postJsonObject['object']['oneOf']:
if not choice.get('type'):
continue
if not choice.get('name'):
continue
2020-04-05 09:17:19 +00:00
content += \
'<input type="radio" name="answer" value="' + \
2020-07-28 10:31:36 +00:00
choice['name'] + '"> ' + choice['name'] + '<br><br>\n'
2020-04-05 09:17:19 +00:00
content += \
'<input type="submit" value="' + \
2020-07-28 10:31:36 +00:00
translate['Vote'] + '" class="vote"><br><br>\n'
content += '</form>\n</div>\n'
2019-11-25 13:34:44 +00:00
else:
# show the responses to a question
2020-07-28 10:31:36 +00:00
content += '<div class="questionresult">\n'
2019-11-25 13:34:44 +00:00
# get the maximum number of votes
2020-04-05 09:17:19 +00:00
maxVotes = 1
2019-11-25 13:34:44 +00:00
for questionOption in postJsonObject['object']['oneOf']:
if not questionOption.get('name'):
continue
if not questionOption.get('replies'):
continue
2020-04-05 09:17:19 +00:00
votes = 0
2019-12-10 18:05:38 +00:00
try:
2020-04-05 09:17:19 +00:00
votes = int(questionOption['replies']['totalItems'])
except BaseException:
2019-12-10 18:05:38 +00:00
pass
2020-04-05 09:17:19 +00:00
if votes > maxVotes:
maxVotes = int(votes+1)
2019-11-25 13:34:44 +00:00
# show the votes as sliders
2020-04-05 09:17:19 +00:00
questionCtr = 1
2019-11-25 13:34:44 +00:00
for questionOption in postJsonObject['object']['oneOf']:
if not questionOption.get('name'):
continue
if not questionOption.get('replies'):
continue
2020-04-05 09:17:19 +00:00
votes = 0
2019-12-10 18:05:38 +00:00
try:
2020-04-05 09:17:19 +00:00
votes = int(questionOption['replies']['totalItems'])
except BaseException:
2019-12-10 18:05:38 +00:00
pass
2020-04-05 09:17:19 +00:00
votesPercent = str(int(votes * 100 / maxVotes))
content += \
'<p><input type="text" title="' + str(votes) + \
'" name="skillName' + str(questionCtr) + \
'" value="' + questionOption['name'] + \
2020-07-28 10:31:36 +00:00
' (' + str(votes) + ')" style="width:40%">\n'
2020-04-05 09:17:19 +00:00
content += \
'<input type="range" min="1" max="100" ' + \
'class="slider" title="' + \
str(votes) + '" name="skillValue' + str(questionCtr) + \
2020-07-28 10:31:36 +00:00
'" value="' + votesPercent + '"></p>\n'
2020-04-05 09:17:19 +00:00
questionCtr += 1
2020-07-28 10:31:36 +00:00
content += '</div>\n'
2019-09-06 15:08:32 +00:00
return content
2020-04-05 09:17:19 +00:00
def addEmojiToDisplayName(baseDir: str, httpPrefix: str,
nickname: str, domain: str,
displayName: str, inProfileName: bool) -> str:
2019-09-23 11:44:43 +00:00
"""Adds emoji icons to display names on individual posts
"""
2019-10-18 12:46:35 +00:00
if ':' not in displayName:
return displayName
2020-04-05 09:17:19 +00:00
displayName = displayName.replace('<p>', '').replace('</p>', '')
emojiTags = {}
print('TAG: displayName before tags: ' + displayName)
displayName = \
addHtmlTags(baseDir, httpPrefix,
nickname, domain, displayName, [], emojiTags)
displayName = displayName.replace('<p>', '').replace('</p>', '')
print('TAG: displayName after tags: ' + displayName)
2019-10-18 12:46:35 +00:00
# convert the emoji dictionary to a list
2020-04-05 09:17:19 +00:00
emojiTagsList = []
for tagName, tag in emojiTags.items():
2019-10-18 12:46:35 +00:00
emojiTagsList.append(tag)
2020-04-05 09:17:19 +00:00
print('TAG: emoji tags list: ' + str(emojiTagsList))
2019-10-18 12:46:35 +00:00
if not inProfileName:
2020-04-05 09:17:19 +00:00
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'post header')
2019-10-18 12:46:35 +00:00
else:
2020-04-05 09:17:19 +00:00
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'profile')
print('TAG: displayName after tags 2: ' + displayName)
2019-10-18 12:46:35 +00:00
# remove any stray emoji
while ':' in displayName:
if '://' in displayName:
break
2020-04-05 09:17:19 +00:00
emojiStr = displayName.split(':')[1]
prevDisplayName = displayName
displayName = displayName.replace(':' + emojiStr + ':', '').strip()
if prevDisplayName == displayName:
2019-10-18 12:46:35 +00:00
break
2020-04-05 09:17:19 +00:00
print('TAG: displayName after tags 3: ' + displayName)
print('TAG: displayName after tag replacements: ' + displayName)
2019-10-09 17:03:07 +00:00
2019-09-23 11:44:43 +00:00
return displayName
2020-04-05 09:17:19 +00:00
2019-10-18 12:00:14 +00:00
def postContainsPublic(postJsonObject: {}) -> bool:
"""Does the given post contain #Public
"""
2020-04-05 09:17:19 +00:00
containsPublic = False
2019-10-18 12:00:14 +00:00
if not postJsonObject['object'].get('to'):
return containsPublic
2020-03-22 21:16:02 +00:00
2019-10-18 12:00:14 +00:00
for toAddress in postJsonObject['object']['to']:
if toAddress.endswith('#Public'):
2020-04-05 09:17:19 +00:00
containsPublic = True
2019-10-18 12:00:14 +00:00
break
if not containsPublic:
if postJsonObject['object'].get('cc'):
for toAddress in postJsonObject['object']['cc']:
if toAddress.endswith('#Public'):
2020-04-05 09:17:19 +00:00
containsPublic = True
2019-10-18 12:00:14 +00:00
break
return containsPublic
2020-04-05 09:17:19 +00:00
def loadIndividualPostAsHtmlFromCache(baseDir: str,
nickname: str, domain: str,
2019-10-19 11:53:57 +00:00
postJsonObject: {}) -> str:
"""If a cached html version of the given post exists then load it and
return the html text
2019-10-19 12:08:18 +00:00
This is much quicker than generating the html from the json object
2019-10-19 11:53:57 +00:00
"""
2020-04-05 09:17:19 +00:00
cachedPostFilename = \
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
2019-10-19 12:07:12 +00:00
2020-04-05 09:17:19 +00:00
postHtml = ''
2019-11-29 23:04:37 +00:00
if not cachedPostFilename:
return postHtml
2020-03-22 21:16:02 +00:00
2019-10-19 11:53:57 +00:00
if not os.path.isfile(cachedPostFilename):
return postHtml
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
tries = 0
while tries < 3:
2019-10-19 11:53:57 +00:00
try:
with open(cachedPostFilename, 'r') as file:
2020-04-05 09:17:19 +00:00
postHtml = file.read()
2019-10-19 11:53:57 +00:00
break
except Exception as e:
print(e)
# no sleep
2020-04-05 09:17:19 +00:00
tries += 1
2019-10-19 11:53:57 +00:00
if postHtml:
return postHtml
2019-10-18 12:00:14 +00:00
2020-04-05 09:17:19 +00:00
def saveIndividualPostAsHtmlToCache(baseDir: str,
nickname: str, domain: str,
postJsonObject: {},
postHtml: str) -> bool:
2019-10-19 11:53:57 +00:00
"""Saves the given html for a post to a cache file
2020-04-05 09:17:19 +00:00
This is so that it can be quickly reloaded on subsequent
refresh of the timeline
2019-10-19 11:53:57 +00:00
"""
2020-04-05 09:17:19 +00:00
htmlPostCacheDir = \
getCachedPostDirectory(baseDir, nickname, domain)
cachedPostFilename = \
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
2019-10-19 11:53:57 +00:00
# create the cache directory if needed
if not os.path.isdir(htmlPostCacheDir):
os.mkdir(htmlPostCacheDir)
try:
2020-07-12 20:04:58 +00:00
with open(cachedPostFilename, 'w+') as fp:
2019-10-19 11:53:57 +00:00
fp.write(postHtml)
return True
except Exception as e:
2020-04-05 09:17:19 +00:00
print('ERROR: saving post to cache ' + str(e))
2019-10-19 11:53:57 +00:00
return False
2020-04-05 09:17:19 +00:00
def preparePostFromHtmlCache(postHtml: str, boxName: str,
pageNumber: int) -> str:
2019-11-25 10:29:09 +00:00
"""Sets the page number on a cached html post
"""
# if on the bookmarks timeline then remain there
2020-05-21 20:15:24 +00:00
if boxName == 'tlbookmarks' or boxName == 'bookmarks':
2020-04-05 09:17:19 +00:00
postHtml = postHtml.replace('?tl=inbox', '?tl=tlbookmarks')
2020-05-22 12:48:58 +00:00
if '?page=' in postHtml:
pageNumberStr = postHtml.split('?page=')[1]
if '?' in pageNumberStr:
pageNumberStr = pageNumberStr.split('?')[0]
postHtml = postHtml.replace('?page=' + pageNumberStr, '?page=-999')
2020-04-05 09:17:19 +00:00
withPageNumber = postHtml.replace(';-999;', ';' + str(pageNumber) + ';')
withPageNumber = withPageNumber.replace('?page=-999',
'?page=' + str(pageNumber))
return withPageNumber
2020-04-05 09:17:19 +00:00
def postIsMuted(baseDir: str, nickname: str, domain: str,
postJsonObject: {}, messageId: str) -> bool:
2019-12-01 13:45:30 +00:00
""" Returns true if the given post is muted
"""
2020-04-05 09:17:19 +00:00
isMuted = postJsonObject.get('muted')
if isMuted is True or isMuted is False:
2019-12-01 16:15:41 +00:00
return isMuted
2020-04-05 09:17:19 +00:00
postDir = baseDir + '/accounts/' + nickname + '@' + domain
muteFilename = \
postDir + '/inbox/' + messageId.replace('/', '#') + '.json.muted'
2019-12-01 13:45:30 +00:00
if os.path.isfile(muteFilename):
return True
2020-04-05 09:17:19 +00:00
muteFilename = \
postDir + '/outbox/' + messageId.replace('/', '#') + '.json.muted'
2019-12-01 14:09:45 +00:00
if os.path.isfile(muteFilename):
return True
2020-04-05 09:17:19 +00:00
muteFilename = \
baseDir + '/accounts/cache/announce/' + nickname + \
'/' + messageId.replace('/', '#') + '.json.muted'
2019-12-01 13:45:30 +00:00
if os.path.isfile(muteFilename):
return True
return False
2020-04-05 09:17:19 +00:00
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
"""
2020-04-05 09:17:19 +00:00
attachmentStr = ''
galleryStr = ''
if not postJsonObject['object'].get('attachment'):
2020-04-05 09:17:19 +00:00
return attachmentStr, galleryStr
if not isinstance(postJsonObject['object']['attachment'], list):
2020-04-05 09:17:19 +00:00
return attachmentStr, galleryStr
2020-04-05 09:17:19 +00:00
attachmentCtr = 0
2020-07-28 10:31:36 +00:00
attachmentStr += '<div class="media">\n'
for attach in postJsonObject['object']['attachment']:
if not (attach.get('mediaType') and attach.get('url')):
continue
2020-04-05 09:17:19 +00:00
mediaType = attach['mediaType']
imageDescription = ''
if attach.get('name'):
2020-04-05 09:17:19 +00:00
imageDescription = attach['name'].replace('"', "'")
if mediaType == 'image/png' or \
mediaType == 'image/jpeg' or \
mediaType == 'image/webp' or \
mediaType == 'image/avif' or \
2020-04-05 09:17:19 +00:00
mediaType == 'image/gif':
if attach['url'].endswith('.png') or \
attach['url'].endswith('.jpg') or \
attach['url'].endswith('.jpeg') or \
attach['url'].endswith('.webp') or \
attach['url'].endswith('.avif') or \
attach['url'].endswith('.gif'):
2020-04-05 09:17:19 +00:00
if attachmentCtr > 0:
attachmentStr += '<br>'
if boxName == 'tlmedia':
galleryStr += '<div class="gallery">\n'
if not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += ' <a href="' + attach['url'] + '">\n'
galleryStr += \
' <img loading="lazy" src="' + \
attach['url'] + '" alt="" title="">\n'
galleryStr += ' </a>\n'
if postJsonObject['object'].get('url'):
2020-04-05 09:17:19 +00:00
imagePostUrl = postJsonObject['object']['url']
else:
2020-04-05 09:17:19 +00:00
imagePostUrl = postJsonObject['object']['id']
if imageDescription and not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += \
' <a href="' + imagePostUrl + \
'" class="gallerytext"><div ' + \
'class="gallerytext">' + \
imageDescription + '</div></a>\n'
else:
2020-04-05 09:17:19 +00:00
galleryStr += \
'<label class="transparent">---</label><br>'
galleryStr += ' <div class="mediaicons">\n'
galleryStr += \
' ' + replyStr+announceStr + likeStr + \
bookmarkStr + deleteStr + muteStr + '\n'
galleryStr += ' </div>\n'
galleryStr += ' <div class="mediaavatar">\n'
galleryStr += ' ' + avatarLink + '\n'
galleryStr += ' </div>\n'
galleryStr += '</div>\n'
attachmentStr += '<a href="' + attach['url'] + '">'
attachmentStr += \
'<img loading="lazy" src="' + attach['url'] + \
'" alt="' + imageDescription + '" title="' + \
imageDescription + '" class="attachment"></a>\n'
attachmentCtr += 1
elif (mediaType == 'video/mp4' or
mediaType == 'video/webm' or
mediaType == 'video/ogv'):
extension = '.mp4'
if attach['url'].endswith('.webm'):
2020-04-05 09:17:19 +00:00
extension = '.webm'
elif attach['url'].endswith('.ogv'):
2020-04-05 09:17:19 +00:00
extension = '.ogv'
if attach['url'].endswith(extension):
2020-04-05 09:17:19 +00:00
if attachmentCtr > 0:
attachmentStr += '<br>'
if boxName == 'tlmedia':
galleryStr += '<div class="gallery">\n'
if not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += ' <a href="' + attach['url'] + '">\n'
galleryStr += \
' <video width="600" height="400" controls>\n'
galleryStr += \
' <source src="' + attach['url'] + \
'" alt="' + imageDescription + \
'" title="' + imageDescription + \
'" class="attachment" type="video/' + \
extension.replace('.', '') + '">'
idx = 'Your browser does not support the video tag.'
galleryStr += translate[idx]
galleryStr += ' </video>\n'
galleryStr += ' </a>\n'
if postJsonObject['object'].get('url'):
2020-04-05 09:17:19 +00:00
videoPostUrl = postJsonObject['object']['url']
else:
2020-04-05 09:17:19 +00:00
videoPostUrl = postJsonObject['object']['id']
if imageDescription and not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += \
' <a href="' + videoPostUrl + \
'" class="gallerytext"><div ' + \
'class="gallerytext">' + \
imageDescription + '</div></a>\n'
else:
2020-04-05 09:17:19 +00:00
galleryStr += \
'<label class="transparent">---</label><br>'
galleryStr += ' <div class="mediaicons">\n'
galleryStr += \
' ' + replyStr + announceStr + likeStr + \
bookmarkStr + deleteStr + muteStr + '\n'
galleryStr += ' </div>\n'
galleryStr += ' <div class="mediaavatar">\n'
galleryStr += ' ' + avatarLink + '\n'
galleryStr += ' </div>\n'
galleryStr += '</div>\n'
attachmentStr += \
'<center><video width="400" height="300" controls>'
attachmentStr += \
'<source src="' + attach['url'] + '" alt="' + \
imageDescription + '" title="' + imageDescription + \
'" class="attachment" type="video/' + \
extension.replace('.', '') + '">'
attachmentStr += \
translate['Your browser does not support the video tag.']
attachmentStr += '</video></center>'
attachmentCtr += 1
elif (mediaType == 'audio/mpeg' or
mediaType == 'audio/ogg'):
extension = '.mp3'
if attach['url'].endswith('.ogg'):
2020-04-05 09:17:19 +00:00
extension = '.ogg'
if attach['url'].endswith(extension):
2020-04-05 09:17:19 +00:00
if attachmentCtr > 0:
attachmentStr += '<br>'
if boxName == 'tlmedia':
galleryStr += '<div class="gallery">\n'
if not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += ' <a href="' + attach['url'] + '">\n'
galleryStr += ' <audio controls>\n'
galleryStr += \
' <source src="' + attach['url'] + \
'" alt="' + imageDescription + \
'" title="' + imageDescription + \
'" class="attachment" type="audio/' + \
extension.replace('.', '') + '">'
idx = 'Your browser does not support the audio tag.'
galleryStr += translate[idx]
galleryStr += ' </audio>\n'
galleryStr += ' </a>\n'
if postJsonObject['object'].get('url'):
2020-04-05 09:17:19 +00:00
audioPostUrl = postJsonObject['object']['url']
else:
2020-04-05 09:17:19 +00:00
audioPostUrl = postJsonObject['object']['id']
if imageDescription and not isMuted:
2020-04-05 09:17:19 +00:00
galleryStr += \
' <a href="' + audioPostUrl + \
'" class="gallerytext"><div ' + \
'class="gallerytext">' + \
imageDescription + '</div></a>\n'
else:
2020-04-05 09:17:19 +00:00
galleryStr += \
'<label class="transparent">---</label><br>'
galleryStr += ' <div class="mediaicons">\n'
galleryStr += \
' ' + replyStr + announceStr + \
likeStr + bookmarkStr + \
deleteStr + muteStr+'\n'
galleryStr += ' </div>\n'
galleryStr += ' <div class="mediaavatar">\n'
galleryStr += ' ' + avatarLink + '\n'
galleryStr += ' </div>\n'
galleryStr += '</div>\n'
2020-07-28 10:31:36 +00:00
attachmentStr += '<center>\n<audio controls>\n'
2020-04-05 09:17:19 +00:00
attachmentStr += \
'<source src="' + attach['url'] + '" alt="' + \
imageDescription + '" title="' + imageDescription + \
'" class="attachment" type="audio/' + \
extension.replace('.', '') + '">'
attachmentStr += \
translate['Your browser does not support the audio tag.']
2020-07-28 10:31:36 +00:00
attachmentStr += '</audio>\n</center>\n'
2020-04-05 09:17:19 +00:00
attachmentCtr += 1
attachmentStr += '</div>'
return attachmentStr, galleryStr
def individualPostAsHtml(allowDownloads: bool,
recentPostsCache: {}, maxRecentPosts: int,
2020-04-05 09:17:19 +00:00
iconsDir: str, translate: {},
pageNumber: int, baseDir: str,
session, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int,
postJsonObject: {},
avatarUrl: str, showAvatarOptions: bool,
allowDeletion: bool,
httpPrefix: str, projectVersion: str,
boxName: str, YTReplacementDomain: str,
showRepeats=True,
2020-04-05 09:17:19 +00:00
showIcons=False,
manuallyApprovesFollowers=False,
showPublicOnly=False,
2019-10-19 11:42:41 +00:00
storeToCache=True) -> str:
2019-07-31 10:09:02 +00:00
""" Shows a single post as html
"""
2020-08-29 20:11:19 +00:00
if not postJsonObject:
return ''
2020-08-28 21:57:25 +00:00
# benchmark
postStartTime = time.time()
2020-04-05 09:17:19 +00:00
postActor = postJsonObject['actor']
2019-11-03 16:11:52 +00:00
2019-11-06 11:39:41 +00:00
# ZZZzzz
2020-04-05 09:17:19 +00:00
if isPersonSnoozed(baseDir, nickname, domain, postActor):
2019-11-06 11:39:41 +00:00
return ''
2020-08-28 21:57:25 +00:00
# benchmark 1
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
2020-08-28 22:21:15 +00:00
print('TIMING INDIV ' + boxName + ' 1 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-04-05 09:17:19 +00:00
avatarPosition = ''
messageId = ''
2019-11-28 13:41:11 +00:00
if postJsonObject.get('id'):
2020-08-23 11:13:35 +00:00
messageId = removeIdEnding(postJsonObject['id'])
2019-11-28 13:41:11 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 2
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
2020-08-28 22:21:15 +00:00
print('TIMING INDIV ' + boxName + ' 2 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-04-05 09:17:19 +00:00
messageIdStr = ''
2019-11-28 13:39:03 +00:00
if messageId:
2020-04-05 09:17:19 +00:00
messageIdStr = ';' + messageId
2019-11-28 13:39:03 +00:00
2020-04-05 09:17:19 +00:00
fullDomain = domain
2019-11-28 13:39:03 +00:00
if port:
2020-04-05 09:17:19 +00:00
if port != 80 and port != 443:
2019-11-28 13:39:03 +00:00
if ':' not in domain:
2020-04-05 09:17:19 +00:00
fullDomain = domain + ':' + str(port)
2019-11-28 13:39:03 +00:00
2020-04-05 09:17:19 +00:00
pageNumberParam = ''
2019-11-28 13:39:03 +00:00
if pageNumber:
2020-04-05 09:17:19 +00:00
pageNumberParam = '?page=' + str(pageNumber)
2019-11-28 13:39:03 +00:00
if (not showPublicOnly and
(storeToCache or boxName == 'bookmarks' or
boxName == 'tlbookmarks') and
boxName != 'tlmedia'):
2019-11-03 16:11:52 +00:00
# update avatar if needed
if not avatarUrl:
2020-04-05 09:17:19 +00:00
avatarUrl = \
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
2020-08-28 22:36:08 +00:00
# benchmark 2.1
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 2.1 = ' + str(timeDiff))
2020-08-28 22:36:08 +00:00
2020-04-05 09:17:19 +00:00
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
2019-11-03 16:11:52 +00:00
2020-08-28 22:36:08 +00:00
# benchmark 2.2
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 2.2 = ' + str(timeDiff))
2020-08-28 22:36:08 +00:00
2020-04-05 09:17:19 +00:00
postHtml = \
loadIndividualPostAsHtmlFromCache(baseDir, nickname, domain,
2019-10-19 11:53:57 +00:00
postJsonObject)
if postHtml:
2020-04-05 09:17:19 +00:00
postHtml = preparePostFromHtmlCache(postHtml, boxName, pageNumber)
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
postJsonObject, postHtml)
2020-08-28 21:57:25 +00:00
# benchmark 3
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 3 = ' + str(timeDiff))
return postHtml
2019-10-19 10:34:06 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 4
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 4 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-11-28 13:39:03 +00:00
if not avatarUrl:
2020-04-05 09:17:19 +00:00
avatarUrl = \
getPersonAvatarUrl(baseDir, postActor, personCache,
allowDownloads)
2020-04-05 09:17:19 +00:00
avatarUrl = \
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
2019-11-28 13:39:03 +00:00
else:
2020-04-05 09:17:19 +00:00
updateAvatarImageCache(session, baseDir, httpPrefix,
postActor, avatarUrl, personCache,
allowDownloads)
2019-11-28 13:39:03 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 5
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 5 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-11-28 13:39:03 +00:00
if not avatarUrl:
2020-04-05 09:17:19 +00:00
avatarUrl = postActor + '/avatar.png'
2019-11-28 13:39:03 +00:00
if fullDomain not in postActor:
2020-04-05 09:17:19 +00:00
(inboxUrl, pubKeyId, pubKey,
fromPersonId, sharedInbox,
avatarUrl2, displayName) = getPersonBox(baseDir, session, wfRequest,
personCache,
projectVersion, httpPrefix,
nickname, domain, 'outbox')
2020-08-28 21:57:25 +00:00
# benchmark 6
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 6 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-11-28 13:39:03 +00:00
if avatarUrl2:
2020-04-05 09:17:19 +00:00
avatarUrl = avatarUrl2
2019-11-28 13:39:03 +00:00
if displayName:
if ':' in displayName:
2020-04-05 09:17:19 +00:00
displayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
displayName, False)
2020-08-28 21:57:25 +00:00
# benchmark 7
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 7 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-09-30 21:05:32 +00:00
avatarLink = ' <a class="imageAnchor" href="' + postActor + '">'
2020-04-05 09:17:19 +00:00
avatarLink += \
' <img loading="lazy" src="' + avatarUrl + '" title="' + \
2020-09-30 20:34:14 +00:00
translate['Show profile'] + '" alt=" "' + avatarPosition + '/></a>\n'
2020-04-05 09:17:19 +00:00
if showAvatarOptions and \
fullDomain + '/users/' + nickname not in postActor:
avatarLink = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?options=' + postActor + \
2020-07-28 10:31:36 +00:00
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + '">\n'
2020-04-05 09:17:19 +00:00
avatarLink += \
2020-09-30 20:26:13 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['Show options for this person'] + \
2020-07-28 10:31:36 +00:00
'" src="' + avatarUrl + '" ' + avatarPosition + '/></a>\n'
2020-04-05 09:17:19 +00:00
avatarImageInPost = \
2020-09-30 20:20:08 +00:00
' <div class="timeline-avatar">' + avatarLink.strip() + '</div>\n'
2019-11-28 13:39:03 +00:00
# don't create new html within the bookmarks timeline
# it should already have been created for the inbox
2020-05-21 20:15:24 +00:00
if boxName == 'tlbookmarks' or boxName == 'bookmarks':
return ''
2020-08-23 11:13:35 +00:00
timelinePostBookmark = removeIdEnding(postJsonObject['id'])
2020-04-05 09:17:19 +00:00
timelinePostBookmark = timelinePostBookmark.replace('://', '-')
timelinePostBookmark = timelinePostBookmark.replace('/', '-')
2019-11-19 15:27:43 +00:00
# If this is the inbox timeline then don't show the repeat icon on any DMs
2020-04-05 09:17:19 +00:00
showRepeatIcon = showRepeats
isPublicRepeat = False
showDMicon = False
if showRepeats:
if isDM(postJsonObject):
2020-04-05 09:17:19 +00:00
showDMicon = True
showRepeatIcon = False
2020-02-14 17:30:09 +00:00
else:
if not isPublicPost(postJsonObject):
2020-04-05 09:17:19 +00:00
isPublicRepeat = True
titleStr = ''
galleryStr = ''
isAnnounced = False
if postJsonObject['type'] == 'Announce':
postJsonAnnounce = \
downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject,
projectVersion, translate,
YTReplacementDomain)
2019-09-28 16:05:01 +00:00
if not postJsonAnnounce:
return ''
2020-04-05 09:17:19 +00:00
postJsonObject = postJsonAnnounce
isAnnounced = True
2019-09-28 16:05:01 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 8
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 8 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-07-31 10:09:02 +00:00
if not isinstance(postJsonObject['object'], dict):
return ''
2019-09-02 11:59:15 +00:00
# if this post should be public then check its recipients
if showPublicOnly:
2019-10-18 12:00:14 +00:00
if not postContainsPublic(postJsonObject):
return ''
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
isModerationPost = False
if postJsonObject['object'].get('moderationStatus'):
2020-04-05 09:17:19 +00:00
isModerationPost = True
containerClass = 'container'
containerClassIcons = 'containericons'
timeClass = 'time-right'
actorNickname = getNicknameFromActor(postActor)
if not actorNickname:
# single user instance
2020-04-05 09:17:19 +00:00
actorNickname = 'dev'
actorDomain, actorPort = getDomainFromActor(postActor)
2019-08-20 13:13:44 +00:00
2020-04-05 09:17:19 +00:00
displayName = getDisplayName(baseDir, postActor, personCache)
if displayName:
2019-10-18 17:07:45 +00:00
if ':' in displayName:
2020-04-05 09:17:19 +00:00
displayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
displayName, False)
titleStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?options=' + postActor + \
2020-04-05 09:17:19 +00:00
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
2020-07-28 10:31:36 +00:00
'">' + displayName + '</a>\n'
2019-08-22 12:41:16 +00:00
else:
2019-10-21 10:04:05 +00:00
if not messageId:
2020-04-05 09:17:19 +00:00
# pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print('ERROR: no messageId')
if not actorNickname:
2020-04-05 09:17:19 +00:00
# pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print('ERROR: no actorNickname')
if not actorDomain:
2020-04-05 09:17:19 +00:00
# pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print('ERROR: no actorDomain')
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?options=' + postActor + \
2020-04-05 09:17:19 +00:00
';' + str(pageNumber) + ';' + avatarUrl + messageIdStr + \
2020-07-28 10:31:36 +00:00
'">@' + actorNickname + '@' + actorDomain + '</a>\n'
2019-08-25 17:22:24 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 9
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 9 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-08-25 17:53:20 +00:00
# Show a DM icon for DMs in the inbox timeline
if showDMicon:
2020-04-05 09:17:19 +00:00
titleStr = \
titleStr + ' <img loading="lazy" src="/' + \
2020-07-28 10:31:36 +00:00
iconsDir + '/dm.png" class="DMicon"/>\n'
2019-08-25 17:53:20 +00:00
2020-04-05 09:17:19 +00:00
replyStr = ''
# check if replying is permitted
commentsEnabled = True
if 'commentsEnabled' in postJsonObject['object']:
if postJsonObject['object']['commentsEnabled'] is False:
commentsEnabled = False
if showIcons and commentsEnabled:
# reply is permitted - create reply icon
2020-04-05 09:17:19 +00:00
replyToLink = postJsonObject['object']['id']
2019-11-28 12:34:02 +00:00
if postJsonObject['object'].get('attributedTo'):
2020-08-06 16:21:46 +00:00
if isinstance(postJsonObject['object']['attributedTo'], str):
replyToLink += \
'?mention=' + postJsonObject['object']['attributedTo']
2019-11-28 12:34:02 +00:00
if postJsonObject['object'].get('content'):
2020-04-05 09:17:19 +00:00
mentionedActors = \
2020-02-23 15:32:47 +00:00
getMentionsFromHtml(postJsonObject['object']['content'])
2019-11-28 12:34:02 +00:00
if mentionedActors:
for actorUrl in mentionedActors:
2020-04-05 09:17:19 +00:00
if '?mention=' + actorUrl not in replyToLink:
replyToLink += '?mention=' + actorUrl
if len(replyToLink) > 500:
2019-11-28 12:34:02 +00:00
break
2020-04-05 09:17:19 +00:00
replyToLink += pageNumberParam
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
replyStr = ''
2020-02-14 17:16:01 +00:00
if isPublicRepeat:
2020-04-05 09:17:19 +00:00
replyStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?replyto=' + replyToLink + \
2020-04-05 09:17:19 +00:00
'?actor=' + postJsonObject['actor'] + \
2020-07-28 10:31:36 +00:00
'" title="' + translate['Reply to this post'] + '">\n'
2020-02-11 10:24:22 +00:00
else:
if isDM(postJsonObject):
2020-04-05 09:17:19 +00:00
replyStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<a class="imageAnchor" href="/users/' + nickname + \
2020-04-05 09:17:19 +00:00
'?replydm=' + replyToLink + \
'?actor=' + postJsonObject['actor'] + \
2020-07-28 10:31:36 +00:00
'" title="' + translate['Reply to this post'] + '">\n'
2019-11-28 12:34:02 +00:00
else:
2020-04-05 09:17:19 +00:00
replyStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<a class="imageAnchor" href="/users/' + nickname + \
2020-04-05 09:17:19 +00:00
'?replyfollowers=' + replyToLink + \
'?actor=' + postJsonObject['actor'] + \
2020-07-28 10:31:36 +00:00
'" title="' + translate['Reply to this post'] + '">\n'
2020-04-05 09:17:19 +00:00
replyStr += \
2020-09-30 21:26:55 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<img loading="lazy" title="' + \
2020-06-17 13:21:56 +00:00
translate['Reply to this post'] + '" alt="' + \
2020-04-05 09:17:19 +00:00
translate['Reply to this post'] + \
2020-07-28 10:31:36 +00:00
' |" src="/' + iconsDir + '/reply.png"/></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 10
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 10 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-08-26 17:41:38 +00:00
isEvent = isEventPost(postJsonObject)
2020-08-28 21:57:25 +00:00
# benchmark 11
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 11 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-04-05 09:17:19 +00:00
editStr = ''
if fullDomain + '/users/' + nickname in postJsonObject['actor']:
2020-08-23 11:13:35 +00:00
if '/statuses/' in postJsonObject['object']['id']:
if isBlogPost(postJsonObject):
if not isNewsPost(postJsonObject):
blogPostId = postJsonObject['object']['id']
editStr += \
' ' + \
'<a class="imageAnchor" href="/users/' + \
nickname + \
'/tlblogs?editblogpost=' + \
blogPostId.split('/statuses/')[1] + \
'?actor=' + actorNickname + \
'" title="' + translate['Edit blog post'] + '">' + \
'<img loading="lazy" title="' + \
translate['Edit blog post'] + '" alt="' + \
translate['Edit blog post'] + \
' |" src="/' + iconsDir + '/edit.png"/></a>\n'
2020-08-26 17:41:38 +00:00
elif isEvent:
eventPostId = postJsonObject['object']['id']
editStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<a class="imageAnchor" href="/users/' + nickname + \
2020-08-26 17:41:38 +00:00
'/tlblogs?editeventpost=' + \
eventPostId.split('/statuses/')[1] + \
'?actor=' + actorNickname + \
'" title="' + translate['Edit event'] + '">' + \
'<img loading="lazy" title="' + \
translate['Edit event'] + '" alt="' + \
translate['Edit event'] + \
' |" src="/' + iconsDir + '/edit.png"/></a>\n'
2020-04-05 09:17:19 +00:00
announceStr = ''
2019-11-28 12:00:01 +00:00
if not isModerationPost and showRepeatIcon:
# don't allow announce/repeat of your own posts
2020-04-05 09:17:19 +00:00
announceIcon = 'repeat_inactive.png'
announceLink = 'repeat'
2020-02-14 17:16:01 +00:00
if not isPublicRepeat:
2020-04-05 09:17:19 +00:00
announceLink = 'repeatprivate'
announceTitle = translate['Repeat this post']
if announcedByPerson(postJsonObject, nickname, fullDomain):
announceIcon = 'repeat.png'
2020-02-14 17:16:01 +00:00
if not isPublicRepeat:
2020-04-05 09:17:19 +00:00
announceLink = 'unrepeatprivate'
announceTitle = translate['Undo the repeat']
announceStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?' + announceLink + \
2020-04-05 09:17:19 +00:00
'=' + postJsonObject['object']['id'] + pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \
'?bm=' + timelinePostBookmark + \
2020-07-28 10:31:36 +00:00
'?tl=' + boxName + '" title="' + announceTitle + '">\n'
2020-04-05 09:17:19 +00:00
announceStr += \
2020-09-30 21:41:34 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<img loading="lazy" title="' + translate['Repeat this post'] + \
2020-06-17 13:21:56 +00:00
'" alt="' + translate['Repeat this post'] + \
2020-07-28 10:31:36 +00:00
' |" src="/' + iconsDir + '/' + announceIcon + '"/></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 12
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-08-28 13:36:21 +00:00
# whether to show a like button
hideLikeButtonFile = \
baseDir + '/accounts/' + nickname + '@' + domain + '/.hideLikeButton'
showLikeButton = True
if os.path.isfile(hideLikeButtonFile):
showLikeButton = False
2020-04-05 09:17:19 +00:00
likeStr = ''
2020-08-28 13:36:21 +00:00
if not isModerationPost and showLikeButton:
2020-04-05 09:17:19 +00:00
likeIcon = 'like_inactive.png'
likeLink = 'like'
likeTitle = translate['Like this post']
2020-07-08 17:39:40 +00:00
likeCount = noOfLikes(postJsonObject)
2020-08-28 22:36:08 +00:00
# benchmark 12.1
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12.1 = ' + str(timeDiff))
2020-08-28 22:36:08 +00:00
2020-07-08 17:39:40 +00:00
likeCountStr = ''
if likeCount > 0:
2020-09-05 11:29:52 +00:00
if likeCount <= 10:
likeCountStr = ' (' + str(likeCount) + ')'
else:
likeCountStr = ' (10+)'
2020-04-05 09:17:19 +00:00
if likedByPerson(postJsonObject, nickname, fullDomain):
2020-09-05 11:29:52 +00:00
if likeCount == 1:
# liked by the reader only
likeCountStr = ''
likeIcon = 'like.png'
2020-04-05 09:17:19 +00:00
likeLink = 'unlike'
likeTitle = translate['Undo the like']
2020-08-28 22:36:08 +00:00
# benchmark 12.2
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12.2 = ' + str(timeDiff))
2020-08-28 22:36:08 +00:00
2020-09-05 11:46:33 +00:00
likeStr = ''
2020-09-05 11:44:23 +00:00
if likeCountStr:
# show the number of likes next to icon
likeStr += '<label class="likesCount">'
likeStr += likeCountStr.replace('(', '').replace(')', '').strip()
likeStr += '</label>\n'
2020-09-05 11:46:33 +00:00
likeStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + nickname + '?' + \
2020-04-05 09:17:19 +00:00
likeLink + '=' + postJsonObject['object']['id'] + \
pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \
'?bm=' + timelinePostBookmark + \
2020-07-08 17:39:40 +00:00
'?tl=' + boxName + '" title="' + \
2020-07-28 10:31:36 +00:00
likeTitle + likeCountStr + '">\n'
2020-04-05 09:17:19 +00:00
likeStr += \
2020-09-30 21:41:34 +00:00
' ' + \
2020-07-08 17:39:40 +00:00
'<img loading="lazy" title="' + likeTitle + likeCountStr + \
2020-06-17 13:21:56 +00:00
'" alt="' + likeTitle + \
2020-09-05 11:34:41 +00:00
' |" src="/' + iconsDir + '/' + likeIcon + '"/></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 22:36:08 +00:00
# benchmark 12.5
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12.5 = ' + str(timeDiff))
2020-08-28 22:36:08 +00:00
2020-04-05 09:17:19 +00:00
bookmarkStr = ''
2019-11-28 12:00:01 +00:00
if not isModerationPost:
2020-04-05 09:17:19 +00:00
bookmarkIcon = 'bookmark_inactive.png'
bookmarkLink = 'bookmark'
bookmarkTitle = translate['Bookmark this post']
if bookmarkedByPerson(postJsonObject, nickname, fullDomain):
bookmarkIcon = 'bookmark.png'
bookmarkLink = 'unbookmark'
bookmarkTitle = translate['Undo the bookmark']
2020-08-28 22:36:08 +00:00
# benchmark 12.6
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12.6 = ' + str(timeDiff))
2020-04-05 09:17:19 +00:00
bookmarkStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + nickname + '?' + \
2020-04-05 09:17:19 +00:00
bookmarkLink + '=' + postJsonObject['object']['id'] + \
pageNumberParam + \
'?actor=' + postJsonObject['actor'] + \
'?bm=' + timelinePostBookmark + \
2020-07-28 10:31:36 +00:00
'?tl=' + boxName + '" title="' + bookmarkTitle + '">\n'
2020-04-05 09:17:19 +00:00
bookmarkStr += \
2020-09-30 21:26:55 +00:00
' ' + \
2020-06-17 13:21:56 +00:00
'<img loading="lazy" title="' + bookmarkTitle + '" alt="' + \
2020-04-05 09:17:19 +00:00
bookmarkTitle + ' |" src="/' + iconsDir + \
2020-07-28 10:31:36 +00:00
'/' + bookmarkIcon + '"/></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 22:46:12 +00:00
# benchmark 12.9
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 12.9 = ' + str(timeDiff))
2020-08-28 22:46:12 +00:00
2020-04-05 09:17:19 +00:00
isMuted = postIsMuted(baseDir, nickname, domain, postJsonObject, messageId)
2020-08-28 21:57:25 +00:00
# benchmark 13
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 13 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-04-05 09:17:19 +00:00
deleteStr = ''
muteStr = ''
if (allowDeletion or
('/' + fullDomain + '/' in postActor and
messageId.startswith(postActor))):
if '/users/' + nickname + '/' in messageId:
2020-10-08 09:14:21 +00:00
if not isNewsPost(postJsonObject):
deleteStr = \
' <a class="imageAnchor" href="/users/' + \
nickname + \
'?delete=' + messageId + pageNumberParam + \
'" title="' + translate['Delete this post'] + '">\n'
deleteStr += \
' ' + \
'<img loading="lazy" alt="' + \
translate['Delete this post'] + \
' |" title="' + translate['Delete this post'] + \
'" src="/' + iconsDir + '/delete.png"/></a>\n'
2019-12-01 13:45:30 +00:00
else:
if not isMuted:
2020-04-05 09:17:19 +00:00
muteStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + nickname + \
2020-04-05 09:17:19 +00:00
'?mute=' + messageId + pageNumberParam + '?tl=' + boxName + \
'?bm=' + timelinePostBookmark + \
2020-07-28 10:31:36 +00:00
'" title="' + translate['Mute this post'] + '">\n'
2020-04-05 09:17:19 +00:00
muteStr += \
2020-09-30 21:34:54 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<img loading="lazy" alt="' + \
translate['Mute this post'] + \
' |" title="' + translate['Mute this post'] + \
2020-07-28 10:31:36 +00:00
'" src="/' + iconsDir + '/mute.png"/></a>\n'
2019-12-01 13:45:30 +00:00
else:
2020-04-05 09:17:19 +00:00
muteStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="/users/' + \
2020-08-14 19:31:19 +00:00
nickname + '?unmute=' + messageId + \
2020-04-05 09:17:19 +00:00
pageNumberParam + '?tl=' + boxName + '?bm=' + \
timelinePostBookmark + '" title="' + \
2020-07-28 10:31:36 +00:00
translate['Undo mute'] + '">\n'
2020-04-05 09:17:19 +00:00
muteStr += \
2020-09-30 21:34:54 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<img loading="lazy" alt="' + translate['Undo mute'] + \
' |" title="' + translate['Undo mute'] + \
2020-07-28 10:31:36 +00:00
'" src="/' + iconsDir+'/unmute.png"/></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 22:54:37 +00:00
# benchmark 13.1
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 13.1 = ' + str(timeDiff))
2020-08-28 22:54:37 +00:00
2020-04-05 09:17:19 +00:00
replyAvatarImageInPost = ''
2019-08-25 18:08:25 +00:00
if showRepeatIcon:
2019-08-25 17:22:24 +00:00
if isAnnounced:
if postJsonObject['object'].get('attributedTo'):
2020-08-06 16:21:46 +00:00
attributedTo = ''
if isinstance(postJsonObject['object']['attributedTo'], str):
attributedTo = postJsonObject['object']['attributedTo']
2020-04-05 09:17:19 +00:00
if attributedTo.startswith(postActor):
titleStr += \
2020-09-30 20:57:07 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['announces'] + \
'" alt="' + translate['announces'] + \
'" src="/' + iconsDir + \
2020-07-28 10:31:36 +00:00
'/repeat_inactive.png" class="announceOrReply"/>\n'
2019-08-25 17:22:24 +00:00
else:
2020-08-28 22:54:37 +00:00
# benchmark 13.2
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.2 = ' + str(timeDiff))
2020-08-06 16:21:46 +00:00
announceNickname = None
if attributedTo:
announceNickname = getNicknameFromActor(attributedTo)
2019-09-25 12:48:12 +00:00
if announceNickname:
2020-04-05 09:17:19 +00:00
announceDomain, announcePort = \
getDomainFromActor(attributedTo)
getPersonFromCache(baseDir, attributedTo,
personCache, allowDownloads)
2020-04-05 09:17:19 +00:00
announceDisplayName = \
getDisplayName(baseDir, attributedTo, personCache)
2019-09-25 12:48:12 +00:00
if announceDisplayName:
2020-08-28 22:54:37 +00:00
# benchmark 13.3
if not allowDownloads:
timeDiff = \
int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.3 = ' + str(timeDiff))
2020-08-29 08:42:03 +00:00
2019-10-18 12:55:53 +00:00
if ':' in announceDisplayName:
2020-04-05 09:17:19 +00:00
announceDisplayName = \
addEmojiToDisplayName(baseDir, httpPrefix,
nickname, domain,
announceDisplayName,
False)
2020-08-29 08:42:03 +00:00
# benchmark 13.3.1
if not allowDownloads:
timeDiff = \
int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.3.1 = ' + str(timeDiff))
2020-08-29 08:42:03 +00:00
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['announces'] + '" alt="' + \
translate['announces'] + '" src="/' + \
iconsDir + '/repeat_inactive.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
' <a href="' + \
2020-04-05 09:17:19 +00:00
postJsonObject['object']['id'] + '">' + \
2020-07-28 10:31:36 +00:00
announceDisplayName + '</a>\n'
2019-09-25 12:48:12 +00:00
# show avatar of person replied to
2020-04-05 09:17:19 +00:00
announceActor = \
postJsonObject['object']['attributedTo']
announceAvatarUrl = \
getPersonAvatarUrl(baseDir, announceActor,
personCache, allowDownloads)
2020-08-28 22:54:37 +00:00
# benchmark 13.4
if not allowDownloads:
timeDiff = \
int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.4 = ' + str(timeDiff))
2020-08-28 22:54:37 +00:00
2019-09-25 12:48:12 +00:00
if announceAvatarUrl:
2020-04-05 09:17:19 +00:00
idx = 'Show options for this person'
replyAvatarImageInPost = \
2020-09-30 20:26:13 +00:00
' ' \
2020-07-28 10:31:36 +00:00
'<div class="timeline-avatar-reply">\n' \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" ' + \
2020-08-14 19:31:19 +00:00
'href="/users/' + nickname + \
2020-04-05 09:17:19 +00:00
'?options=' + \
announceActor + ';' + str(pageNumber) + \
';' + announceAvatarUrl + \
messageIdStr + '">' \
'<img loading="lazy" src="' + \
announceAvatarUrl + '" ' \
'title="' + translate[idx] + \
'" alt=" "' + avatarPosition + \
2020-09-30 20:41:33 +00:00
'/></a>\n </div>\n'
2019-09-25 12:48:12 +00:00
else:
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 20:41:33 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['announces'] + \
'" alt="' + translate['announces'] + \
'" src="/' + iconsDir + \
'/repeat_inactive.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
' <a href="' + \
2020-04-05 09:17:19 +00:00
postJsonObject['object']['id'] + '">@' + \
announceNickname + '@' + \
2020-07-28 10:31:36 +00:00
announceDomain + '</a>\n'
2019-09-25 12:48:12 +00:00
else:
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 20:41:33 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['announces'] + '" alt="' + \
translate['announces'] + '" src="/' + iconsDir + \
'/repeat_inactive.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
' <a href="' + \
2020-04-05 09:17:19 +00:00
postJsonObject['object']['id'] + \
2020-07-28 10:31:36 +00:00
'">@unattributed</a>\n'
2019-08-22 12:41:16 +00:00
else:
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<img loading="lazy" title="' + translate['announces'] + \
2020-04-05 09:17:19 +00:00
'" alt="' + translate['announces'] + \
'" src="/' + iconsDir + \
'/repeat_inactive.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
' <a href="' + \
2020-07-28 10:31:36 +00:00
postJsonObject['object']['id'] + '">@unattributed</a>\n'
2019-07-21 13:03:57 +00:00
else:
2019-08-25 17:22:24 +00:00
if postJsonObject['object'].get('inReplyTo'):
2020-04-05 09:17:19 +00:00
containerClassIcons = 'containericons darker'
containerClass = 'container darker'
2019-10-31 09:59:06 +00:00
if postJsonObject['object']['inReplyTo'].startswith(postActor):
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 20:41:33 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['replying to themselves'] + \
'" alt="' + translate['replying to themselves'] + \
'" src="/' + iconsDir + \
2020-07-28 10:31:36 +00:00
'/reply.png" class="announceOrReply"/>\n'
2019-08-25 17:22:24 +00:00
else:
2019-09-24 10:09:35 +00:00
if '/statuses/' in postJsonObject['object']['inReplyTo']:
2020-04-05 09:17:19 +00:00
inReplyTo = postJsonObject['object']['inReplyTo']
replyActor = inReplyTo.split('/statuses/')[0]
replyNickname = getNicknameFromActor(replyActor)
2019-09-24 10:09:35 +00:00
if replyNickname:
2020-04-05 09:17:19 +00:00
replyDomain, replyPort = \
getDomainFromActor(replyActor)
2019-09-24 10:09:35 +00:00
if replyNickname and replyDomain:
2020-04-05 09:17:19 +00:00
getPersonFromCache(baseDir, replyActor,
personCache,
allowDownloads)
2020-04-05 09:17:19 +00:00
replyDisplayName = \
getDisplayName(baseDir, replyActor,
personCache)
2019-09-24 10:09:35 +00:00
if replyDisplayName:
2019-10-18 12:55:53 +00:00
if ':' in replyDisplayName:
2020-08-28 22:54:37 +00:00
# benchmark 13.5
if not allowDownloads:
timeDiff = \
int((time.time() -
postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' +
boxName + ' 13.5 = ' +
str(timeDiff))
2020-04-05 09:17:19 +00:00
repDisp = replyDisplayName
replyDisplayName = \
addEmojiToDisplayName(baseDir,
httpPrefix,
nickname,
domain,
repDisp,
False)
2020-08-28 22:54:37 +00:00
# benchmark 13.6
if not allowDownloads:
timeDiff = \
int((time.time() -
postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' +
boxName + ' 13.6 = ' +
str(timeDiff))
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['replying to'] + \
'" alt="' + \
translate['replying to'] + \
'" src="/' + \
iconsDir + '/reply.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
2020-09-30 21:00:05 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<a href="' + inReplyTo + \
2020-07-28 10:31:36 +00:00
'">' + replyDisplayName + '</a>\n'
2019-09-24 11:05:47 +00:00
2020-08-29 08:53:01 +00:00
# benchmark 13.7
if not allowDownloads:
timeDiff = int((time.time() -
postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.7 = ' + str(timeDiff))
2020-08-29 08:53:01 +00:00
2019-09-24 11:11:45 +00:00
# show avatar of person replied to
2020-04-05 09:17:19 +00:00
replyAvatarUrl = \
getPersonAvatarUrl(baseDir,
replyActor,
personCache,
allowDownloads)
2020-08-29 08:53:01 +00:00
# benchmark 13.8
if not allowDownloads:
timeDiff = int((time.time() -
postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName +
' 13.8 = ' + str(timeDiff))
2020-08-29 08:53:01 +00:00
2019-09-24 11:05:47 +00:00
if replyAvatarUrl:
2020-04-05 09:17:19 +00:00
replyAvatarImageInPost = \
2020-09-30 21:21:43 +00:00
' <div class=' + \
2020-07-28 10:31:36 +00:00
'"timeline-avatar-reply">\n'
2020-04-05 09:17:19 +00:00
replyAvatarImageInPost += \
2020-09-30 21:21:43 +00:00
' ' + \
2020-08-14 19:49:51 +00:00
'<a class="imageAnchor" ' + \
'href="/users/' + nickname + \
2020-04-05 09:17:19 +00:00
'?options=' + replyActor + \
';' + str(pageNumber) + ';' + \
replyAvatarUrl + \
2020-07-28 10:31:36 +00:00
messageIdStr + '">\n'
2020-04-05 09:17:19 +00:00
replyAvatarImageInPost += \
2020-09-30 21:21:43 +00:00
' ' + \
2020-04-05 09:17:19 +00:00
'<img loading="lazy" src="' + \
replyAvatarUrl + '" '
replyAvatarImageInPost += \
'title="' + \
translate['Show profile']
replyAvatarImageInPost += \
'" alt=" "' + \
2020-09-30 21:21:43 +00:00
avatarPosition + '/></a>\n' + \
' </div>\n'
2019-09-24 10:09:35 +00:00
else:
2020-04-05 09:17:19 +00:00
inReplyTo = \
postJsonObject['object']['inReplyTo']
titleStr += \
2020-09-30 21:53:10 +00:00
' ' + \
'<img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['replying to'] + \
'" alt="' + \
translate['replying to'] + \
'" src="/' + \
iconsDir + '/reply.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
2020-09-30 21:00:05 +00:00
' <a href="' + \
2020-04-05 09:17:19 +00:00
inReplyTo + '">@' + \
replyNickname + '@' + \
2020-07-28 10:31:36 +00:00
replyDomain + '</a>\n'
2019-09-24 10:09:35 +00:00
else:
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:05:32 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['replying to'] + \
'" alt="' + \
translate['replying to'] + \
'" src="/' + \
iconsDir + \
2020-09-30 20:57:07 +00:00
'/reply.png" class="announceOrReply"/>\n' + \
2020-09-30 21:00:05 +00:00
' <a href="' + \
2020-04-05 09:17:19 +00:00
postJsonObject['object']['inReplyTo'] + \
2020-07-28 10:31:36 +00:00
'">@unknown</a>\n'
2019-09-24 10:09:35 +00:00
else:
2020-04-05 09:17:19 +00:00
postDomain = \
postJsonObject['object']['inReplyTo']
2020-06-11 12:26:15 +00:00
prefixes = getProtocolPrefixes()
2020-06-11 12:16:45 +00:00
for prefix in prefixes:
postDomain = postDomain.replace(prefix, '')
2019-09-24 10:09:35 +00:00
if '/' in postDomain:
2020-04-05 09:17:19 +00:00
postDomain = postDomain.split('/', 1)[0]
2019-09-24 10:09:35 +00:00
if postDomain:
2020-04-05 09:17:19 +00:00
titleStr += \
2020-09-30 21:05:32 +00:00
' <img loading="lazy" title="' + \
2020-04-05 09:17:19 +00:00
translate['replying to'] + \
'" alt="' + translate['replying to'] + \
'" src="/' + \
iconsDir + '/reply.png" ' + \
2020-09-30 20:57:07 +00:00
'class="announceOrReply"/>\n' + \
2020-09-30 21:00:05 +00:00
' <a href="' + \
2020-04-05 09:17:19 +00:00
postJsonObject['object']['inReplyTo'] + \
2020-07-28 10:31:36 +00:00
'">' + postDomain + '</a>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 14
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 14 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-04-05 09:17:19 +00:00
attachmentStr, galleryStr = \
getPostAttachmentsAsHtml(postJsonObject, boxName, translate,
2020-08-16 14:25:31 +00:00
isMuted, avatarLink.strip(),
2020-04-05 09:17:19 +00:00
replyStr, announceStr, likeStr,
bookmarkStr, deleteStr, muteStr)
publishedStr = ''
2019-11-29 22:45:56 +00:00
if postJsonObject['object'].get('published'):
2020-04-05 09:17:19 +00:00
publishedStr = postJsonObject['object']['published']
2019-11-29 22:45:56 +00:00
if '.' not in publishedStr:
if '+' not in publishedStr:
2020-04-05 09:17:19 +00:00
datetimeObject = \
datetime.strptime(publishedStr, "%Y-%m-%dT%H:%M:%SZ")
2019-11-29 22:45:56 +00:00
else:
2020-04-05 09:17:19 +00:00
datetimeObject = \
datetime.strptime(publishedStr.split('+')[0] + 'Z',
2020-02-23 15:32:47 +00:00
"%Y-%m-%dT%H:%M:%SZ")
2019-08-27 22:50:40 +00:00
else:
2020-04-05 09:17:19 +00:00
publishedStr = \
publishedStr.replace('T', ' ').split('.')[0]
datetimeObject = parse(publishedStr)
publishedStr = datetimeObject.strftime("%a %b %d, %H:%M")
2020-02-24 23:14:49 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 15
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 15 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2020-10-08 10:28:14 +00:00
publishedLink = messageId
2020-02-24 23:14:49 +00:00
# blog posts should have no /statuses/ in their link
if isBlogPost(postJsonObject):
# is this a post to the local domain?
2020-04-05 09:17:19 +00:00
if '://' + domain in messageId:
publishedLink = messageId.replace('/statuses/', '/')
2020-03-28 17:50:55 +00:00
# if this is a local link then make it relative so that it works
# on clearnet or onion address
2020-04-05 09:17:19 +00:00
if domain + '/users/' in publishedLink or \
domain + ':' + str(port) + '/users/' in publishedLink:
publishedLink = '/users/' + publishedLink.split('/users/')[1]
2020-02-24 23:14:49 +00:00
2020-10-08 10:32:59 +00:00
if not isNewsPost(postJsonObject):
footerStr = '<a href="' + publishedLink + \
'" class="' + timeClass + '">' + publishedStr + '</a>\n'
else:
2020-10-08 10:44:39 +00:00
footerStr = ' <label class="' + timeClass + '">' + \
publishedStr + '</label>\n'
2019-07-31 19:37:29 +00:00
# change the background color for DMs in inbox timeline
if showDMicon:
2020-04-05 09:17:19 +00:00
containerClassIcons = 'containericons dm'
containerClass = 'container dm'
2019-07-30 12:47:42 +00:00
if showIcons:
2020-09-30 21:13:39 +00:00
footerStr = '\n <div class="' + containerClassIcons + '">\n'
2020-04-05 09:17:19 +00:00
footerStr += replyStr + announceStr + likeStr + bookmarkStr + \
deleteStr + muteStr + editStr
2020-10-08 10:40:45 +00:00
if not isNewsPost(postJsonObject):
footerStr += ' <a href="' + publishedLink + '" class="' + \
timeClass + '">' + publishedStr + '</a>\n'
else:
2020-10-08 10:44:39 +00:00
footerStr += ' <label class="' + timeClass + '">' + \
publishedStr + '</label>\n'
2020-09-30 21:15:49 +00:00
footerStr += ' </div>\n'
2020-04-05 09:17:19 +00:00
postIsSensitive = False
2020-02-18 10:04:31 +00:00
if postJsonObject['object'].get('sensitive'):
# sensitive posts should have a summary
if postJsonObject['object'].get('summary'):
2020-04-05 09:17:19 +00:00
postIsSensitive = postJsonObject['object']['sensitive']
else:
# add a generic summary if none is provided
2020-04-05 09:17:19 +00:00
postJsonObject['object']['summary'] = translate['Sensitive']
2020-02-23 15:32:47 +00:00
# add an extra line if there is a content warning,
# for better vertical spacing on mobile
2020-02-18 10:04:31 +00:00
if postIsSensitive:
2020-04-05 09:17:19 +00:00
footerStr = '<br>' + footerStr
2020-01-03 09:37:30 +00:00
2019-10-17 22:45:02 +00:00
if not postJsonObject['object'].get('summary'):
2020-04-05 09:17:19 +00:00
postJsonObject['object']['summary'] = ''
2019-11-04 20:39:14 +00:00
2020-08-05 12:47:15 +00:00
if postJsonObject['object'].get('cipherText'):
postJsonObject['object']['content'] = \
2020-08-06 20:16:42 +00:00
E2EEdecryptMessageFromDevice(postJsonObject['object'])
2020-08-05 12:47:15 +00:00
2019-11-29 22:49:17 +00:00
if not postJsonObject['object'].get('content'):
return ''
2020-08-05 12:47:15 +00:00
isPatch = isGitPatch(baseDir, nickname, domain,
2020-05-03 13:12:52 +00:00
postJsonObject['object']['type'],
postJsonObject['object']['summary'],
postJsonObject['object']['content'])
2020-08-28 21:57:25 +00:00
# benchmark 16
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 16 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
if not isPatch:
objectContent = \
removeLongWords(postJsonObject['object']['content'], 40, [])
objectContent = removeTextFormatting(objectContent)
objectContent = \
switchWords(baseDir, nickname, domain, objectContent)
2020-09-14 09:41:44 +00:00
objectContent = htmlReplaceEmailQuote(objectContent)
2020-08-02 17:27:56 +00:00
objectContent = htmlReplaceQuoteMarks(objectContent)
2020-05-02 20:06:01 +00:00
else:
2020-08-02 17:27:56 +00:00
objectContent = \
2020-08-02 17:55:00 +00:00
postJsonObject['object']['content']
2020-05-02 20:06:01 +00:00
2020-02-18 10:04:31 +00:00
if not postIsSensitive:
2020-04-05 09:17:19 +00:00
contentStr = objectContent + attachmentStr
contentStr = addEmbeddedElements(translate, contentStr)
contentStr = insertQuestion(baseDir, translate,
nickname, domain, port,
contentStr, postJsonObject,
pageNumber)
2019-07-31 12:44:08 +00:00
else:
2020-04-05 09:17:19 +00:00
postID = 'post' + str(createPassword(8))
contentStr = ''
2019-07-31 12:44:08 +00:00
if postJsonObject['object'].get('summary'):
2020-04-05 09:17:19 +00:00
contentStr += \
2020-10-07 21:33:23 +00:00
'<b>' + str(postJsonObject['object']['summary']) + '</b>\n '
if isModerationPost:
2020-04-05 09:17:19 +00:00
containerClass = 'container report'
# get the content warning text
cwContentStr = objectContent + attachmentStr
if not isPatch:
cwContentStr = addEmbeddedElements(translate, cwContentStr)
cwContentStr = \
insertQuestion(baseDir, translate, nickname, domain, port,
cwContentStr, postJsonObject, pageNumber)
if not isBlogPost(postJsonObject):
# get the content warning button
2020-07-03 10:24:27 +00:00
contentStr += \
getContentWarningButton(postID, translate, cwContentStr)
else:
contentStr += cwContentStr
2019-07-31 12:44:08 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 17
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 17 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
if postJsonObject['object'].get('tag') and not isPatch:
2020-04-05 09:17:19 +00:00
contentStr = \
replaceEmojiFromTags(contentStr,
postJsonObject['object']['tag'],
'content')
2019-08-19 12:59:57 +00:00
2019-12-01 13:45:30 +00:00
if isMuted:
2020-04-05 09:17:19 +00:00
contentStr = ''
2019-12-01 13:45:30 +00:00
else:
if not isPatch:
2020-09-30 20:50:13 +00:00
contentStr = ' <div class="message">' + \
2020-09-30 21:53:10 +00:00
contentStr + \
' </div>\n'
2020-05-02 19:24:17 +00:00
else:
2020-05-02 19:39:09 +00:00
contentStr = \
2020-05-02 21:12:06 +00:00
'<div class="gitpatch"><pre><code>' + contentStr + \
2020-07-28 10:31:36 +00:00
'</code></pre></div>\n'
2020-04-05 09:17:19 +00:00
postHtml = ''
if boxName != 'tlmedia':
2020-09-30 20:20:08 +00:00
postHtml = ' <div id="' + timelinePostBookmark + \
2020-04-05 09:17:19 +00:00
'" class="' + containerClass + '">\n'
postHtml += avatarImageInPost
2020-10-01 10:06:44 +00:00
postHtml += ' <div class="post-title">\n' + \
2020-09-30 20:45:39 +00:00
' ' + titleStr + \
2020-10-01 10:06:44 +00:00
replyAvatarImageInPost + ' </div>\n'
2020-07-28 10:31:36 +00:00
postHtml += contentStr + footerStr + '\n'
2020-09-30 21:21:43 +00:00
postHtml += ' </div>\n'
2019-09-28 11:29:42 +00:00
else:
2020-04-05 09:17:19 +00:00
postHtml = galleryStr
2019-10-19 09:13:08 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 18
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 18 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
if not showPublicOnly and storeToCache and \
2020-05-21 20:15:24 +00:00
boxName != 'tlmedia' and boxName != 'tlbookmarks' and \
boxName != 'bookmarks':
2020-04-05 09:17:19 +00:00
saveIndividualPostAsHtmlToCache(baseDir, nickname, domain,
postJsonObject, postHtml)
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
postJsonObject, postHtml)
2019-10-19 09:13:08 +00:00
2020-08-28 21:57:25 +00:00
# benchmark 19
if not allowDownloads:
timeDiff = int((time.time() - postStartTime) * 1000)
if timeDiff > 100:
print('TIMING INDIV ' + boxName + ' 19 = ' + str(timeDiff))
2020-08-28 21:57:25 +00:00
2019-10-19 11:53:57 +00:00
return postHtml
2019-07-21 09:09:28 +00:00
2020-04-05 09:17:19 +00:00
2019-09-06 15:08:32 +00:00
def isQuestion(postObjectJson: {}) -> bool:
""" is the given post a question?
"""
2020-04-05 09:17:19 +00:00
if postObjectJson['type'] != 'Create' and \
postObjectJson['type'] != 'Update':
2019-11-25 10:10:59 +00:00
return False
if not isinstance(postObjectJson['object'], dict):
return False
if not postObjectJson['object'].get('type'):
return False
2020-04-05 09:17:19 +00:00
if postObjectJson['object']['type'] != 'Question':
2019-11-25 10:10:59 +00:00
return False
if not postObjectJson['object'].get('oneOf'):
return False
if not isinstance(postObjectJson['object']['oneOf'], list):
return False
return True
2019-09-06 15:08:32 +00:00
2020-04-05 09:17:19 +00:00
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
2020-06-17 11:53:41 +00:00
return '*' + label + '*'
2020-10-01 17:26:21 +00:00
def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
2020-10-01 17:58:12 +00:00
httpPrefix: str, translate: {},
2020-10-01 18:02:48 +00:00
iconsDir: str, moderator: bool) -> str:
"""Returns html content for the left column
"""
2020-10-01 18:02:48 +00:00
htmlStr = ''
2020-10-02 14:37:05 +00:00
domain = domainFull
if ':' in domain:
domain = domain.split(':')
leftColumnImageFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \
'/left_col_image.png'
if not os.path.isfile(leftColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeLeftColumnImageFilename = \
baseDir + '/img/left_col_image' + theme + '.png'
if os.path.isfile(themeLeftColumnImageFilename):
copyfile(themeLeftColumnImageFilename, leftColumnImageFilename)
# show the image at the top of the column
editImageClass = 'leftColEdit'
2020-10-02 14:37:05 +00:00
if os.path.isfile(leftColumnImageFilename):
editImageClass = 'leftColEditImage'
2020-10-02 14:37:05 +00:00
htmlStr += \
2020-10-02 21:56:22 +00:00
'\n <center>\n' + \
2020-10-02 20:33:30 +00:00
' <img class="leftColImg" loading="lazy" src="/users/' + \
nickname + '/left_col_image.png" />\n' + \
2020-10-02 14:37:05 +00:00
' </center>\n'
2020-10-02 15:58:25 +00:00
if editImageClass == 'leftColEdit':
2020-10-02 21:56:22 +00:00
htmlStr += '\n <center>\n'
2020-10-02 15:58:25 +00:00
2020-10-01 18:02:48 +00:00
if moderator:
# show the edit icon
2020-10-01 18:02:48 +00:00
htmlStr += \
2020-10-02 15:19:18 +00:00
' <a href="' + \
2020-10-02 15:58:25 +00:00
'/users/' + nickname + '/editlinks">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
2020-10-01 20:22:44 +00:00
translate['Edit Links'] + '" title="' + \
translate['Edit Links'] + '" src="/' + \
2020-10-02 15:19:18 +00:00
iconsDir + '/edit.png" /></a>\n'
2020-10-02 15:58:25 +00:00
# RSS icon
htmlStr += \
' <a href="' + \
httpPrefix + '://' + domainFull + \
'/blog/' + nickname + '/rss.xml">' + \
'<img class="' + editImageClass + \
2020-10-03 09:42:33 +00:00
'" loading="lazy" alt="' + \
translate['RSS feed for this site'] + \
'" title="' + translate['RSS feed for this site'] + \
'" src="/' + iconsDir + '/rss.png" /></a>\n'
2020-10-02 15:58:25 +00:00
if editImageClass == 'leftColEdit':
2020-10-02 21:56:22 +00:00
htmlStr += ' </center>\n'
2020-10-02 15:58:25 +00:00
else:
htmlStr += ' <br>\n'
2020-10-01 17:26:21 +00:00
2020-10-01 18:47:24 +00:00
linksFilename = baseDir + '/accounts/links.txt'
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:
2020-10-01 22:48:10 +00:00
if '#' not in lineStr:
if '*' not in lineStr:
continue
2020-10-01 18:47:24 +00:00
lineStr = lineStr.strip()
words = lineStr.split(' ')
# get the link
linkStr = None
for word in words:
2020-10-01 22:48:10 +00:00
if word == '#':
continue
if word == '*':
continue
2020-10-02 10:43:47 +00:00
if '://' in word:
2020-10-01 18:47:24 +00:00
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 += \
2020-10-01 20:13:42 +00:00
' <p><a href="' + linkStr + '">' + \
lineStr + '</a></p>\n'
2020-10-01 22:40:07 +00:00
else:
2020-10-01 22:58:11 +00:00
if lineStr.startswith('#') or lineStr.startswith('*'):
lineStr = lineStr[1:].strip()
htmlStr += \
2020-10-02 09:47:25 +00:00
' <h3 class="linksHeader">' + \
lineStr + '</h3>\n'
2020-10-01 22:58:11 +00:00
else:
htmlStr += \
' <p>' + lineStr + '</p>\n'
2020-10-01 22:40:07 +00:00
return htmlStr
2020-10-08 17:05:01 +00:00
def votesOnNewswireItem(status: []) -> int:
"""Returns the number of votes on a newswire item
"""
totalVotes = 0
for line in status:
if 'vote:' in line:
totalVotes += 1
return totalVotes
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
2020-10-06 21:28:40 +00:00
def htmlNewswire(newswire: str, nickname: str, moderator: bool,
translate: {}, positiveVoting: bool) -> str:
2020-10-04 20:53:34 +00:00
"""Converts a newswire dict into html
"""
htmlStr = ''
for dateStr, item in newswire.items():
2020-10-06 19:32:31 +00:00
dateStrLink = dateStr.replace(' ', 'T')
dateStrLink = dateStrLink.replace('+00:00', '')
if 'vote:' + nickname in item[2]:
totalVotesStr = ''
2020-10-08 17:05:01 +00:00
totalVotes = 0
if moderator:
2020-10-08 17:05:01 +00:00
totalVotes = votesOnNewswireItem(item[2])
totalVotesStr = \
votesIndicator(totalVotes, positiveVoting)
2020-10-08 17:18:13 +00:00
htmlStr += '<p class="newswireItemVotedOn">' + \
'<a href="' + item[1] + '">' + item[0] + '</a>' + \
totalVotesStr
if moderator:
htmlStr += \
2020-10-06 19:25:41 +00:00
' ' + \
'<a href="/users/' + nickname + \
2020-10-06 21:28:40 +00:00
'/newswireunvote=' + dateStrLink + '" ' + \
'title="' + translate['Remove Vote'] + '">' + \
2020-10-08 17:18:13 +00:00
'<label class="newswireDateVotedOn">'
2020-10-06 21:33:54 +00:00
htmlStr += dateStr.replace('+00:00', '') + '</label></a></p>'
else:
2020-10-08 17:18:13 +00:00
htmlStr += ' <label class="newswireDateVotedOn">'
htmlStr += dateStr.replace('+00:00', '') + '</label></p>'
else:
totalVotesStr = ''
2020-10-08 17:05:01 +00:00
totalVotes = 0
if moderator:
2020-10-08 17:05:01 +00:00
totalVotes = votesOnNewswireItem(item[2])
# show a number of ticks or crosses for how many
# votes for or against
2020-10-08 17:05:01 +00:00
totalVotesStr = \
votesIndicator(totalVotes, positiveVoting)
htmlStr += '<p class="newswireItem">' + \
'<a href="' + item[1] + '">' + item[0] + '</a>' + \
totalVotesStr
if moderator:
htmlStr += \
2020-10-06 19:25:41 +00:00
' ' + \
'<a href="/users/' + nickname + \
2020-10-06 21:28:40 +00:00
'/newswirevote=' + dateStrLink + '" ' + \
'title="' + translate['Vote'] + '">' + \
2020-10-06 19:25:41 +00:00
'<label class="newswireDate">'
htmlStr += dateStr.replace('+00:00', '') + '</label></a></p>'
else:
htmlStr += ' <label class="newswireDate">'
htmlStr += dateStr.replace('+00:00', '') + '</label></p>'
2020-10-04 20:53:34 +00:00
return htmlStr
2020-10-02 19:32:33 +00:00
def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
httpPrefix: str, translate: {},
iconsDir: str, moderator: bool,
newswire: {}, positiveVoting: bool) -> str:
"""Returns html content for the right column
"""
htmlStr = ''
2020-10-02 19:28:51 +00:00
domain = domainFull
if ':' in domain:
domain = domain.split(':')
rightColumnImageFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \
'/right_col_image.png'
if not os.path.isfile(rightColumnImageFilename):
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeRightColumnImageFilename = \
baseDir + '/img/right_col_image' + theme + '.png'
if os.path.isfile(themeRightColumnImageFilename):
copyfile(themeRightColumnImageFilename, rightColumnImageFilename)
# show the image at the top of the column
editImageClass = 'rightColEdit'
if os.path.isfile(rightColumnImageFilename):
editImageClass = 'rightColEditImage'
htmlStr += \
2020-10-02 21:56:22 +00:00
'\n <center>\n' + \
' <img class="rightColImg" ' + \
'loading="lazy" src="/users/' + \
nickname + '/right_col_image.png" />\n' + \
2020-10-02 19:28:51 +00:00
' </center>\n'
2020-10-03 19:33:02 +00:00
if editImageClass == 'rightColEdit':
htmlStr += '\n <center>\n'
2020-10-02 19:28:51 +00:00
if moderator:
if os.path.isfile(baseDir + '/accounts/newswiremoderation.txt'):
# show the edit icon highlighted
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/editnewswire">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Edit newswire'] + '" title="' + \
translate['Edit newswire'] + '" src="/' + \
iconsDir + '/edit_notify.png" /></a>\n'
else:
# show the edit icon
htmlStr += \
' <a href="' + \
'/users/' + nickname + '/editnewswire">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Edit newswire'] + '" title="' + \
translate['Edit newswire'] + '" src="/' + \
iconsDir + '/edit.png" /></a>\n'
2020-10-02 19:28:51 +00:00
2020-10-04 12:29:07 +00:00
htmlStr += \
' <a href="/newswire.xml">' + \
'<img class="' + editImageClass + \
'" loading="lazy" alt="' + \
translate['Newswire RSS Feed'] + '" title="' + \
translate['Newswire RSS Feed'] + '" src="/' + \
iconsDir + '/rss.png" /></a>\n'
2020-10-03 19:33:02 +00:00
if editImageClass == 'rightColEdit':
htmlStr += ' </center>\n'
else:
htmlStr += ' <br>\n'
2020-10-03 16:48:05 +00:00
htmlStr += htmlNewswire(newswire, nickname, moderator, translate,
positiveVoting)
return htmlStr
2020-04-05 09:17:19 +00:00
def htmlTimeline(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,
2020-05-23 14:23:56 +00:00
manuallyApproveFollowers: bool,
minimal: bool,
YTReplacementDomain: str,
newswire: {}, moderator: bool,
positiveVoting: bool) -> str:
2019-07-21 09:09:28 +00:00
"""Show the timeline as html
"""
2020-08-28 20:59:54 +00:00
timelineStartTime = time.time()
2020-04-05 09:17:19 +00:00
accountDir = baseDir + '/accounts/' + nickname + '@' + domain
# should the calendar icon be highlighted?
newCalendarEvent = False
2020-04-05 09:17:19 +00:00
calendarImage = 'calendar.png'
calendarPath = '/calendar'
calendarFile = accountDir + '/.newCalendar'
if os.path.isfile(calendarFile):
newCalendarEvent = True
2020-04-05 09:17:19 +00:00
calendarImage = 'calendar_notify.png'
with open(calendarFile, 'r') as calfile:
2020-05-22 11:32:38 +00:00
calendarPath = calfile.read().replace('##sent##', '')
calendarPath = calendarPath.replace('\n', '').replace('\r', '')
# should the DM button be highlighted?
2020-04-05 09:17:19 +00:00
newDM = False
dmFile = accountDir + '/.newDM'
if os.path.isfile(dmFile):
2020-04-05 09:17:19 +00:00
newDM = True
if boxName == 'dm':
os.remove(dmFile)
# should the Replies button be highlighted?
2020-04-05 09:17:19 +00:00
newReply = False
replyFile = accountDir + '/.newReply'
if os.path.isfile(replyFile):
2020-04-05 09:17:19 +00:00
newReply = True
if boxName == 'tlreplies':
os.remove(replyFile)
2019-11-02 11:31:25 +00:00
# should the Shares button be highlighted?
2020-04-05 09:17:19 +00:00
newShare = False
newShareFile = accountDir + '/.newShare'
2019-11-02 11:31:25 +00:00
if os.path.isfile(newShareFile):
2020-04-05 09:17:19 +00:00
newShare = True
if boxName == 'tlshares':
2019-11-02 11:31:25 +00:00
os.remove(newShareFile)
2020-08-24 09:31:31 +00:00
# should the Moderation/reports button be highlighted?
2020-04-05 09:17:19 +00:00
newReport = False
newReportFile = accountDir + '/.newReport'
if os.path.isfile(newReportFile):
2020-04-05 09:17:19 +00:00
newReport = True
if boxName == 'moderation':
os.remove(newReportFile)
2020-08-24 09:31:31 +00:00
# directory where icons are found
# This changes depending upon theme
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
2020-08-24 09:31:31 +00:00
# the css filename
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-11-14 14:52:35 +00:00
2020-08-24 09:31:31 +00:00
# filename of the banner shown at the top
2020-04-05 09:17:19 +00:00
bannerFile = 'banner.png'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
2019-11-14 14:52:35 +00:00
if not os.path.isfile(bannerFilename):
2020-04-05 09:17:19 +00:00
bannerFile = 'banner.jpg'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
2019-11-14 14:52:35 +00:00
if not os.path.isfile(bannerFilename):
2020-04-05 09:17:19 +00:00
bannerFile = 'banner.gif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
if not os.path.isfile(bannerFilename):
bannerFile = 'banner.avif'
bannerFilename = baseDir + '/accounts/' + \
nickname + '@' + domain + '/' + bannerFile
2019-11-14 14:52:35 +00:00
if not os.path.isfile(bannerFilename):
2020-04-05 09:17:19 +00:00
bannerFile = 'banner.webp'
2020-03-22 21:16:02 +00:00
2020-08-28 20:59:54 +00:00
# benchmark 1
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 1 = ' + str(timeDiff))
2020-03-22 21:16:02 +00:00
with open(cssFilename, 'r') as cssFile:
2020-08-24 09:31:31 +00:00
# load css
2020-04-05 09:17:19 +00:00
profileStyle = \
cssFile.read().replace('banner.png',
'/users/' + nickname + '/' + bannerFile)
2020-08-24 09:31:31 +00:00
# replace any https within the css with whatever prefix is needed
2020-04-05 09:17:19 +00:00
if httpPrefix != 'https':
profileStyle = \
profileStyle.replace('https://',
httpPrefix + '://')
2020-08-24 09:31:31 +00:00
# is the user a moderator?
if not moderator:
moderator = isModerator(baseDir, nickname)
2020-04-05 09:17:19 +00:00
2020-08-28 20:59:54 +00:00
# benchmark 2
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 2 = ' + str(timeDiff))
2020-08-24 09:31:31 +00:00
# the appearance of buttons - highlighted or not
2020-04-05 09:17:19 +00:00
inboxButton = 'button'
blogsButton = 'button'
2020-10-07 09:10:42 +00:00
newsButton = 'button'
2020-04-05 09:17:19 +00:00
dmButton = 'button'
if newDM:
2020-04-05 09:17:19 +00:00
dmButton = 'buttonhighlighted'
repliesButton = 'button'
if newReply:
2020-04-05 09:17:19 +00:00
repliesButton = 'buttonhighlighted'
mediaButton = 'button'
bookmarksButton = 'button'
2020-08-23 11:13:35 +00:00
eventsButton = 'button'
2020-04-05 09:17:19 +00:00
sentButton = 'button'
sharesButton = 'button'
2019-11-16 16:08:13 +00:00
if newShare:
2020-04-05 09:17:19 +00:00
sharesButton = 'buttonhighlighted'
moderationButton = 'button'
2019-11-16 16:08:13 +00:00
if newReport:
2020-04-05 09:17:19 +00:00
moderationButton = 'buttonhighlighted'
if boxName == 'inbox':
inboxButton = 'buttonselected'
elif boxName == 'tlblogs':
blogsButton = 'buttonselected'
2020-10-07 09:10:42 +00:00
elif boxName == 'tlnews':
newsButton = 'buttonselected'
2020-04-05 09:17:19 +00:00
elif boxName == 'dm':
dmButton = 'buttonselected'
if newDM:
2020-04-05 09:17:19 +00:00
dmButton = 'buttonselectedhighlighted'
elif boxName == 'tlreplies':
repliesButton = 'buttonselected'
if newReply:
2020-04-05 09:17:19 +00:00
repliesButton = 'buttonselectedhighlighted'
elif boxName == 'tlmedia':
mediaButton = 'buttonselected'
elif boxName == 'outbox':
sentButton = 'buttonselected'
elif boxName == 'moderation':
moderationButton = 'buttonselected'
if newReport:
2020-04-05 09:17:19 +00:00
moderationButton = 'buttonselectedhighlighted'
elif boxName == 'tlshares':
sharesButton = 'buttonselected'
2019-11-02 11:31:25 +00:00
if newShare:
2020-04-05 09:17:19 +00:00
sharesButton = 'buttonselectedhighlighted'
2020-05-21 20:15:24 +00:00
elif boxName == 'tlbookmarks' or boxName == 'bookmarks':
2020-04-05 09:17:19 +00:00
bookmarksButton = 'buttonselected'
2020-08-26 10:06:16 +00:00
elif boxName == 'tlevents':
2020-08-23 11:13:35 +00:00
eventsButton = 'buttonselected'
2019-11-02 14:53:30 +00:00
2020-08-24 09:31:31 +00:00
# get the full domain, including any port number
2020-04-05 09:17:19 +00:00
fullDomain = domain
if port != 80 and port != 443:
2019-11-02 14:53:30 +00:00
if ':' not in domain:
2020-04-05 09:17:19 +00:00
fullDomain = domain + ':' + str(port)
2020-08-24 09:31:31 +00:00
2020-04-05 09:17:19 +00:00
usersPath = '/users/' + nickname
actor = httpPrefix + '://' + fullDomain + usersPath
2019-07-29 20:56:07 +00:00
2020-04-05 09:17:19 +00:00
showIndividualPostIcons = True
2020-03-22 21:16:02 +00:00
2020-08-24 09:31:31 +00:00
# show an icon for new follow approvals
2020-04-05 09:17:19 +00:00
followApprovals = ''
followRequestsFilename = \
baseDir + '/accounts/' + \
nickname + '@' + domain + '/followrequests.txt'
2019-07-29 20:56:07 +00:00
if os.path.isfile(followRequestsFilename):
2020-04-05 09:17:19 +00:00
with open(followRequestsFilename, 'r') as f:
2019-07-29 20:56:07 +00:00
for line in f:
2020-04-05 09:17:19 +00:00
if len(line) > 0:
2019-07-30 10:21:02 +00:00
# show follow approvals icon
2020-04-05 09:17:19 +00:00
followApprovals = \
'<a href="' + usersPath + \
'/followers#buttonheader">' + \
'<img loading="lazy" ' + \
2020-04-05 09:17:19 +00:00
'class="timelineicon" alt="' + \
translate['Approve follow requests'] + \
'" title="' + translate['Approve follow requests'] + \
2020-07-28 10:31:36 +00:00
'" src="/' + iconsDir + '/person.png"/></a>\n'
2019-07-29 20:56:07 +00:00
break
2020-08-28 20:59:54 +00:00
# benchmark 3
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 3 = ' + str(timeDiff))
2020-08-24 09:31:31 +00:00
# moderation / reports button
2020-04-05 09:17:19 +00:00
moderationButtonStr = ''
2020-05-23 14:23:56 +00:00
if moderator and not minimal:
2020-04-05 09:17:19 +00:00
moderationButtonStr = \
'<a href="' + usersPath + \
'/moderation"><button class="' + \
moderationButton + '"><span>' + \
htmlHighlightLabel(translate['Mod'], newReport) + \
2020-07-28 10:31:36 +00:00
' </span></button></a>\n'
2020-04-05 09:17:19 +00:00
2020-08-24 09:31:31 +00:00
# shares, bookmarks and events buttons
2020-05-23 14:23:56 +00:00
sharesButtonStr = ''
bookmarksButtonStr = ''
2020-08-23 11:13:35 +00:00
eventsButtonStr = ''
2020-05-23 14:23:56 +00:00
if not minimal:
sharesButtonStr = \
'<a href="' + usersPath + '/tlshares"><button class="' + \
sharesButton + '"><span>' + \
htmlHighlightLabel(translate['Shares'], newShare) + \
2020-07-28 10:31:36 +00:00
' </span></button></a>\n'
2019-11-02 11:31:25 +00:00
2020-05-23 14:23:56 +00:00
bookmarksButtonStr = \
'<a href="' + usersPath + '/tlbookmarks"><button class="' + \
bookmarksButton + '"><span>' + translate['Bookmarks'] + \
2020-07-28 10:31:36 +00:00
' </span></button></a>\n'
2019-11-17 14:01:49 +00:00
2020-08-23 11:13:35 +00:00
eventsButtonStr = \
'<a href="' + usersPath + '/tlevents"><button class="' + \
eventsButton + '"><span>' + translate['Events'] + \
' </span></button></a>\n'
2020-04-05 09:17:19 +00:00
tlStr = htmlHeader(cssFilename, profileStyle)
2020-08-28 20:59:54 +00:00
# benchmark 4
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 4 = ' + str(timeDiff))
2020-08-24 09:05:46 +00:00
# what screen to go to when a new post is created
if boxName == 'dm':
2020-04-05 09:17:19 +00:00
newPostButtonStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/newdm"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
translate['Create a new DM'] + \
'" alt="| ' + translate['Create a new DM'] + \
2020-07-28 10:31:36 +00:00
'" class="timelineicon"/></a>\n'
2020-10-07 09:55:28 +00:00
elif boxName == 'tlblogs' or boxName == 'tlnews':
2020-08-24 09:05:46 +00:00
newPostButtonStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-08-24 09:05:46 +00:00
'/newblog"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + '" alt="| ' + \
translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
elif boxName == 'tlevents':
newPostButtonStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-08-24 09:05:46 +00:00
'/newevent"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
translate['Create a new event'] + '" alt="| ' + \
translate['Create a new event'] + \
'" class="timelineicon"/></a>\n'
else:
if not manuallyApproveFollowers:
newPostButtonStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-08-24 09:05:46 +00:00
'/newpost"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + '" alt="| ' + \
translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
else:
newPostButtonStr = \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-08-24 09:05:46 +00:00
'/newfollowers"><img loading="lazy" src="/' + \
iconsDir + '/newpost.png" title="' + \
translate['Create a new post'] + \
'" alt="| ' + translate['Create a new post'] + \
'" class="timelineicon"/></a>\n'
2020-04-05 09:17:19 +00:00
# This creates a link to the profile page when viewed
# in lynx, but should be invisible in a graphical web browser
tlStr += \
2020-10-03 09:00:50 +00:00
'<label class="transparent"><a href="/users/' + nickname + '">' + \
translate['Switch to profile view'] + '</a></label>\n'
2020-03-22 21:16:02 +00:00
# banner and row of buttons
2020-04-05 09:17:19 +00:00
tlStr += \
'<a href="/users/' + nickname + '" title="' + \
translate['Switch to profile view'] + '" alt="' + \
translate['Switch to profile view'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += '<div class="timeline-banner">'
2020-07-28 10:31:36 +00:00
tlStr += '</div>\n</a>\n'
2020-02-24 14:39:25 +00:00
2020-10-01 09:07:04 +00:00
# start the timeline
tlStr += '<table class="timeline">\n'
tlStr += ' <colgroup>\n'
tlStr += ' <col span="1" class="column-left">\n'
tlStr += ' <col span="1" class="column-center">\n'
tlStr += ' <col span="1" class="column-right">\n'
tlStr += ' </colgroup>\n'
2020-10-03 18:35:41 +00:00
tlStr += ' <tbody>\n'
tlStr += ' <tr>\n'
2020-10-01 17:26:21 +00:00
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2020-10-01 09:07:04 +00:00
# left column
2020-10-01 17:26:21 +00:00
leftColumnStr = \
getLeftColumnContent(baseDir, nickname, domainFull,
2020-10-01 18:02:48 +00:00
httpPrefix, translate, iconsDir,
moderator)
2020-10-03 11:18:45 +00:00
tlStr += ' <td valign="top" class="col-left">' + \
leftColumnStr + ' </td>\n'
2020-10-01 09:07:04 +00:00
# center column containing posts
2020-10-03 11:18:45 +00:00
tlStr += ' <td valign="top" class="col-center">\n'
2020-09-30 21:53:10 +00:00
2020-09-30 20:07:31 +00:00
# start of the button header with inbox, outbox, etc
2020-09-30 20:00:55 +00:00
tlStr += ' <div class="container">\n'
2020-02-24 14:39:25 +00:00
# first button
2020-04-05 09:17:19 +00:00
if defaultTimeline == 'tlmedia':
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/tlmedia"><button class="' + \
mediaButton + '"><span>' + translate['Media'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-04-05 09:17:19 +00:00
elif defaultTimeline == 'tlblogs':
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/tlblogs"><button class="' + \
blogsButton + '"><span>' + translate['Blogs'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-10-07 09:10:42 +00:00
elif defaultTimeline == 'tlnews':
tlStr += \
' <a href="' + usersPath + \
'/tlnews"><button class="' + \
newsButton + '"><span>' + translate['News'] + \
'</span></button></a>\n'
2020-02-24 14:39:25 +00:00
else:
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/inbox"><button class="' + \
inboxButton + '"><span>' + \
2020-07-28 10:31:36 +00:00
translate['Inbox'] + '</span></button></a>\n'
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + '/dm"><button class="' + dmButton + \
'"><span>' + htmlHighlightLabel(translate['DM'], newDM) + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + '/tlreplies"><button class="' + \
repliesButton + '"><span>' + \
htmlHighlightLabel(translate['Replies'], newReply) + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-02-24 14:39:25 +00:00
# typically the media button
2020-04-05 09:17:19 +00:00
if defaultTimeline != 'tlmedia':
2020-05-23 14:23:56 +00:00
if not minimal:
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-05-23 14:23:56 +00:00
'/tlmedia"><button class="' + \
mediaButton + '"><span>' + translate['Media'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2019-11-28 16:16:43 +00:00
else:
2020-05-23 14:23:56 +00:00
if not minimal:
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-05-23 14:23:56 +00:00
'/inbox"><button class="' + \
inboxButton+'"><span>' + translate['Inbox'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-02-24 14:39:25 +00:00
# typically the blogs button
2020-08-24 09:31:31 +00:00
# but may change if this is a blogging oriented instance
2020-04-05 09:17:19 +00:00
if defaultTimeline != 'tlblogs':
2020-05-23 14:23:56 +00:00
if not minimal:
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-05-23 14:23:56 +00:00
'/tlblogs"><button class="' + \
blogsButton + '"><span>' + translate['Blogs'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-10-07 09:10:42 +00:00
else:
if not minimal:
tlStr += \
' <a href="' + usersPath + \
'/inbox"><button class="' + \
inboxButton + '"><span>' + translate['Inbox'] + \
'</span></button></a>\n'
# typically the news button
# but may change if this is a news oriented instance
if defaultTimeline != 'tlnews':
if not minimal:
tlStr += \
' <a href="' + usersPath + \
'/tlnews"><button class="' + \
newsButton + '"><span>' + translate['News'] + \
'</span></button></a>\n'
2020-02-24 14:39:25 +00:00
else:
2020-05-23 14:23:56 +00:00
if not minimal:
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-05-23 14:23:56 +00:00
'/inbox"><button class="' + \
inboxButton + '"><span>' + translate['Inbox'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-02-24 14:39:25 +00:00
2020-08-24 09:31:31 +00:00
# button for the outbox
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 20:00:55 +00:00
' <a href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/outbox"><button class="' + \
sentButton+'"><span>' + translate['Outbox'] + \
2020-07-28 10:31:36 +00:00
'</span></button></a>\n'
2020-08-24 09:31:31 +00:00
# add other buttons
2020-04-05 09:17:19 +00:00
tlStr += \
2020-08-23 11:13:35 +00:00
sharesButtonStr + bookmarksButtonStr + eventsButtonStr + \
2020-04-05 09:17:19 +00:00
moderationButtonStr + newPostButtonStr
2020-08-24 09:31:31 +00:00
# show todays events buttons on the first inbox page
if boxName == 'inbox' and pageNumber == 1:
if todaysEventsCheck(baseDir, nickname, domain):
now = datetime.now()
# happening today button
tlStr += \
' <a href="' + usersPath + '/calendar?year=' + \
str(now.year) + '?month=' + str(now.month) + \
'?day=' + str(now.day) + '"><button class="buttonevent">' + \
translate['Happening Today'] + '</button></a>\n'
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
tlStr += \
' <a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>\n'
else:
# happening this week button
if thisWeeksEventsCheck(baseDir, nickname, domain):
tlStr += \
' <a href="' + usersPath + \
'/calendar"><button class="buttonevent">' + \
translate['Happening This Week'] + '</button></a>\n'
2020-10-02 19:32:33 +00:00
2020-08-24 09:31:31 +00:00
# the search button
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + \
2020-04-05 09:17:19 +00:00
'/search"><img loading="lazy" src="/' + \
iconsDir + '/search.png" title="' + \
translate['Search and follow'] + '" alt="| ' + \
2020-07-28 10:31:36 +00:00
translate['Search and follow'] + '" class="timelineicon"/></a>\n'
2020-08-28 20:59:54 +00:00
# benchmark 5
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 5 = ' + str(timeDiff))
2020-08-24 09:31:31 +00:00
# the calendar button
calendarAltText = translate['Calendar']
if newCalendarEvent:
# indicate that the calendar icon is highlighted
2020-06-17 11:53:41 +00:00
calendarAltText = '*' + calendarAltText + '*'
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + calendarPath + \
2020-04-05 09:17:19 +00:00
'"><img loading="lazy" src="/' + iconsDir + '/' + \
calendarImage + '" title="' + translate['Calendar'] + \
2020-07-28 10:31:36 +00:00
'" alt="| ' + calendarAltText + '" class="timelineicon"/></a>\n'
2020-08-24 09:31:31 +00:00
# the show/hide button, for a simpler header appearance
2020-04-05 09:17:19 +00:00
tlStr += \
2020-09-30 21:05:32 +00:00
' <a class="imageAnchor" href="' + usersPath + '/minimal' + \
2020-04-05 09:17:19 +00:00
'"><img loading="lazy" src="/' + iconsDir + \
2020-05-23 19:31:06 +00:00
'/showhide.png" title="' + translate['Show/Hide Buttons'] + \
'" alt="| ' + translate['Show/Hide Buttons'] + \
2020-07-28 10:31:36 +00:00
'" class="timelineicon"/></a>\n'
2020-04-05 09:17:19 +00:00
tlStr += followApprovals
2020-09-30 20:07:31 +00:00
# end of the button header with inbox, outbox, etc
tlStr += ' </div>\n'
# second row of buttons for moderator actions
2020-04-05 09:17:19 +00:00
if moderator and boxName == 'moderation':
tlStr += \
'<form method="POST" action="/users/' + \
nickname + '/moderationaction">'
tlStr += '<div class="container">\n'
idx = 'Nickname or URL. Block using *@domain or nickname@domain'
tlStr += \
' <b>' + translate[idx] + '</b><br>\n'
tlStr += ' <input type="text" ' + \
'name="moderationAction" value="" autofocus><br>\n'
tlStr += \
' <input type="submit" title="' + \
translate['Remove the above item'] + \
'" name="submitRemove" value="' + \
2020-07-28 10:31:36 +00:00
translate['Remove'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += \
' <input type="submit" title="' + \
translate['Suspend the above account nickname'] + \
2020-07-28 10:31:36 +00:00
'" name="submitSuspend" value="' + translate['Suspend'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += \
' <input type="submit" title="' + \
translate['Remove a suspension for an account nickname'] + \
'" name="submitUnsuspend" value="' + \
2020-07-28 10:31:36 +00:00
translate['Unsuspend'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += \
' <input type="submit" title="' + \
translate['Block an account on another instance'] + \
2020-07-28 10:31:36 +00:00
'" name="submitBlock" value="' + translate['Block'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += \
' <input type="submit" title="' + \
translate['Unblock an account on another instance'] + \
2020-07-28 10:31:36 +00:00
'" name="submitUnblock" value="' + translate['Unblock'] + '">\n'
2020-04-05 09:17:19 +00:00
tlStr += \
' <input type="submit" title="' + \
translate['Information about current blocks/suspensions'] + \
2020-07-28 10:31:36 +00:00
'" name="submitInfo" value="' + translate['Info'] + '">\n'
tlStr += '</div>\n</form>\n'
2020-04-05 09:17:19 +00:00
2020-08-28 20:59:54 +00:00
# benchmark 6
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 6 = ' + str(timeDiff))
2020-04-05 09:17:19 +00:00
if boxName == 'tlshares':
maxSharesPerAccount = itemsPerPage
return (tlStr +
htmlSharesTimeline(translate, pageNumber, itemsPerPage,
baseDir, actor, nickname, domain, port,
maxSharesPerAccount, httpPrefix) +
htmlFooter())
2019-11-02 14:19:51 +00:00
2020-08-28 20:59:54 +00:00
# benchmark 7
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 7 = ' + str(timeDiff))
# benchmark 8
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 8 = ' + str(timeDiff))
# page up arrow
2020-04-05 09:17:19 +00:00
if pageNumber > 1:
tlStr += \
2020-10-01 13:01:49 +00:00
' <center>\n' + \
2020-10-01 11:00:56 +00:00
' <a href="' + usersPath + '/' + boxName + \
2020-04-05 09:17:19 +00:00
'?page=' + str(pageNumber - 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pageup.png" title="' + \
translate['Page up'] + '" alt="' + \
2020-10-01 11:00:56 +00:00
translate['Page up'] + '"></a>\n' + \
2020-10-01 13:01:49 +00:00
' </center>\n'
# show the posts
2020-04-05 09:17:19 +00:00
itemCtr = 0
2019-09-23 21:05:31 +00:00
if timelineJson:
2020-08-26 10:03:44 +00:00
# if this is the media timeline then add an extra gallery container
2020-04-05 09:17:19 +00:00
if boxName == 'tlmedia':
if pageNumber > 1:
tlStr += '<br>'
tlStr += '<div class="galleryContainer">\n'
2020-08-26 10:03:44 +00:00
# show each post in the timeline
2019-09-23 21:05:31 +00:00
for item in timelineJson['orderedItems']:
2020-08-28 21:15:21 +00:00
timelinePostStartTime = time.time()
2020-04-05 09:17:19 +00:00
if item['type'] == 'Create' or \
item['type'] == 'Announce' or \
item['type'] == 'Update':
2019-11-25 09:37:01 +00:00
# is the actor who sent this post snoozed?
2020-04-05 09:17:19 +00:00
if isPersonSnoozed(baseDir, nickname, domain, item['actor']):
2019-11-25 09:37:01 +00:00
continue
# is the post in the memory cache of recent ones?
2020-04-05 09:17:19 +00:00
currTlStr = None
if boxName != 'tlmedia' and \
recentPostsCache.get('index'):
postId = \
2020-08-23 11:13:35 +00:00
removeIdEnding(item['id']).replace('/', '#')
if postId in recentPostsCache['index']:
if not item.get('muted'):
2020-03-22 21:16:02 +00:00
if recentPostsCache['html'].get(postId):
2020-04-05 09:17:19 +00:00
currTlStr = recentPostsCache['html'][postId]
currTlStr = \
preparePostFromHtmlCache(currTlStr,
boxName,
pageNumber)
2020-08-28 21:15:21 +00:00
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
2020-08-28 21:15:21 +00:00
if timeDiff > 100:
print('TIMELINE POST CACHE TIMING ' +
boxName + ' = ' + str(timeDiff))
if not currTlStr:
2020-08-28 22:10:03 +00:00
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
2020-08-28 22:10:03 +00:00
if timeDiff > 100:
print('TIMELINE POST DISK TIMING START ' +
boxName + ' = ' + str(timeDiff))
2019-11-25 09:37:01 +00:00
# read the post from disk
2020-04-05 09:17:19 +00:00
currTlStr = \
individualPostAsHtml(False, recentPostsCache,
maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, pageNumber,
baseDir, session, wfRequest,
personCache,
nickname, domain, port,
item, None, True,
allowDeletion,
httpPrefix, projectVersion,
boxName,
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
boxName != 'dm',
showIndividualPostIcons,
manuallyApproveFollowers,
False, True)
2020-08-28 21:15:21 +00:00
# benchmark cache post
timeDiff = \
int((time.time() -
timelinePostStartTime) * 1000)
2020-08-28 21:15:21 +00:00
if timeDiff > 100:
print('TIMELINE POST DISK TIMING ' +
boxName + ' = ' + str(timeDiff))
2019-09-30 16:26:59 +00:00
if currTlStr:
2020-04-05 09:17:19 +00:00
itemCtr += 1
tlStr += currTlStr
if boxName == 'tlmedia':
tlStr += '</div>\n'
2020-09-30 20:20:08 +00:00
# end of column-center
2020-10-01 09:07:04 +00:00
tlStr += ' </td>\n'
# right column
2020-10-02 19:32:33 +00:00
rightColumnStr = getRightColumnContent(baseDir, nickname, domainFull,
httpPrefix, translate, iconsDir,
moderator, newswire, positiveVoting)
2020-10-03 11:07:17 +00:00
tlStr += ' <td valign="top" class="col-right">' + \
rightColumnStr + ' </td>\n'
2020-10-03 17:46:50 +00:00
tlStr += ' </tr>\n'
2020-09-30 18:06:34 +00:00
2020-08-28 20:59:54 +00:00
# benchmark 9
2020-08-28 21:05:42 +00:00
timeDiff = int((time.time() - timelineStartTime) * 1000)
2020-08-28 20:59:54 +00:00
if timeDiff > 100:
print('TIMELINE TIMING ' + boxName + ' 9 = ' + str(timeDiff))
# page down arrow
2020-04-05 09:17:19 +00:00
if itemCtr > 2:
tlStr += \
2020-10-01 12:51:25 +00:00
' <tr>\n' + \
' <td class="col-left"></td>\n' + \
' <td class="col-center">\n' + \
2020-10-01 12:57:25 +00:00
' <center>\n' + \
2020-10-01 12:51:25 +00:00
' <a href="' + usersPath + '/' + boxName + '?page=' + \
2020-04-05 09:17:19 +00:00
str(pageNumber + 1) + \
'"><img loading="lazy" class="pageicon" src="/' + \
iconsDir + '/pagedown.png" title="' + \
translate['Page down'] + '" alt="' + \
2020-10-01 11:00:56 +00:00
translate['Page down'] + '"></a>\n' + \
2020-10-01 12:57:25 +00:00
' </center>\n' + \
2020-10-01 12:51:25 +00:00
' </td>\n' + \
' <td class="col-right"></td>\n' + \
' </tr>\n'
tlStr += ' </tbody>\n'
tlStr += '</table>\n'
2020-04-05 09:17:19 +00:00
tlStr += htmlFooter()
2019-07-21 09:09:28 +00:00
return tlStr
2020-04-05 09:17:19 +00:00
def htmlShares(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,
newswire: {}, positiveVoting: bool) -> str:
2019-11-02 14:31:39 +00:00
"""Show the shares timeline as html
"""
2020-04-05 09:17:19 +00:00
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, None,
'tlshares', allowDeletion,
2020-05-23 14:23:56 +00:00
httpPrefix, projectVersion, manuallyApproveFollowers,
False, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlInbox(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, inboxJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-07-20 21:13:36 +00:00
"""Show the inbox as html
"""
2020-04-05 09:17:19 +00:00
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson,
'inbox', allowDeletion,
2020-05-23 14:23:56 +00:00
httpPrefix, projectVersion, manuallyApproveFollowers,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlBookmarks(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, bookmarksJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-11-17 14:01:49 +00:00
"""Show the bookmarks as html
"""
2020-04-05 09:17:19 +00:00
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, bookmarksJson,
'tlbookmarks', allowDeletion,
2020-05-23 14:23:56 +00:00
httpPrefix, projectVersion, manuallyApproveFollowers,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
2020-08-23 11:13:35 +00:00
def htmlEvents(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,
newswire: {}, positiveVoting: bool) -> str:
2020-08-23 11:13:35 +00:00
"""Show the events as html
"""
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, bookmarksJson,
'tlevents', allowDeletion,
httpPrefix, projectVersion, manuallyApproveFollowers,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-08-23 11:13:35 +00:00
2020-04-05 09:17:19 +00:00
def htmlInboxDMs(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, inboxJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-08-25 17:34:09 +00:00
"""Show the DM timeline as html
"""
2020-04-05 09:17:19 +00:00
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'dm', allowDeletion,
httpPrefix, projectVersion, False, minimal,
YTReplacementDomain, newswire, False, positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlInboxReplies(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, inboxJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-09-23 20:09:11 +00:00
"""Show the replies timeline as html
"""
2020-04-05 09:17:19 +00:00
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'tlreplies',
2020-05-23 14:23:56 +00:00
allowDeletion, httpPrefix, projectVersion, False,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlInboxMedia(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, inboxJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-09-28 11:29:42 +00:00
"""Show the media timeline as html
"""
2020-04-05 09:17:19 +00:00
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'tlmedia',
2020-05-23 14:23:56 +00:00
allowDeletion, httpPrefix, projectVersion, False,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlInboxBlogs(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, inboxJson: {},
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2020-02-24 14:39:25 +00:00
"""Show the blogs timeline as html
"""
2020-04-05 09:17:19 +00:00
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'tlblogs',
2020-05-23 14:23:56 +00:00
allowDeletion, httpPrefix, projectVersion, False,
minimal, YTReplacementDomain, newswire, False,
positiveVoting)
2020-04-05 09:17:19 +00:00
2020-10-07 19:04:15 +00:00
def htmlInboxNews(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,
newswire: {}, moderator: bool,
positiveVoting: bool) -> str:
2020-10-07 19:04:15 +00:00
"""Show the news timeline as html
"""
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'tlnews',
allowDeletion, httpPrefix, projectVersion, False,
minimal, YTReplacementDomain, newswire, moderator,
positiveVoting)
2020-10-07 19:04:15 +00:00
2020-04-05 09:17:19 +00:00
def htmlModeration(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,
newswire: {}, positiveVoting: bool) -> str:
2019-08-12 13:22:17 +00:00
"""Show the moderation feed as html
"""
2020-04-05 09:17:19 +00:00
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, inboxJson, 'moderation',
allowDeletion, httpPrefix, projectVersion, True, False,
YTReplacementDomain, newswire, False, positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlOutbox(defaultTimeline: str,
recentPostsCache: {}, maxRecentPosts: int,
translate: {}, pageNumber: int, itemsPerPage: int,
session, baseDir: str, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, outboxJson: {},
2019-08-14 20:12:27 +00:00
allowDeletion: bool,
2020-05-23 14:23:56 +00:00
httpPrefix: str, projectVersion: str,
minimal: bool, YTReplacementDomain: str,
newswire: {}, positiveVoting: bool) -> str:
2019-07-20 21:13:36 +00:00
"""Show the Outbox as html
"""
2020-04-05 09:17:19 +00:00
manuallyApproveFollowers = \
followerApprovalActive(baseDir, nickname, domain)
return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts,
translate, pageNumber,
itemsPerPage, session, baseDir, wfRequest, personCache,
nickname, domain, port, outboxJson, 'outbox',
allowDeletion, httpPrefix, projectVersion,
manuallyApproveFollowers, minimal,
YTReplacementDomain, newswire, False, positiveVoting)
2020-04-05 09:17:19 +00:00
def htmlIndividualPost(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) -> str:
2019-07-20 21:13:36 +00:00
"""Show an individual post as html
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
2020-07-13 19:42:30 +00:00
postStr = ''
if likedBy:
likedByNickname = getNicknameFromActor(likedBy)
likedByDomain, likedByPort = getDomainFromActor(likedBy)
if likedByPort:
if likedByPort != 80 and likedByPort != 443:
likedByDomain += ':' + str(likedByPort)
2020-07-14 09:09:33 +00:00
likedByHandle = likedByNickname + '@' + likedByDomain
2020-07-13 19:42:30 +00:00
postStr += \
'<p>' + translate['Liked by'] + \
' <a href="' + likedBy + '">@' + \
2020-07-28 10:31:36 +00:00
likedByHandle + '</a>\n'
2020-07-14 09:09:33 +00:00
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
actor = '/users/' + nickname
followStr = ' <form method="POST" ' + \
2020-07-28 10:31:36 +00:00
'accept-charset="UTF-8" action="' + actor + '/searchhandle">\n'
2020-07-14 09:09:33 +00:00
followStr += \
2020-07-28 10:31:36 +00:00
' <input type="hidden" name="actor" value="' + actor + '">\n'
2020-07-14 09:09:33 +00:00
followStr += \
' <input type="hidden" name="searchtext" value="' + \
2020-07-28 10:31:36 +00:00
likedByHandle + '">\n'
2020-07-14 09:09:33 +00:00
if not isFollowingActor(baseDir, nickname, domainFull, likedBy):
followStr += ' <button type="submit" class="button" ' + \
2020-07-28 10:31:36 +00:00
'name="submitSearch">' + translate['Follow'] + '</button>\n'
2020-07-14 09:09:33 +00:00
followStr += ' <button type="submit" class="button" ' + \
2020-07-28 10:31:36 +00:00
'name="submitBack">' + translate['Go Back'] + '</button>\n'
followStr += ' </form>\n'
2020-07-14 09:09:33 +00:00
postStr += followStr + '</p>\n'
2020-07-13 19:42:30 +00:00
postStr += \
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None,
baseDir, session, wfRequest, personCache,
nickname, domain, port, postJsonObject,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, authorized, False, False, False)
2020-08-23 11:13:35 +00:00
messageId = removeIdEnding(postJsonObject['id'])
2019-08-02 19:47:30 +00:00
# show the previous posts
2020-02-05 18:50:53 +00:00
if isinstance(postJsonObject['object'], dict):
while postJsonObject['object'].get('inReplyTo'):
2020-04-05 09:17:19 +00:00
postFilename = \
locatePost(baseDir, nickname, domain,
2020-02-23 14:24:11 +00:00
postJsonObject['object']['inReplyTo'])
2020-02-05 18:50:53 +00:00
if not postFilename:
break
2020-04-05 09:17:19 +00:00
postJsonObject = loadJson(postFilename)
2020-02-05 18:50:53 +00:00
if postJsonObject:
2020-04-05 09:17:19 +00:00
postStr = \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None,
baseDir, session, wfRequest,
personCache,
nickname, domain, port,
postJsonObject,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, authorized,
False, False, False) + postStr
2019-08-02 19:47:30 +00:00
# show the following posts
2020-04-05 09:17:19 +00:00
postFilename = locatePost(baseDir, nickname, domain, messageId)
if postFilename:
# is there a replies file for this post?
2020-04-05 09:17:19 +00:00
repliesFilename = postFilename.replace('.json', '.replies')
if os.path.isfile(repliesFilename):
# get items from the replies file
2020-04-05 09:17:19 +00:00
repliesJson = {
'orderedItems': []
}
populateRepliesJson(baseDir, nickname, domain,
repliesFilename, authorized, repliesJson)
# add items to the html output
for item in repliesJson['orderedItems']:
2020-04-05 09:17:19 +00:00
postStr += \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None,
baseDir, session, wfRequest,
personCache,
nickname, domain, port, item,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, authorized,
False, False, False)
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-18 10:01:31 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
postsCSS = cssFile.read()
if httpPrefix != 'https':
postsCSS = postsCSS.replace('https://',
httpPrefix + '://')
return htmlHeader(cssFilename, postsCSS) + postStr + htmlFooter()
def htmlPostReplies(recentPostsCache: {}, maxRecentPosts: int,
translate: {}, baseDir: str,
session, wfRequest: {}, personCache: {},
nickname: str, domain: str, port: int, repliesJson: {},
httpPrefix: str, projectVersion: str,
YTReplacementDomain: str) -> str:
2019-07-20 21:13:36 +00:00
"""Show the replies to an individual post as html
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
repliesStr = ''
2019-08-02 16:49:42 +00:00
if repliesJson.get('orderedItems'):
for item in repliesJson['orderedItems']:
2020-04-05 09:17:19 +00:00
repliesStr += \
individualPostAsHtml(True, recentPostsCache,
maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None,
baseDir, session, wfRequest, personCache,
nickname, domain, port, item,
None, True, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, False, False, False, False)
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-18 10:01:31 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
postsCSS = cssFile.read()
if httpPrefix != 'https':
postsCSS = postsCSS.replace('https://',
httpPrefix + '://')
return htmlHeader(cssFilename, postsCSS) + repliesStr + htmlFooter()
2019-07-29 09:49:46 +00:00
2020-04-05 09:17:19 +00:00
def htmlRemoveSharedItem(translate: {}, baseDir: str,
2020-07-11 20:31:25 +00:00
actor: str, shareName: str,
callingDomain: str) -> str:
"""Shows a screen asking to confirm the removal of a shared item
"""
2020-04-05 09:17:19 +00:00
itemID = getValidSharedItemID(shareName)
nickname = getNicknameFromActor(actor)
domain, port = getDomainFromActor(actor)
2020-07-11 20:31:25 +00:00
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2020-04-05 09:17:19 +00:00
sharesFile = baseDir + '/accounts/' + \
nickname + '@' + domain + '/shares.json'
if not os.path.isfile(sharesFile):
2020-04-05 09:17:19 +00:00
print('ERROR: no shares file ' + sharesFile)
return None
2020-04-05 09:17:19 +00:00
sharesJson = loadJson(sharesFile)
if not sharesJson:
2019-11-03 09:36:04 +00:00
print('ERROR: unable to load shares.json')
return None
2019-11-03 09:48:01 +00:00
if not sharesJson.get(itemID):
2020-04-05 09:17:19 +00:00
print('ERROR: share named "' + itemID + '" is not in ' + sharesFile)
return None
2020-04-05 09:17:19 +00:00
sharedItemDisplayName = sharesJson[itemID]['displayName']
sharedItemImageUrl = None
2019-11-03 09:48:01 +00:00
if sharesJson[itemID].get('imageUrl'):
2020-04-05 09:17:19 +00:00
sharedItemImageUrl = sharesJson[itemID]['imageUrl']
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/shares-background.png'):
if not os.path.isfile(baseDir + '/accounts/shares-background.png'):
copyfile(baseDir + '/img/shares-background.png',
baseDir + '/accounts/shares-background.png')
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-follow.css'
if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
sharesStr = htmlHeader(cssFilename, profileStyle)
2020-07-28 10:31:36 +00:00
sharesStr += '<div class="follow">\n'
sharesStr += ' <div class="followAvatar">\n'
sharesStr += ' <center>\n'
if sharedItemImageUrl:
2020-04-05 09:17:19 +00:00
sharesStr += ' <img loading="lazy" src="' + \
2020-07-28 10:31:36 +00:00
sharedItemImageUrl + '"/>\n'
2020-04-05 09:17:19 +00:00
sharesStr += \
' <p class="followText">' + translate['Remove'] + \
2020-07-28 10:31:36 +00:00
' ' + sharedItemDisplayName + ' ?</p>\n'
2020-07-11 20:44:20 +00:00
postActor = getAltPath(actor, domainFull, callingDomain)
2020-07-28 10:31:36 +00:00
sharesStr += ' <form method="POST" action="' + postActor + '/rmshare">\n'
sharesStr += \
' <input type="hidden" name="actor" value="' + actor + '">\n'
2020-04-05 09:17:19 +00:00
sharesStr += ' <input type="hidden" name="shareName" value="' + \
2020-07-28 10:31:36 +00:00
shareName + '">\n'
2020-04-05 09:17:19 +00:00
sharesStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
sharesStr += \
' <a href="' + actor + '/inbox' + '"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
sharesStr += ' </form>\n'
sharesStr += ' </center>\n'
sharesStr += ' </div>\n'
sharesStr += '</div>\n'
2020-04-05 09:17:19 +00:00
sharesStr += htmlFooter()
return sharesStr
2019-08-27 12:47:11 +00:00
2020-04-05 09:17:19 +00:00
def htmlDeletePost(recentPostsCache: {}, maxRecentPosts: int,
translate, pageNumber: int,
session, baseDir: str, messageId: str,
httpPrefix: str, projectVersion: str,
wfRequest: {}, personCache: {},
callingDomain: str,
YTReplacementDomain: str) -> str:
2019-08-27 12:47:11 +00:00
"""Shows a screen asking to confirm the deletion of a post
"""
if '/statuses/' not in messageId:
return None
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
actor = messageId.split('/statuses/')[0]
nickname = getNicknameFromActor(actor)
domain, port = getDomainFromActor(actor)
domainFull = domain
if port:
if port != 80 and port != 443:
domainFull = domain + ':' + str(port)
2019-08-27 12:47:11 +00:00
2020-04-05 09:17:19 +00:00
postFilename = locatePost(baseDir, nickname, domain, messageId)
2019-08-27 12:47:11 +00:00
if not postFilename:
return None
2020-04-05 09:17:19 +00:00
postJsonObject = loadJson(postFilename)
if not postJsonObject:
return None
2019-08-27 12:47:11 +00:00
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/delete-background.png'):
if not os.path.isfile(baseDir + '/accounts/delete-background.png'):
copyfile(baseDir + '/img/delete-background.png',
baseDir + '/accounts/delete-background.png')
2019-08-27 12:47:11 +00:00
2020-04-05 09:17:19 +00:00
deletePostStr = None
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
if httpPrefix != 'https':
profileStyle = profileStyle.replace('https://',
httpPrefix + '://')
deletePostStr = htmlHeader(cssFilename, profileStyle)
deletePostStr += \
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, pageNumber,
baseDir, session, wfRequest, personCache,
nickname, domain, port, postJsonObject,
None, True, False,
httpPrefix, projectVersion, 'outbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, False, False, False, False)
deletePostStr += '<center>'
deletePostStr += \
' <p class="followText">' + \
translate['Delete this post?'] + '</p>'
2020-07-11 20:44:20 +00:00
postActor = getAltPath(actor, domainFull, callingDomain)
deletePostStr += \
2020-07-28 10:31:36 +00:00
' <form method="POST" action="' + postActor + '/rmpost">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <input type="hidden" name="pageNumber" value="' + \
2020-07-28 10:31:36 +00:00
str(pageNumber) + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <input type="hidden" name="messageId" value="' + \
2020-07-28 10:31:36 +00:00
messageId + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <a href="' + actor + '/inbox"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
deletePostStr += ' </form>\n'
deletePostStr += '</center>\n'
2020-04-05 09:17:19 +00:00
deletePostStr += htmlFooter()
2019-08-27 12:47:11 +00:00
return deletePostStr
2020-04-05 09:17:19 +00:00
def htmlCalendarDeleteConfirm(translate: {}, baseDir: str,
path: str, httpPrefix: str,
domainFull: str, postId: str, postTime: str,
year: int, monthNumber: int,
2020-07-11 20:17:55 +00:00
dayNumber: int, callingDomain: str) -> str:
2020-02-23 12:20:54 +00:00
"""Shows a screen asking to confirm the deletion of a calendar event
"""
2020-04-05 09:17:19 +00:00
nickname = getNicknameFromActor(path)
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
domain, port = getDomainFromActor(actor)
messageId = actor + '/statuses/' + postId
2020-02-23 12:20:54 +00:00
2020-04-05 09:17:19 +00:00
postFilename = locatePost(baseDir, nickname, domain, messageId)
2020-02-23 12:20:54 +00:00
if not postFilename:
return None
2020-04-05 09:17:19 +00:00
postJsonObject = loadJson(postFilename)
2020-02-23 12:20:54 +00:00
if not postJsonObject:
return None
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/delete-background.png'):
if not os.path.isfile(baseDir + '/accounts/delete-background.png'):
copyfile(baseDir + '/img/delete-background.png',
baseDir + '/accounts/delete-background.png')
2020-02-23 12:20:54 +00:00
2020-04-05 09:17:19 +00:00
deletePostStr = None
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2020-02-23 12:20:54 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
if httpPrefix != 'https':
profileStyle = profileStyle.replace('https://',
httpPrefix + '://')
deletePostStr = htmlHeader(cssFilename, profileStyle)
deletePostStr += \
'<center><h1>' + postTime + ' ' + str(year) + '/' + \
str(monthNumber) + \
'/' + str(dayNumber) + '</h1></center>'
deletePostStr += '<center>'
deletePostStr += ' <p class="followText">' + \
translate['Delete this event'] + '</p>'
2020-07-11 20:17:55 +00:00
2020-07-11 20:44:20 +00:00
postActor = getAltPath(actor, domainFull, callingDomain)
2020-07-11 20:17:55 +00:00
deletePostStr += \
2020-07-28 10:31:36 +00:00
' <form method="POST" action="' + postActor + '/rmpost">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += ' <input type="hidden" name="year" value="' + \
2020-07-28 10:31:36 +00:00
str(year) + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += ' <input type="hidden" name="month" value="' + \
2020-07-28 10:31:36 +00:00
str(monthNumber) + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += ' <input type="hidden" name="day" value="' + \
2020-07-28 10:31:36 +00:00
str(dayNumber) + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
2020-07-28 10:31:36 +00:00
' <input type="hidden" name="pageNumber" value="1">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <input type="hidden" name="messageId" value="' + \
2020-07-28 10:31:36 +00:00
messageId + '">\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
deletePostStr += \
' <a href="' + actor + '/calendar?year=' + \
str(year) + '?month=' + \
str(monthNumber) + '"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
deletePostStr += ' </form>\n'
deletePostStr += '</center>\n'
2020-04-05 09:17:19 +00:00
deletePostStr += htmlFooter()
2020-02-23 12:20:54 +00:00
return deletePostStr
2020-04-05 09:17:19 +00:00
def htmlFollowConfirm(translate: {}, baseDir: str,
originPathStr: str,
followActor: str,
2019-09-07 08:57:52 +00:00
followProfileUrl: str) -> str:
2019-07-29 09:49:46 +00:00
"""Asks to confirm a follow
"""
2020-04-05 09:17:19 +00:00
followDomain, port = getDomainFromActor(followActor)
2020-03-22 21:16:02 +00:00
2020-07-25 19:07:06 +00:00
if os.path.isfile(baseDir + '/accounts/follow-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/follow-background.jpg'):
copyfile(baseDir + '/accounts/follow-background-custom.jpg',
baseDir + '/accounts/follow-background.jpg')
2019-07-29 09:49:46 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-follow.css'
if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
followStr = htmlHeader(cssFilename, profileStyle)
2020-07-28 10:31:36 +00:00
followStr += '<div class="follow">\n'
followStr += ' <div class="followAvatar">\n'
followStr += ' <center>\n'
followStr += ' <a href="' + followActor + '">\n'
followStr += ' <img loading="lazy" src="' + followProfileUrl + '"/></a>\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <p class="followText">' + translate['Follow'] + ' ' + \
2020-07-28 10:31:36 +00:00
getNicknameFromActor(followActor) + '@' + followDomain + ' ?</p>\n'
2020-04-05 09:17:19 +00:00
followStr += ' <form method="POST" action="' + \
2020-07-28 10:31:36 +00:00
originPathStr + '/followconfirm">\n'
2020-04-05 09:17:19 +00:00
followStr += ' <input type="hidden" name="actor" value="' + \
2020-07-28 10:31:36 +00:00
followActor + '">\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <a href="' + originPathStr + '"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
followStr += ' </form>\n'
followStr += '</center>\n'
followStr += '</div>\n'
followStr += '</div>\n'
2020-04-05 09:17:19 +00:00
followStr += htmlFooter()
2019-07-29 09:49:46 +00:00
return followStr
2019-07-29 20:36:26 +00:00
2020-04-05 09:17:19 +00:00
def htmlUnfollowConfirm(translate: {}, baseDir: str,
originPathStr: str,
followActor: str,
2019-09-07 08:57:52 +00:00
followProfileUrl: str) -> str:
2019-07-29 20:36:26 +00:00
"""Asks to confirm unfollowing an actor
"""
2020-04-05 09:17:19 +00:00
followDomain, port = getDomainFromActor(followActor)
2020-03-22 21:16:02 +00:00
2020-07-25 19:07:06 +00:00
if os.path.isfile(baseDir + '/accounts/follow-background-custom.jpg'):
if not os.path.isfile(baseDir + '/accounts/follow-background.jpg'):
copyfile(baseDir + '/accounts/follow-background-custom.jpg',
baseDir + '/accounts/follow-background.jpg')
2019-07-29 20:36:26 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-follow.css'
if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
followStr = htmlHeader(cssFilename, profileStyle)
2020-07-28 10:31:36 +00:00
followStr += '<div class="follow">\n'
followStr += ' <div class="followAvatar">\n'
followStr += ' <center>\n'
followStr += ' <a href="' + followActor + '">\n'
followStr += ' <img loading="lazy" src="' + followProfileUrl + '"/></a>\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <p class="followText">' + translate['Stop following'] + \
2020-07-28 10:31:36 +00:00
' ' + getNicknameFromActor(followActor) + \
'@' + followDomain + ' ?</p>\n'
2020-04-05 09:17:19 +00:00
followStr += ' <form method="POST" action="' + \
2020-07-28 10:31:36 +00:00
originPathStr + '/unfollowconfirm">\n'
2020-04-05 09:17:19 +00:00
followStr += ' <input type="hidden" name="actor" value="' + \
2020-07-28 10:31:36 +00:00
followActor + '">\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
followStr += \
' <a href="' + originPathStr + '"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
followStr += ' </form>\n'
followStr += '</center>\n'
followStr += '</div>\n'
followStr += '</div>\n'
2020-04-05 09:17:19 +00:00
followStr += htmlFooter()
2019-07-29 20:36:26 +00:00
return followStr
2019-07-30 22:34:04 +00:00
2020-04-05 09:17:19 +00:00
def htmlPersonOptions(translate: {}, baseDir: str,
domain: str, originPathStr: str,
optionsActor: str,
optionsProfileUrl: str,
optionsLink: str,
pageNumber: int,
donateUrl: str,
xmppAddress: str,
matrixAddress: str,
ssbAddress: str,
2020-05-04 11:28:43 +00:00
blogAddress: str,
2020-04-05 09:17:19 +00:00
toxAddress: str,
PGPpubKey: str,
2020-07-06 10:14:41 +00:00
PGPfingerprint: str,
emailAddress) -> str:
2019-08-24 21:10:20 +00:00
"""Show options for a person: view/follow/block/report
"""
2020-04-05 09:17:19 +00:00
optionsDomain, optionsPort = getDomainFromActor(optionsActor)
2020-06-29 16:25:28 +00:00
optionsDomainFull = optionsDomain
if optionsPort:
if optionsPort != 80 and optionsPort != 443:
optionsDomainFull = optionsDomain + ':' + str(optionsPort)
2020-03-22 21:16:02 +00:00
2020-07-25 22:12:59 +00:00
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')
2019-08-24 21:10:20 +00:00
2020-04-05 09:17:19 +00:00
followStr = 'Follow'
blockStr = 'Block'
nickname = None
2020-06-29 16:25:28 +00:00
optionsNickname = None
2019-08-24 23:00:03 +00:00
if originPathStr.startswith('/users/'):
2020-04-05 09:17:19 +00:00
nickname = originPathStr.split('/users/')[1]
2019-08-24 23:00:03 +00:00
if '/' in nickname:
2020-04-05 09:17:19 +00:00
nickname = nickname.split('/')[0]
2019-08-24 23:00:03 +00:00
if '?' in nickname:
2020-04-05 09:17:19 +00:00
nickname = nickname.split('?')[0]
followerDomain, followerPort = getDomainFromActor(optionsActor)
if isFollowingActor(baseDir, nickname, domain, optionsActor):
followStr = 'Unfollow'
2019-08-24 23:00:03 +00:00
2020-04-05 09:17:19 +00:00
optionsNickname = getNicknameFromActor(optionsActor)
optionsDomainFull = optionsDomain
2019-08-24 23:00:03 +00:00
if optionsPort:
2020-04-05 09:17:19 +00:00
if optionsPort != 80 and optionsPort != 443:
optionsDomainFull = optionsDomain + ':' + str(optionsPort)
if isBlocked(baseDir, nickname, domain,
optionsNickname, optionsDomainFull):
blockStr = 'Block'
2019-08-24 23:00:03 +00:00
2020-04-05 09:17:19 +00:00
optionsLinkStr = ''
2019-08-24 23:00:03 +00:00
if optionsLink:
2020-04-05 09:17:19 +00:00
optionsLinkStr = \
' <input type="hidden" name="postUrl" value="' + \
2020-09-19 09:44:22 +00:00
optionsLink + '">\n'
2020-07-25 22:12:59 +00:00
cssFilename = baseDir + '/epicyon-options.css'
if os.path.isfile(baseDir + '/options.css'):
cssFilename = baseDir + '/options.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
profileStyle = \
profileStyle.replace('--follow-text-entry-width: 90%;',
'--follow-text-entry-width: 20%;')
2019-11-06 11:39:41 +00:00
# To snooze, or not to snooze? That is the question
2020-04-05 09:17:19 +00:00
snoozeButtonStr = 'Snooze'
2019-11-06 11:39:41 +00:00
if nickname:
2020-04-05 09:17:19 +00:00
if isPersonSnoozed(baseDir, nickname, domain, optionsActor):
snoozeButtonStr = 'Unsnooze'
2019-11-06 11:39:41 +00:00
2020-04-05 09:17:19 +00:00
donateStr = ''
2019-11-06 23:20:00 +00:00
if donateUrl:
2020-04-05 09:17:19 +00:00
donateStr = \
' <a href="' + donateUrl + \
'"><button class="button" name="submitDonate">' + \
2020-07-28 10:31:36 +00:00
translate['Donate'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
optionsStr = htmlHeader(cssFilename, profileStyle)
optionsStr += '<br><br>\n'
2020-07-26 08:46:22 +00:00
optionsStr += '<div class="options">\n'
optionsStr += ' <div class="optionsAvatar">\n'
optionsStr += ' <center>\n'
optionsStr += ' <a href="' + optionsActor + '">\n'
2020-07-26 08:48:13 +00:00
optionsStr += ' <img loading="lazy" src="' + optionsProfileUrl + \
'"/></a>\n'
2020-08-05 21:12:09 +00:00
handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain
2020-04-05 09:17:19 +00:00
optionsStr += \
' <p class="optionsText">' + translate['Options for'] + \
2020-08-05 21:12:09 +00:00
' @' + handle + '</p>\n'
if emailAddress:
2020-04-05 09:17:19 +00:00
optionsStr += \
'<p class="imText">' + translate['Email'] + \
': <a href="mailto:' + \
2020-07-26 08:46:22 +00:00
emailAddress + '">' + emailAddress + '</a></p>\n'
2019-12-17 14:57:16 +00:00
if xmppAddress:
2020-04-05 09:17:19 +00:00
optionsStr += \
'<p class="imText">' + translate['XMPP'] + \
2020-07-26 08:48:13 +00:00
': <a href="xmpp:' + xmppAddress + '">' + \
xmppAddress + '</a></p>\n'
2019-12-17 15:25:34 +00:00
if matrixAddress:
2020-04-05 09:17:19 +00:00
optionsStr += \
'<p class="imText">' + translate['Matrix'] + ': ' + \
2020-07-26 08:46:22 +00:00
matrixAddress + '</p>\n'
2020-02-26 14:35:17 +00:00
if ssbAddress:
2020-04-05 09:17:19 +00:00
optionsStr += \
2020-07-26 08:46:22 +00:00
'<p class="imText">SSB: ' + ssbAddress + '</p>\n'
2020-05-04 11:28:43 +00:00
if blogAddress:
optionsStr += \
'<p class="imText">Blog: <a href="' + blogAddress + '">' + \
2020-07-26 08:46:22 +00:00
blogAddress + '</a></p>\n'
2020-03-22 14:42:26 +00:00
if toxAddress:
2020-04-05 09:17:19 +00:00
optionsStr += \
2020-07-26 08:46:22 +00:00
'<p class="imText">Tox: ' + toxAddress + '</p>\n'
2020-07-06 10:14:41 +00:00
if PGPfingerprint:
optionsStr += '<p class="pgp">PGP: ' + \
2020-07-26 08:46:22 +00:00
PGPfingerprint.replace('\n', '<br>') + '</p>\n'
if PGPpubKey:
2020-04-05 09:17:19 +00:00
optionsStr += '<p class="pgp">' + \
2020-07-26 08:46:22 +00:00
PGPpubKey.replace('\n', '<br>') + '</p>\n'
2020-04-05 09:17:19 +00:00
optionsStr += ' <form method="POST" action="' + \
2020-07-26 08:46:22 +00:00
originPathStr + '/personoptions">\n'
2020-04-05 09:17:19 +00:00
optionsStr += ' <input type="hidden" name="pageNumber" value="' + \
2020-07-26 08:46:22 +00:00
str(pageNumber) + '">\n'
2020-04-05 09:17:19 +00:00
optionsStr += ' <input type="hidden" name="actor" value="' + \
2020-07-26 08:46:22 +00:00
optionsActor + '">\n'
2020-04-05 09:17:19 +00:00
optionsStr += ' <input type="hidden" name="avatarUrl" value="' + \
2020-07-26 08:46:22 +00:00
optionsProfileUrl + '">\n'
2020-06-29 16:25:28 +00:00
if optionsNickname:
handle = optionsNickname + '@' + optionsDomainFull
petname = getPetName(baseDir, nickname, domain, handle)
optionsStr += \
2020-09-19 09:54:36 +00:00
' ' + translate['Petname'] + ': \n' + \
2020-09-19 09:51:04 +00:00
' <input type="text" name="optionpetname" value="' + \
2020-07-26 08:46:22 +00:00
petname + '">\n' \
2020-09-19 09:51:04 +00:00
' <button type="submit" class="buttonsmall" ' + \
2020-08-12 10:14:20 +00:00
'name="submitPetname">' + \
2020-07-26 08:46:22 +00:00
translate['Submit'] + '</button><br>\n'
2020-07-03 19:25:53 +00:00
if isFollowingActor(baseDir, nickname, domain, optionsActor):
if receivingCalendarEvents(baseDir, nickname, domain,
optionsNickname, optionsDomainFull):
optionsStr += \
2020-09-19 09:51:04 +00:00
' <input type="checkbox" ' + \
2020-07-03 19:55:58 +00:00
'class="profilecheckbox" name="onCalendar" checked> ' + \
2020-07-03 19:25:53 +00:00
translate['Receive calendar events from this account'] + \
2020-09-19 09:51:04 +00:00
'\n <button type="submit" class="buttonsmall" ' + \
2020-07-03 19:25:53 +00:00
'name="submitOnCalendar">' + \
2020-07-03 19:55:58 +00:00
translate['Submit'] + '</button><br>\n'
2020-07-03 19:25:53 +00:00
else:
optionsStr += \
2020-09-19 09:51:04 +00:00
' <input type="checkbox" ' + \
2020-07-03 19:55:58 +00:00
'class="profilecheckbox" name="onCalendar"> ' + \
2020-07-03 19:25:53 +00:00
translate['Receive calendar events from this account'] + \
2020-09-19 09:51:04 +00:00
'\n <button type="submit" class="buttonsmall" ' + \
2020-07-03 19:25:53 +00:00
'name="submitOnCalendar">' + \
2020-07-03 19:55:58 +00:00
translate['Submit'] + '</button><br>\n'
2020-07-03 19:25:53 +00:00
2020-04-05 09:17:19 +00:00
optionsStr += optionsLinkStr
2020-08-12 10:34:18 +00:00
optionsStr += \
2020-09-19 09:59:07 +00:00
' <a href="/"><button type="button" class="buttonIcon" ' + \
2020-08-12 10:34:18 +00:00
'name="submitBack">' + translate['Go Back'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
optionsStr += \
' <button type="submit" class="button" name="submitView">' + \
2020-07-26 08:46:22 +00:00
translate['View'] + '</button>\n'
2020-04-05 09:17:19 +00:00
optionsStr += donateStr
optionsStr += \
' <button type="submit" class="button" name="submit' + \
2020-07-26 08:46:22 +00:00
followStr + '">' + translate[followStr] + '</button>\n'
2020-04-05 09:17:19 +00:00
optionsStr += \
' <button type="submit" class="button" name="submit' + \
2020-07-26 08:46:22 +00:00
blockStr + '">' + translate[blockStr] + '</button>\n'
2020-04-05 09:17:19 +00:00
optionsStr += \
' <button type="submit" class="button" name="submitDM">' + \
2020-07-26 08:46:22 +00:00
translate['DM'] + '</button>\n'
2020-04-05 09:17:19 +00:00
optionsStr += \
' <button type="submit" class="button" name="submit' + \
2020-07-26 08:46:22 +00:00
snoozeButtonStr + '">' + translate[snoozeButtonStr] + '</button>\n'
2020-04-05 09:17:19 +00:00
optionsStr += \
' <button type="submit" class="button" name="submitReport">' + \
2020-07-26 08:46:22 +00:00
translate['Report'] + '</button>\n'
2020-07-03 19:11:05 +00:00
2020-08-05 21:12:09 +00:00
personNotes = ''
personNotesFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + \
'/notes/' + handle + '.txt'
if os.path.isfile(personNotesFilename):
with open(personNotesFilename, 'r') as fp:
personNotes = fp.read()
optionsStr += \
' <br><br>' + translate['Notes'] + ': \n'
2020-09-19 09:51:04 +00:00
optionsStr += ' <button type="submit" class="buttonsmall" ' + \
2020-08-05 21:12:09 +00:00
'name="submitPersonNotes">' + \
translate['Submit'] + '</button><br>\n'
optionsStr += \
' <textarea id="message" ' + \
2020-08-05 21:49:03 +00:00
'name="optionnotes" style="height:400px">' + \
2020-08-05 21:12:09 +00:00
personNotes + '</textarea>\n'
2020-07-26 08:46:22 +00:00
optionsStr += ' </form>\n'
optionsStr += '</center>\n'
optionsStr += '</div>\n'
optionsStr += '</div>\n'
2020-04-05 09:17:19 +00:00
optionsStr += htmlFooter()
2019-08-24 21:10:20 +00:00
return optionsStr
2020-04-05 09:17:19 +00:00
def htmlUnblockConfirm(translate: {}, baseDir: str,
originPathStr: str,
blockActor: str,
2019-09-07 08:57:52 +00:00
blockProfileUrl: str) -> str:
"""Asks to confirm unblocking an actor
"""
2020-04-05 09:17:19 +00:00
blockDomain, port = getDomainFromActor(blockActor)
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/block-background.png'):
if not os.path.isfile(baseDir + '/accounts/block-background.png'):
copyfile(baseDir + '/img/block-background.png',
baseDir + '/accounts/block-background.png')
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-follow.css'
if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
blockStr = htmlHeader(cssFilename, profileStyle)
2020-07-28 10:31:36 +00:00
blockStr += '<div class="block">\n'
blockStr += ' <div class="blockAvatar">\n'
blockStr += ' <center>\n'
blockStr += ' <a href="' + blockActor + '">\n'
blockStr += ' <img loading="lazy" src="' + blockProfileUrl + '"/></a>\n'
2020-04-05 09:17:19 +00:00
blockStr += \
' <p class="blockText">' + translate['Stop blocking'] + ' ' + \
2020-07-28 10:31:36 +00:00
getNicknameFromActor(blockActor) + '@' + blockDomain + ' ?</p>\n'
2020-04-05 09:17:19 +00:00
blockStr += ' <form method="POST" action="' + \
2020-07-28 10:31:36 +00:00
originPathStr + '/unblockconfirm">\n'
2020-04-05 09:17:19 +00:00
blockStr += ' <input type="hidden" name="actor" value="' + \
2020-07-28 10:31:36 +00:00
blockActor + '">\n'
2020-04-05 09:17:19 +00:00
blockStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Yes'] + '</button>\n'
2020-04-05 09:17:19 +00:00
blockStr += \
' <a href="' + originPathStr + '"><button class="button">' + \
2020-07-28 10:31:36 +00:00
translate['No'] + '</button></a>\n'
blockStr += ' </form>\n'
blockStr += '</center>\n'
blockStr += '</div>\n'
blockStr += '</div>\n'
2020-04-05 09:17:19 +00:00
blockStr += htmlFooter()
return blockStr
2020-04-05 09:17:19 +00:00
def htmlSearchEmojiTextEntry(translate: {},
baseDir: str, path: str) -> str:
2019-08-19 20:01:29 +00:00
"""Search for an emoji by name
"""
2019-11-03 14:46:30 +00:00
# emoji.json is generated so that it can be customized and the changes
2020-03-22 21:16:02 +00:00
# will be retained even if default_emoji.json is subsequently updated
2020-04-05 09:17:19 +00:00
if not os.path.isfile(baseDir + '/emoji/emoji.json'):
copyfile(baseDir + '/emoji/default_emoji.json',
baseDir + '/emoji/emoji.json')
actor = path.replace('/search', '')
domain, port = getDomainFromActor(actor)
if os.path.isfile(baseDir + '/img/search-background.png'):
if not os.path.isfile(baseDir + '/accounts/search-background.png'):
copyfile(baseDir + '/img/search-background.png',
baseDir + '/accounts/search-background.png')
cssFilename = baseDir + '/epicyon-follow.css'
if os.path.isfile(baseDir + '/follow.css'):
cssFilename = baseDir + '/follow.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
emojiStr = htmlHeader(cssFilename, profileStyle)
2020-07-28 10:31:36 +00:00
emojiStr += '<div class="follow">\n'
emojiStr += ' <div class="followAvatar">\n'
emojiStr += ' <center>\n'
2020-04-05 09:17:19 +00:00
emojiStr += \
' <p class="followText">' + \
2020-07-28 10:31:36 +00:00
translate['Enter an emoji name to search for'] + '</p>\n'
2020-04-05 09:17:19 +00:00
emojiStr += ' <form method="POST" action="' + \
2020-07-28 10:31:36 +00:00
actor + '/searchhandleemoji">\n'
2020-04-05 09:17:19 +00:00
emojiStr += ' <input type="hidden" name="actor" value="' + \
2020-07-28 10:31:36 +00:00
actor + '">\n'
emojiStr += ' <input type="text" name="searchtext" autofocus><br>\n'
2020-04-05 09:17:19 +00:00
emojiStr += \
' <button type="submit" class="button" name="submitSearch">' + \
2020-07-28 10:31:36 +00:00
translate['Submit'] + '</button>\n'
emojiStr += ' </form>\n'
emojiStr += ' </center>\n'
emojiStr += ' </div>\n'
emojiStr += '</div>\n'
2020-04-05 09:17:19 +00:00
emojiStr += htmlFooter()
2019-08-19 20:01:29 +00:00
return emojiStr
2020-04-05 09:17:19 +00:00
def weekDayOfMonthStart(monthNumber: int, year: int) -> int:
2019-10-10 18:25:42 +00:00
"""Gets the day number of the first day of the month
1=sun, 7=sat
"""
2020-04-05 09:17:19 +00:00
firstDayOfMonth = datetime(year, monthNumber, 1, 0, 0)
return int(firstDayOfMonth.strftime("%w")) + 1
2019-10-10 18:25:42 +00:00
2020-02-22 16:00:27 +00:00
2020-04-05 09:17:19 +00:00
def htmlCalendarDay(translate: {},
baseDir: str, path: str,
year: int, monthNumber: int, dayNumber: int,
nickname: str, domain: str, dayEvents: [],
2019-10-12 16:18:24 +00:00
monthName: str, actor: str) -> str:
2019-10-11 16:00:54 +00:00
"""Show a day within the calendar
"""
2020-04-05 09:17:19 +00:00
accountDir = baseDir + '/accounts/' + nickname + '@' + domain
calendarFile = accountDir + '/.newCalendar'
if os.path.isfile(calendarFile):
os.remove(calendarFile)
2020-02-23 10:23:12 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-calendar.css'
if os.path.isfile(baseDir + '/calendar.css'):
cssFilename = baseDir + '/calendar.css'
2019-10-11 16:00:54 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
calendarStyle = cssFile.read()
2020-07-11 17:49:45 +00:00
calActor = actor
if '/users/' in actor:
calActor = '/users/' + actor.split('/users/')[1]
2020-04-05 09:17:19 +00:00
calendarStr = htmlHeader(cssFilename, calendarStyle)
calendarStr += '<main><table class="calendar">\n'
calendarStr += '<caption class="calendar__banner--month">\n'
calendarStr += \
2020-07-11 17:49:45 +00:00
' <a href="' + calActor + '/calendar?year=' + str(year) + \
2020-07-28 10:31:36 +00:00
'?month=' + str(monthNumber) + '">\n'
2020-04-05 09:17:19 +00:00
calendarStr += \
' <h1>' + str(dayNumber) + ' ' + monthName + \
'</h1></a><br><span class="year">' + str(year) + '</span>\n'
calendarStr += '</caption>\n'
calendarStr += '<tbody>\n'
iconsDir = getIconsDir(baseDir)
2020-02-23 11:20:02 +00:00
2020-02-23 10:23:12 +00:00
if dayEvents:
for eventPost in dayEvents:
2020-04-05 09:17:19 +00:00
eventTime = None
eventDescription = None
eventPlace = None
postId = None
2020-02-23 11:20:02 +00:00
# get the time place and description
2020-02-23 10:23:12 +00:00
for ev in eventPost:
2020-04-05 09:17:19 +00:00
if ev['type'] == 'Event':
2020-02-23 11:20:02 +00:00
if ev.get('postId'):
2020-04-05 09:17:19 +00:00
postId = ev['postId']
2020-02-23 10:23:12 +00:00
if ev.get('startTime'):
2020-04-05 09:17:19 +00:00
eventDate = \
datetime.strptime(ev['startTime'],
2020-03-22 21:16:02 +00:00
"%Y-%m-%dT%H:%M:%S%z")
2020-04-05 09:17:19 +00:00
eventTime = eventDate.strftime("%H:%M").strip()
2020-02-23 10:23:12 +00:00
if ev.get('name'):
2020-04-05 09:17:19 +00:00
eventDescription = ev['name'].strip()
elif ev['type'] == 'Place':
2020-02-23 10:23:12 +00:00
if ev.get('name'):
2020-04-05 09:17:19 +00:00
eventPlace = ev['name']
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
deleteButtonStr = ''
2020-02-23 11:20:02 +00:00
if postId:
2020-04-05 09:17:19 +00:00
deleteButtonStr = \
2020-07-11 17:49:45 +00:00
'<td class="calendar__day__icons"><a href="' + calActor + \
2020-04-05 09:17:19 +00:00
'/eventdelete?id=' + postId + '?year=' + str(year) + \
'?month=' + str(monthNumber) + '?day=' + str(dayNumber) + \
'?time=' + eventTime + \
2020-07-28 10:31:36 +00:00
'">\n<img class="calendardayicon" loading="lazy" alt="' + \
2020-04-05 09:17:19 +00:00
translate['Delete this event'] + ' |" title="' + \
2020-06-17 13:21:56 +00:00
translate['Delete this event'] + '" src="/' + \
2020-07-28 10:31:36 +00:00
iconsDir + '/delete.png" /></a></td>\n'
2020-02-23 11:20:02 +00:00
2020-02-23 10:23:12 +00:00
if eventTime and eventDescription and eventPlace:
2020-04-05 09:17:19 +00:00
calendarStr += \
'<tr><td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="calendar__day__event">' + \
'<span class="place">' + \
eventPlace + '</span><br>' + eventDescription + \
'</td>' + deleteButtonStr + '</tr>\n'
2020-02-23 10:23:12 +00:00
elif eventTime and eventDescription and not eventPlace:
2020-04-05 09:17:19 +00:00
calendarStr += \
'<tr><td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="calendar__day__event">' + \
eventDescription + '</td>' + deleteButtonStr + '</tr>\n'
2020-02-23 10:23:12 +00:00
elif not eventTime and eventDescription and not eventPlace:
2020-04-05 09:17:19 +00:00
calendarStr += \
'<tr><td class="calendar__day__time">' + \
'</td><td class="calendar__day__event">' + \
eventDescription + '</td>' + deleteButtonStr + '</tr>\n'
2020-02-23 10:23:12 +00:00
elif not eventTime and eventDescription and eventPlace:
2020-04-05 09:17:19 +00:00
calendarStr += \
'<tr><td class="calendar__day__time"></td>' + \
'<td class="calendar__day__event"><span class="place">' + \
eventPlace + '</span><br>' + eventDescription + \
'</td>' + deleteButtonStr + '</tr>\n'
2020-02-23 10:23:12 +00:00
elif eventTime and not eventDescription and eventPlace:
2020-04-05 09:17:19 +00:00
calendarStr += \
'<tr><td class="calendar__day__time"><b>' + eventTime + \
'</b></td><td class="calendar__day__event">' + \
'<span class="place">' + \
eventPlace + '</span></td>' + \
deleteButtonStr + '</tr>\n'
2020-02-23 10:23:12 +00:00
2020-04-05 09:17:19 +00:00
calendarStr += '</tbody>\n'
calendarStr += '</table></main>\n'
calendarStr += htmlFooter()
2019-10-11 16:00:54 +00:00
return calendarStr
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
def htmlCalendar(translate: {},
baseDir: str, path: str,
httpPrefix: str, domainFull: str) -> str:
2019-10-10 14:43:21 +00:00
"""Show the calendar for a person
"""
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
domain = domainFull
2019-10-11 19:25:36 +00:00
if ':' in domainFull:
2020-04-05 09:17:19 +00:00
domain = domainFull.split(':')[0]
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
monthNumber = 0
dayNumber = None
year = 1970
actor = httpPrefix + '://' + domainFull + path.replace('/calendar', '')
2019-10-10 18:25:42 +00:00
if '?' in actor:
2020-04-05 09:17:19 +00:00
first = True
2019-10-10 18:25:42 +00:00
for p in actor.split('?'):
if not first:
if '=' in p:
2020-04-05 09:17:19 +00:00
if p.split('=')[0] == 'year':
numStr = p.split('=')[1]
2019-10-10 18:25:42 +00:00
if numStr.isdigit():
2020-04-05 09:17:19 +00:00
year = int(numStr)
elif p.split('=')[0] == 'month':
numStr = p.split('=')[1]
2019-10-10 18:25:42 +00:00
if numStr.isdigit():
2020-04-05 09:17:19 +00:00
monthNumber = int(numStr)
elif p.split('=')[0] == 'day':
numStr = p.split('=')[1]
2019-10-11 16:00:54 +00:00
if numStr.isdigit():
2020-04-05 09:17:19 +00:00
dayNumber = int(numStr)
first = False
actor = actor.split('?')[0]
2019-10-10 18:25:42 +00:00
2020-04-05 09:17:19 +00:00
currDate = datetime.now()
if year == 1970 and monthNumber == 0:
year = currDate.year
monthNumber = currDate.month
2019-10-10 18:25:42 +00:00
2020-04-05 09:17:19 +00:00
nickname = getNicknameFromActor(actor)
2019-10-11 16:00:54 +00:00
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/calendar-background.png'):
if not os.path.isfile(baseDir + '/accounts/calendar-background.png'):
copyfile(baseDir + '/img/calendar-background.png',
baseDir + '/accounts/calendar-background.png')
2019-10-11 16:00:54 +00:00
2020-04-05 09:17:19 +00:00
months = ('January', 'February', 'March', 'April',
'May', 'June', 'July', 'August', 'September',
'October', 'November', 'December')
monthName = translate[months[monthNumber - 1]]
2020-02-23 10:20:10 +00:00
2019-10-11 16:00:54 +00:00
if dayNumber:
2020-04-05 09:17:19 +00:00
dayEvents = None
events = \
getTodaysEvents(baseDir, nickname, domain,
year, monthNumber, dayNumber)
2020-02-23 10:23:12 +00:00
if events:
if events.get(str(dayNumber)):
2020-04-05 09:17:19 +00:00
dayEvents = events[str(dayNumber)]
return htmlCalendarDay(translate, baseDir, path,
year, monthNumber, dayNumber,
nickname, domain, dayEvents,
monthName, actor)
events = \
getCalendarEvents(baseDir, nickname, domain, year, monthNumber)
prevYear = year
prevMonthNumber = monthNumber - 1
if prevMonthNumber < 1:
prevMonthNumber = 12
prevYear = year - 1
nextYear = year
nextMonthNumber = monthNumber + 1
if nextMonthNumber > 12:
nextMonthNumber = 1
nextYear = year + 1
print('Calendar year=' + str(year) + ' month=' + str(monthNumber) +
' ' + str(weekDayOfMonthStart(monthNumber, year)))
if monthNumber < 12:
daysInMonth = \
(date(year, monthNumber + 1, 1) - date(year, monthNumber, 1)).days
2019-10-10 18:25:42 +00:00
else:
2020-04-05 09:17:19 +00:00
daysInMonth = \
(date(year + 1, 1, 1) - date(year, monthNumber, 1)).days
2020-08-29 13:09:39 +00:00
# print('daysInMonth ' + str(monthNumber) + ': ' + str(daysInMonth))
2019-10-10 14:43:21 +00:00
2020-04-05 09:17:19 +00:00
cssFilename = baseDir + '/epicyon-calendar.css'
if os.path.isfile(baseDir + '/calendar.css'):
cssFilename = baseDir + '/calendar.css'
2019-10-10 14:43:21 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
calendarStyle = cssFile.read()
2020-07-11 17:45:45 +00:00
calActor = actor
if '/users/' in actor:
calActor = '/users/' + actor.split('/users/')[1]
2020-04-05 09:17:19 +00:00
calendarStr = htmlHeader(cssFilename, calendarStyle)
calendarStr += '<main><table class="calendar">\n'
calendarStr += '<caption class="calendar__banner--month">\n'
calendarStr += \
2020-07-11 17:45:45 +00:00
' <a href="' + calActor + '/calendar?year=' + str(prevYear) + \
2020-04-05 09:17:19 +00:00
'?month=' + str(prevMonthNumber) + '">'
calendarStr += \
' <img loading="lazy" alt="' + translate['Previous month'] + \
'" title="' + translate['Previous month'] + '" src="/' + iconsDir + \
2020-02-23 14:24:11 +00:00
'/prev.png" class="buttonprev"/></a>\n'
2020-07-11 17:45:45 +00:00
calendarStr += ' <a href="' + calActor + '/inbox">'
2020-04-05 09:17:19 +00:00
calendarStr += ' <h1>' + monthName + '</h1></a>\n'
calendarStr += \
2020-07-11 17:45:45 +00:00
' <a href="' + calActor + '/calendar?year=' + str(nextYear) + \
2020-04-05 09:17:19 +00:00
'?month=' + str(nextMonthNumber) + '">'
calendarStr += \
' <img loading="lazy" alt="' + translate['Next month'] + \
'" title="' + translate['Next month'] + '" src="/' + iconsDir + \
2020-02-23 14:24:11 +00:00
'/prev.png" class="buttonnext"/></a>\n'
2020-04-05 09:17:19 +00:00
calendarStr += '</caption>\n'
calendarStr += '<thead>\n'
calendarStr += '<tr>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Sun'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Mon'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Tue'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Wed'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Thu'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Fri'] + '</th>\n'
calendarStr += ' <th class="calendar__day__header">' + \
translate['Sat'] + '</th>\n'
calendarStr += '</tr>\n'
calendarStr += '</thead>\n'
calendarStr += '<tbody>\n'
dayOfMonth = 0
dow = weekDayOfMonthStart(monthNumber, year)
2020-08-29 13:09:39 +00:00
for weekOfMonth in range(1, 7):
2020-08-29 13:35:22 +00:00
if dayOfMonth == daysInMonth:
continue
2020-08-29 19:54:30 +00:00
calendarStr += ' <tr>\n'
2020-04-05 09:17:19 +00:00
for dayNumber in range(1, 8):
if (weekOfMonth > 1 and dayOfMonth < daysInMonth) or \
(weekOfMonth == 1 and dayNumber >= dow):
dayOfMonth += 1
isToday = False
if year == currDate.year:
if currDate.month == monthNumber:
if dayOfMonth == currDate.day:
isToday = True
2019-10-11 10:28:28 +00:00
if events.get(str(dayOfMonth)):
2020-07-11 17:51:40 +00:00
url = calActor + '/calendar?year=' + \
str(year) + '?month=' + \
2020-04-05 09:17:19 +00:00
str(monthNumber) + '?day=' + str(dayOfMonth)
dayLink = '<a href="' + url + '">' + \
str(dayOfMonth) + '</a>'
2019-10-11 10:28:28 +00:00
# there are events for this day
if not isToday:
2020-04-05 09:17:19 +00:00
calendarStr += \
' <td class="calendar__day__cell" ' + \
'data-event="">' + \
dayLink + '</td>\n'
2019-10-11 10:28:28 +00:00
else:
2020-04-05 09:17:19 +00:00
calendarStr += \
' <td class="calendar__day__cell" ' + \
'data-today-event="">' + \
dayLink + '</td>\n'
2019-10-10 20:51:36 +00:00
else:
2019-10-11 10:28:28 +00:00
# No events today
if not isToday:
2020-04-05 09:17:19 +00:00
calendarStr += \
' <td class="calendar__day__cell">' + \
str(dayOfMonth) + '</td>\n'
2019-10-11 10:28:28 +00:00
else:
2020-04-05 09:17:19 +00:00
calendarStr += \
' <td class="calendar__day__cell" ' + \
'data-today="">' + str(dayOfMonth) + '</td>\n'
2019-10-10 18:25:42 +00:00
else:
2020-04-05 09:17:19 +00:00
calendarStr += ' <td class="calendar__day__cell"></td>\n'
calendarStr += ' </tr>\n'
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
calendarStr += '</tbody>\n'
calendarStr += '</table></main>\n'
calendarStr += htmlFooter()
2019-10-10 14:43:21 +00:00
return calendarStr
2020-04-05 09:17:19 +00:00
def removeOldHashtags(baseDir: str, maxMonths: int) -> str:
"""Remove old hashtags
"""
if maxMonths > 11:
maxMonths = 11
maxDaysSinceEpoch = \
(datetime.utcnow() - datetime(1970, 1 + maxMonths, 1)).days
removeHashtags = []
for subdir, dirs, files in os.walk(baseDir + '/tags'):
for f in files:
tagsFilename = os.path.join(baseDir + '/tags', f)
if not os.path.isfile(tagsFilename):
continue
# get last modified datetime
modTimesinceEpoc = os.path.getmtime(tagsFilename)
lastModifiedDate = datetime.fromtimestamp(modTimesinceEpoc)
fileDaysSinceEpoch = (lastModifiedDate - datetime(1970, 1, 1)).days
# check of the file is too old
if fileDaysSinceEpoch < maxDaysSinceEpoch:
removeHashtags.append(tagsFilename)
for removeFilename in removeHashtags:
try:
os.remove(removeFilename)
except BaseException:
pass
2020-04-05 09:17:19 +00:00
def htmlHashTagSwarm(baseDir: str, actor: str) -> str:
2019-12-13 17:57:15 +00:00
"""Returns a tag swarm of today's hashtags
2019-12-12 20:39:49 +00:00
"""
2020-05-31 11:48:41 +00:00
currTime = datetime.utcnow()
daysSinceEpoch = (currTime - datetime(1970, 1, 1)).days
2020-04-05 09:17:19 +00:00
daysSinceEpochStr = str(daysSinceEpoch) + ' '
tagSwarm = []
2020-04-05 09:17:19 +00:00
for subdir, dirs, files in os.walk(baseDir + '/tags'):
2019-12-12 20:39:49 +00:00
for f in files:
2020-04-05 09:17:19 +00:00
tagsFilename = os.path.join(baseDir + '/tags', f)
2019-12-12 20:39:49 +00:00
if not os.path.isfile(tagsFilename):
continue
# get last modified datetime
2020-05-31 11:55:08 +00:00
modTimesinceEpoc = os.path.getmtime(tagsFilename)
lastModifiedDate = datetime.fromtimestamp(modTimesinceEpoc)
2020-05-31 11:48:41 +00:00
fileDaysSinceEpoch = (lastModifiedDate - datetime(1970, 1, 1)).days
# check if the file was last modified today
if fileDaysSinceEpoch != daysSinceEpoch:
continue
2020-05-31 11:48:41 +00:00
2020-04-05 09:17:19 +00:00
hashTagName = f.split('.')[0]
if isBlockedHashtag(baseDir, hashTagName):
2019-12-13 09:46:46 +00:00
continue
2019-12-12 20:45:55 +00:00
if daysSinceEpochStr not in open(tagsFilename).read():
2019-12-12 20:39:49 +00:00
continue
with open(tagsFilename, 'r') as tagsFile:
line = tagsFile.readline()
lineCtr = 1
2020-04-05 09:17:19 +00:00
tagCtr = 0
2020-05-31 12:03:35 +00:00
maxLineCtr = 1
while line:
if ' ' not in line:
2020-04-15 16:25:13 +00:00
line = tagsFile.readline()
lineCtr += 1
2020-04-15 16:25:55 +00:00
# don't read too many lines
2020-05-31 12:03:35 +00:00
if lineCtr >= maxLineCtr:
2020-04-15 16:25:55 +00:00
break
2019-12-12 20:39:49 +00:00
continue
postDaysSinceEpochStr = line.split(' ')[0]
2019-12-12 20:39:49 +00:00
if not postDaysSinceEpochStr.isdigit():
2020-04-15 16:25:13 +00:00
line = tagsFile.readline()
lineCtr += 1
2020-04-15 16:25:55 +00:00
# don't read too many lines
2020-05-31 12:03:35 +00:00
if lineCtr >= maxLineCtr:
2020-04-15 16:25:55 +00:00
break
2019-12-12 20:39:49 +00:00
continue
2020-04-05 09:17:19 +00:00
postDaysSinceEpoch = int(postDaysSinceEpochStr)
if postDaysSinceEpoch < daysSinceEpoch:
2019-12-12 20:39:49 +00:00
break
2020-04-05 09:17:19 +00:00
if postDaysSinceEpoch == daysSinceEpoch:
if tagCtr == 0:
2020-02-28 20:50:56 +00:00
tagSwarm.append(hashTagName)
2020-04-05 09:17:19 +00:00
tagCtr += 1
line = tagsFile.readline()
lineCtr += 1
# don't read too many lines
2020-05-31 12:03:35 +00:00
if lineCtr >= maxLineCtr:
break
2019-12-13 17:57:15 +00:00
if not tagSwarm:
2019-12-12 20:39:49 +00:00
return ''
2019-12-13 17:57:15 +00:00
tagSwarm.sort()
2020-04-05 09:17:19 +00:00
tagSwarmStr = ''
ctr = 0
2019-12-13 17:57:15 +00:00
for tagName in tagSwarm:
2020-05-31 11:48:41 +00:00
tagSwarmStr += \
'<a href="' + actor + '/tags/' + tagName + \
2020-07-28 10:35:56 +00:00
'" class="hashtagswarm">' + tagName + '</a>\n'
2020-04-05 09:17:19 +00:00
ctr += 1
tagSwarmHtml = tagSwarmStr.strip() + '\n'
2019-12-13 17:57:15 +00:00
return tagSwarmHtml
2019-12-12 20:39:49 +00:00
2020-04-05 09:17:19 +00:00
def htmlSearch(translate: {},
2020-06-10 12:53:16 +00:00
baseDir: str, path: str, domain: str) -> str:
2019-07-30 22:34:04 +00:00
"""Search called from the timeline icon
"""
2020-04-05 09:17:19 +00:00
actor = path.replace('/search', '')
2020-06-10 12:49:09 +00:00
searchNickname = getNicknameFromActor(actor)
2020-04-05 09:17:19 +00:00
if os.path.isfile(baseDir + '/img/search-background.png'):
if not os.path.isfile(baseDir + '/accounts/search-background.png'):
copyfile(baseDir + '/img/search-background.png',
baseDir + '/accounts/search-background.png')
cssFilename = baseDir + '/epicyon-search.css'
2020-09-04 10:11:30 +00:00
if os.path.isfile(baseDir + '/search.css'):
cssFilename = baseDir + '/search.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read()
followStr = htmlHeader(cssFilename, profileStyle)
2020-06-10 12:49:09 +00:00
# show a banner above the search box
searchBannerFilename = \
baseDir + '/accounts/' + searchNickname + '@' + domain + \
2020-06-10 12:57:06 +00:00
'/search_banner.png'
2020-06-10 12:49:09 +00:00
if not os.path.isfile(searchBannerFilename):
theme = getConfigParam(baseDir, 'theme').lower()
if theme == 'default':
theme = ''
else:
theme = '_' + theme
themeSearchBannerFilename = \
baseDir + '/img/search_banner' + theme + '.png'
if os.path.isfile(themeSearchBannerFilename):
copyfile(themeSearchBannerFilename, searchBannerFilename)
if os.path.isfile(searchBannerFilename):
2020-07-28 10:31:36 +00:00
followStr += '<center>\n<div class="searchBanner">\n' + \
2020-06-10 13:27:29 +00:00
'<br><br><br><br><br><br><br><br>' + \
2020-07-28 10:31:36 +00:00
'<br><br><br><br><br><br><br><br>\n</div>\n</center>\n'
2020-06-10 12:49:09 +00:00
# show the search box
2020-07-28 10:31:36 +00:00
followStr += '<div class="follow">\n'
followStr += ' <div class="followAvatar">\n'
followStr += ' <center>\n'
2020-04-11 12:37:20 +00:00
idx = 'Enter an address, shared item, !history, #hashtag, ' + \
2020-04-05 09:17:19 +00:00
'*skill or :emoji: to search for'
followStr += \
2020-07-28 10:31:36 +00:00
' <p class="followText">' + translate[idx] + '</p>\n'
2020-04-05 09:17:19 +00:00
followStr += ' <form method="POST" ' + \
2020-07-28 10:31:36 +00:00
'accept-charset="UTF-8" action="' + actor + '/searchhandle">\n'
followStr += \
' <input type="hidden" name="actor" value="' + actor + '">\n'
followStr += ' <input type="text" name="searchtext" autofocus><br>\n'
2020-08-12 10:02:37 +00:00
followStr += ' <a href="/"><button type="button" class="button" ' + \
'name="submitBack">' + translate['Go Back'] + '</button></a>\n'
2020-08-12 09:40:18 +00:00
followStr += ' <button type="submit" class="button" ' + \
'name="submitSearch">' + translate['Submit'] + '</button>\n'
2020-07-28 10:31:36 +00:00
followStr += ' </form>\n'
2020-04-05 09:17:19 +00:00
followStr += ' <p class="hashtagswarm">' + \
2020-07-28 10:31:36 +00:00
htmlHashTagSwarm(baseDir, actor) + '</p>\n'
followStr += ' </center>\n'
followStr += ' </div>\n'
followStr += '</div>\n'
2020-04-05 09:17:19 +00:00
followStr += htmlFooter()
2019-07-30 22:34:04 +00:00
return followStr
2020-04-05 09:17:19 +00:00
def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int,
translate: {},
baseDir: str, path: str, httpPrefix: str,
nickname: str, domain: str, port: int,
profileHandle: str,
2020-05-07 15:15:44 +00:00
session, cachedWebfingers: {}, personCache: {},
debug: bool, projectVersion: str,
YTReplacementDomain: str) -> str:
2019-07-30 22:34:04 +00:00
"""Show a profile page after a search for a fediverse address
"""
2019-10-17 22:35:56 +00:00
if '/users/' in profileHandle or \
2020-08-13 16:19:35 +00:00
'/accounts/' in profileHandle or \
2019-10-17 22:35:56 +00:00
'/channel/' in profileHandle or \
'/profile/' in profileHandle or \
'/@' in profileHandle:
2020-04-05 09:17:19 +00:00
searchNickname = getNicknameFromActor(profileHandle)
searchDomain, searchPort = getDomainFromActor(profileHandle)
2019-07-30 22:34:04 +00:00
else:
if '@' not in profileHandle:
2020-05-07 14:45:54 +00:00
print('DEBUG: no @ in ' + profileHandle)
2019-07-30 22:34:04 +00:00
return None
if profileHandle.startswith('@'):
2020-04-05 09:17:19 +00:00
profileHandle = profileHandle[1:]
2019-07-30 22:34:04 +00:00
if '@' not in profileHandle:
2020-05-07 14:45:54 +00:00
print('DEBUG: no @ in ' + profileHandle)
2019-07-30 22:34:04 +00:00
return None
2020-04-05 09:17:19 +00:00
searchNickname = profileHandle.split('@')[0]
searchDomain = profileHandle.split('@')[1]
searchPort = None
2019-07-30 22:34:04 +00:00
if ':' in searchDomain:
2020-04-05 09:17:19 +00:00
searchPortStr = searchDomain.split(':')[1]
2020-03-01 10:18:08 +00:00
if searchPortStr.isdigit():
2020-04-05 09:17:19 +00:00
searchPort = int(searchPortStr)
searchDomain = searchDomain.split(':')[0]
2020-06-19 21:05:09 +00:00
if searchPort:
print('DEBUG: Search for handle ' +
str(searchNickname) + '@' + str(searchDomain) + ':' +
str(searchPort))
else:
print('DEBUG: Search for handle ' +
str(searchNickname) + '@' + str(searchDomain))
2019-07-30 22:34:04 +00:00
if not searchNickname:
2020-05-07 14:45:54 +00:00
print('DEBUG: No nickname found in ' + profileHandle)
2019-07-30 22:34:04 +00:00
return None
if not searchDomain:
2020-05-07 14:45:54 +00:00
print('DEBUG: No domain found in ' + profileHandle)
2019-07-30 22:34:04 +00:00
return None
2020-05-07 14:45:54 +00:00
2020-04-05 09:17:19 +00:00
searchDomainFull = searchDomain
2019-07-30 22:34:04 +00:00
if searchPort:
2020-04-05 09:17:19 +00:00
if searchPort != 80 and searchPort != 443:
if ':' not in searchDomain:
2020-04-05 09:17:19 +00:00
searchDomainFull = searchDomain + ':' + str(searchPort)
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
profileStr = ''
cssFilename = baseDir + '/epicyon-profile.css'
if os.path.isfile(baseDir + '/epicyon.css'):
cssFilename = baseDir + '/epicyon.css'
2019-09-11 09:58:43 +00:00
with open(cssFilename, 'r') as cssFile:
2020-04-05 09:17:19 +00:00
wf = \
webfingerHandle(session,
searchNickname + '@' + searchDomainFull,
2020-05-07 15:15:44 +00:00
httpPrefix, cachedWebfingers,
2020-05-07 15:49:48 +00:00
domain, projectVersion)
2019-07-30 22:34:04 +00:00
if not wf:
print('DEBUG: Unable to webfinger ' +
searchNickname + '@' + searchDomainFull)
2020-05-07 15:15:44 +00:00
print('DEBUG: cachedWebfingers ' + str(cachedWebfingers))
2020-05-07 14:45:54 +00:00
print('DEBUG: httpPrefix ' + httpPrefix)
print('DEBUG: domain ' + domain)
2019-07-30 22:34:04 +00:00
return None
2020-06-23 10:41:12 +00:00
if not isinstance(wf, dict):
2020-06-23 10:44:06 +00:00
print('WARN: Webfinger search for ' +
2020-06-23 10:42:44 +00:00
searchNickname + '@' + searchDomainFull +
2020-06-23 10:41:12 +00:00
' did not return a dict. ' +
str(wf))
return None
2020-05-07 14:45:54 +00:00
2020-04-05 09:17:19 +00:00
personUrl = None
2019-10-17 15:18:43 +00:00
if wf.get('errors'):
2020-04-05 09:17:19 +00:00
personUrl = httpPrefix + '://' + \
searchDomainFull + '/users/' + searchNickname
2020-03-22 21:16:02 +00:00
2020-04-05 09:17:19 +00:00
profileStr = 'https://www.w3.org/ns/activitystreams'
asHeader = {
'Accept': 'application/activity+json; profile="' + profileStr + '"'
2020-02-23 15:32:47 +00:00
}
2019-10-17 15:18:43 +00:00
if not personUrl:
2020-04-05 09:17:19 +00:00
personUrl = getUserUrl(wf)
if not personUrl:
2019-10-21 09:57:42 +00:00
# try single user instance
2020-04-05 09:17:19 +00:00
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
2020-03-22 20:36:19 +00:00
}
2020-04-05 09:17:19 +00:00
personUrl = httpPrefix + '://' + searchDomainFull
profileJson = \
getJson(session, personUrl, asHeader, None,
projectVersion, httpPrefix, domain)
2019-10-17 22:35:56 +00:00
if not profileJson:
2020-04-05 09:17:19 +00:00
asHeader = {
'Accept': 'application/ld+json; profile="' + profileStr + '"'
2020-02-23 15:32:47 +00:00
}
2020-04-05 09:17:19 +00:00
profileJson = \
getJson(session, personUrl, asHeader, None,
projectVersion, httpPrefix, domain)
2019-07-30 22:34:04 +00:00
if not profileJson:
print('DEBUG: No actor returned from ' + personUrl)
2019-07-30 22:34:04 +00:00
return None
2020-04-05 09:17:19 +00:00
avatarUrl = ''
2019-07-30 22:34:04 +00:00
if profileJson.get('icon'):
if profileJson['icon'].get('url'):
2020-04-05 09:17:19 +00:00
avatarUrl = profileJson['icon']['url']
2019-08-18 13:30:40 +00:00
if not avatarUrl:
avatarUrl = getPersonAvatarUrl(baseDir, personUrl,
personCache, True)
2020-04-05 09:17:19 +00:00
displayName = searchNickname
2019-08-22 18:37:22 +00:00
if profileJson.get('name'):
2020-04-05 09:17:19 +00:00
displayName = profileJson['name']
profileDescription = ''
2019-07-31 12:44:08 +00:00
if profileJson.get('summary'):
2020-04-05 09:17:19 +00:00
profileDescription = profileJson['summary']
outboxUrl = None
2019-07-30 22:34:04 +00:00
if not profileJson.get('outbox'):
if debug:
pprint(profileJson)
print('DEBUG: No outbox found')
return None
2020-04-05 09:17:19 +00:00
outboxUrl = profileJson['outbox']
profileBackgroundImage = ''
2019-07-30 22:34:04 +00:00
if profileJson.get('image'):
if profileJson['image'].get('url'):
2020-04-05 09:17:19 +00:00
profileBackgroundImage = profileJson['image']['url']
2019-07-30 22:34:04 +00:00
2020-04-05 09:17:19 +00:00
profileStyle = cssFile.read().replace('image.png',
profileBackgroundImage)
if httpPrefix != 'https':
profileStyle = profileStyle.replace('https://',
httpPrefix + '://')
# url to return to
2020-04-05 09:17:19 +00:00
backUrl = path
if not backUrl.endswith('/inbox'):
2020-04-05 09:17:19 +00:00
backUrl += '/inbox'
2020-04-05 09:17:19 +00:00
profileDescriptionShort = profileDescription
2019-10-23 14:27:43 +00:00
if '\n' in profileDescription:
2020-04-05 09:17:19 +00:00
if len(profileDescription.split('\n')) > 2:
profileDescriptionShort = ''
2019-10-23 14:27:43 +00:00
else:
if '<br>' in profileDescription:
2020-04-05 09:17:19 +00:00
if len(profileDescription.split('<br>')) > 2:
profileDescriptionShort = ''
2019-10-23 15:09:20 +00:00
# keep the profile description short
2020-04-05 09:17:19 +00:00
if len(profileDescriptionShort) > 256:
profileDescriptionShort = ''
2019-10-23 15:09:20 +00:00
# remove formatting from profile description used on title
2020-06-28 09:40:59 +00:00
avatarDescription = ''
if profileJson.get('summary'):
2020-06-28 09:32:11 +00:00
if isinstance(profileJson['summary'], str):
2020-06-28 19:04:43 +00:00
avatarDescription = \
profileJson['summary'].replace('<br>', '\n')
2020-06-28 09:32:11 +00:00
avatarDescription = avatarDescription.replace('<p>', '')
avatarDescription = avatarDescription.replace('</p>', '')
if '<' in avatarDescription:
avatarDescription = removeHtml(avatarDescription)
2020-07-28 10:31:36 +00:00
profileStr = ' <div class="hero-image">\n'
profileStr += ' <div class="hero-text">\n'
2020-06-28 09:40:59 +00:00
if avatarUrl:
profileStr += \
' <img loading="lazy" src="' + avatarUrl + \
'" alt="' + avatarDescription + '" title="' + \
2020-07-28 10:31:36 +00:00
avatarDescription + '" class="title">\n'
profileStr += ' <h1>' + displayName + '</h1>\n'
2020-04-05 09:17:19 +00:00
profileStr += ' <p><b>@' + searchNickname + '@' + \
2020-07-28 10:31:36 +00:00
searchDomainFull + '</b></p>\n'
profileStr += ' <p>' + profileDescriptionShort + '</p>\n'
profileStr += ' </div>\n'
profileStr += '</div>\n'
2020-04-05 09:17:19 +00:00
profileStr += '<div class="container">\n'
profileStr += ' <form method="POST" action="' + \
2020-07-28 10:31:36 +00:00
backUrl + '/followconfirm">\n'
profileStr += ' <center>\n'
2020-04-05 09:17:19 +00:00
profileStr += \
' <input type="hidden" name="actor" value="' + \
2020-07-28 10:31:36 +00:00
personUrl + '">\n'
2020-08-12 09:40:18 +00:00
profileStr += \
' <a href="' + backUrl + '"><button class="button">' + \
translate['Go Back'] + '</button></a>\n'
2020-04-05 09:17:19 +00:00
profileStr += \
' <button type="submit" class="button" name="submitYes">' + \
2020-07-28 10:31:36 +00:00
translate['Follow'] + '</button>\n'
2020-04-05 09:17:19 +00:00
profileStr += \
' <button type="submit" class="button" name="submitView">' + \
2020-07-28 10:31:36 +00:00
translate['View'] + '</button>\n'
profileStr += ' </center>\n'
profileStr += ' </form>\n'
profileStr += '</div>\n'
2020-04-05 09:17:19 +00:00
iconsDir = getIconsDir(baseDir)
i = 0
for item in parseUserFeed(session, outboxUrl, asHeader,
projectVersion, httpPrefix, domain):
2019-07-30 22:34:04 +00:00
if not item.get('type'):
continue
2020-04-05 09:17:19 +00:00
if item['type'] != 'Create' and item['type'] != 'Announce':
2019-07-30 22:34:04 +00:00
continue
if not item.get('object'):
continue
2020-04-05 09:17:19 +00:00
profileStr += \
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
2020-04-05 09:17:19 +00:00
iconsDir, translate, None, baseDir,
2020-05-07 15:15:44 +00:00
session, cachedWebfingers, personCache,
2020-04-05 09:17:19 +00:00
nickname, domain, port,
item, avatarUrl, False, False,
httpPrefix, projectVersion, 'inbox',
YTReplacementDomain,
2020-04-05 09:17:19 +00:00
False, False, False, False, False)
i += 1
if i >= 20:
2019-07-30 22:34:04 +00:00
break
2020-04-05 09:17:19 +00:00
return htmlHeader(cssFilename, profileStyle) + profileStr + htmlFooter()