2019-07-20 21:13:36 +00:00
__filename__ = " webinterface.py "
__author__ = " Bob Mottram "
__license__ = " AGPL3+ "
2019-12-14 10:52:19 +00:00
__version__ = " 1.1.0 "
2019-07-20 21:13:36 +00:00
__maintainer__ = " Bob Mottram "
__email__ = " bob@freedombone.net "
__status__ = " Production "
import json
2019-07-24 22:38:42 +00:00
import time
import os
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
2019-09-14 17:12:03 +00:00
from shutil import copyfileobj
2019-07-22 11:44:31 +00:00
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
2019-12-17 20:44:18 +00:00
from pgp import getEmailAddress
from pgp import getPGPpubKey
2019-12-17 14:57:16 +00:00
from xmpp import getXmppAddress
2019-12-17 15:25:34 +00:00
from matrix import getMatrixAddress
2019-11-07 12:14:17 +00:00
from donate import getDonationUrl
2019-11-24 17:40:31 +00:00
from utils import updateRecentPostsCache
2019-07-21 12:41:31 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
2019-08-02 18:57:06 +00:00
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
2019-12-10 17:19:53 +00:00
from utils import isPublicPostFromUrl
2019-08-22 18:36:07 +00:00
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
from utils import saveJson
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
2019-08-02 18:57:06 +00:00
from posts import getPersonBox
2019-07-30 22:34:04 +00:00
from posts import getUserUrl
from posts import parseUserFeed
2019-08-02 18:57:06 +00:00
from posts import populateRepliesJson
2019-08-12 13:22:17 +00:00
from posts import isModerator
2019-08-20 12:48:55 +00:00
from posts import outboxMessageCreateWrap
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
2019-08-02 14:36:03 +00:00
from blocking import isBlocked
2019-12-13 09:46:46 +00:00
from blocking import isBlockedHashtag
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
2019-08-08 11:24:26 +00:00
from config import getConfigParam
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
2020-02-22 19:11:51 +00:00
from happening import todaysEventsCheck
from happening import thisWeeksEventsCheck
2020-02-23 09:35:52 +00:00
from happening import getCalendarEvents
2020-02-23 10:20:10 +00:00
from happening import getTodaysEvents
2019-07-20 21:13:36 +00:00
2020-02-23 15:32:47 +00:00
def updateAvatarImageCache ( session , baseDir : str , httpPrefix : str , \
actor : str , avatarUrl : str , \
personCache : { } , 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-02-04 19:34:52 +00:00
actorStr = actor . replace ( ' / ' , ' - ' )
avatarImagePath = baseDir + ' /cache/avatars/ ' + actorStr
2019-09-14 18:40:03 +00:00
if avatarUrl . endswith ( ' .png ' ) or ' .png? ' in avatarUrl :
2019-09-14 17:16:03 +00:00
sessionHeaders = { ' Accept ' : ' image/png ' }
2019-10-18 12:51:37 +00:00
avatarImageFilename = avatarImagePath + ' .png '
2019-09-14 18:40:03 +00:00
elif avatarUrl . endswith ( ' .jpg ' ) or avatarUrl . endswith ( ' .jpeg ' ) or \
2019-09-14 18:40:47 +00:00
' .jpg? ' in avatarUrl or ' .jpeg? ' in avatarUrl :
2019-09-14 17:16:03 +00:00
sessionHeaders = { ' Accept ' : ' image/jpeg ' }
2019-10-18 12:51:37 +00:00
avatarImageFilename = avatarImagePath + ' .jpg '
2019-09-14 18:40:03 +00:00
elif avatarUrl . endswith ( ' .gif ' ) or ' .gif? ' in avatarUrl :
2019-09-14 17:16:03 +00:00
sessionHeaders = { ' Accept ' : ' image/gif ' }
2019-10-18 12:51:37 +00:00
avatarImageFilename = avatarImagePath + ' .gif '
2019-11-14 15:11:20 +00:00
elif avatarUrl . endswith ( ' .webp ' ) or ' .webp? ' in avatarUrl :
sessionHeaders = { ' Accept ' : ' image/webp ' }
avatarImageFilename = avatarImagePath + ' .webp '
2019-09-14 17:16:03 +00:00
else :
return None
2019-09-14 17:12:03 +00:00
if not os . path . isfile ( avatarImageFilename ) or force :
try :
2019-09-14 18:27:49 +00:00
print ( ' avatar image url: ' + avatarUrl )
2019-09-14 17:12:03 +00:00
result = session . get ( avatarUrl , headers = sessionHeaders , params = None )
2019-09-14 19:39:51 +00:00
if result . status_code < 200 or result . status_code > 202 :
2020-02-23 15:32:47 +00:00
print ( ' Avatar image download failed with status ' + \
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 )
print ( ' avatar image downloaded for ' + actor )
return avatarImageFilename . replace ( baseDir + ' /cache ' , ' ' )
2019-09-14 17:12:03 +00:00
except Exception as e :
print ( ' Failed to download avatar image: ' + str ( avatarUrl ) )
print ( e )
2019-10-18 12:51:37 +00:00
if ' /channel/ ' not in actor :
2020-02-23 15:32:47 +00:00
sessionHeaders = {
' Accept ' : ' application/activity+json; profile= " https://www.w3.org/ns/activitystreams " '
}
2019-10-18 12:51:37 +00:00
else :
2020-02-23 15:32:47 +00:00
sessionHeaders = {
' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " '
}
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
if personJson [ ' id ' ] != actor :
return None
if not personCache . get ( actor ) :
return None
2020-02-23 15:32:47 +00:00
if personCache [ actor ] [ ' actor ' ] [ ' publicKey ' ] [ ' publicKeyPem ' ] != \
personJson [ ' publicKey ' ] [ ' publicKeyPem ' ] :
2019-09-14 19:06:08 +00:00
print ( " ERROR: public keys don ' t match when downloading actor for " + actor )
return None
2019-09-14 17:12:03 +00:00
storePersonInCache ( baseDir , actor , personJson , personCache )
return getPersonAvatarUrl ( baseDir , actor , personCache )
2019-09-14 17:52:55 +00:00
return None
return avatarImageFilename . replace ( baseDir + ' /cache ' , ' ' )
2019-09-14 17:12:03 +00:00
2019-08-20 09:37:09 +00:00
def getPersonAvatarUrl ( baseDir : str , personUrl : str , personCache : { } ) - > str :
2019-08-18 13:30:40 +00:00
""" Returns the avatar url for the person
"""
2019-09-14 17:29:55 +00:00
personJson = getPersonFromCache ( baseDir , personUrl , personCache )
2019-10-31 20:46:37 +00:00
if not personJson :
return None
# get from locally stored image
actorStr = personJson [ ' id ' ] . replace ( ' / ' , ' - ' )
avatarImagePath = baseDir + ' /cache/avatars/ ' + actorStr
if os . path . isfile ( avatarImagePath + ' .png ' ) :
return ' /avatars/ ' + actorStr + ' .png '
if os . path . isfile ( avatarImagePath + ' .jpg ' ) :
return ' /avatars/ ' + actorStr + ' .jpg '
if os . path . isfile ( avatarImagePath + ' .gif ' ) :
return ' /avatars/ ' + actorStr + ' .gif '
2019-11-14 15:11:20 +00:00
if os . path . isfile ( avatarImagePath + ' .webp ' ) :
return ' /avatars/ ' + actorStr + ' .webp '
2019-10-31 20:46:37 +00:00
if os . path . isfile ( avatarImagePath ) :
return ' /avatars/ ' + actorStr
2019-09-14 17:29:55 +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-02-23 15:32:47 +00:00
def htmlSearchEmoji ( translate : { } , baseDir : str , httpPrefix : str , \
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
# will be retained even if default_emoji.json is subsequently updated
2019-08-19 20:18:45 +00:00
if not os . path . isfile ( baseDir + ' /emoji/emoji.json ' ) :
copyfile ( baseDir + ' /emoji/default_emoji.json ' , baseDir + ' /emoji/emoji.json ' )
2019-08-19 19:02:28 +00:00
searchStr = searchStr . lower ( ) . replace ( ' : ' , ' ' ) . strip ( ' \n ' )
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-19 19:02:28 +00:00
emojiCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
emojiCSS = emojiCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-08-19 19:02:28 +00:00
emojiLookupFilename = baseDir + ' /emoji/emoji.json '
# create header
2019-09-11 09:58:43 +00:00
emojiForm = htmlHeader ( cssFilename , emojiCSS )
2019-09-07 10:04:42 +00:00
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 ) :
2019-09-07 10:04:42 +00:00
emojiForm + = ' <center><h5> ' + translate [ ' No results ' ] + ' </h5></center> '
2019-08-19 19:02:28 +00:00
emojiForm + = htmlFooter ( )
return emojiForm
2019-10-22 11:55:06 +00:00
emojiJson = loadJson ( emojiLookupFilename )
if emojiJson :
2019-08-19 19:02:28 +00:00
results = { }
2019-08-19 19:10:55 +00:00
for emojiName , filename in emojiJson . items ( ) :
if searchStr in emojiName :
results [ emojiName ] = filename + ' .png '
for emojiName , filename in emojiJson . items ( ) :
if emojiName in searchStr :
results [ emojiName ] = filename + ' .png '
2019-08-19 21:18:04 +00:00
headingShown = False
2019-08-19 19:02:28 +00:00
emojiForm + = ' <center> '
for emojiName , filename in results . items ( ) :
2019-08-19 21:18:04 +00:00
if os . path . isfile ( baseDir + ' /emoji/ ' + filename ) :
if not headingShown :
2020-02-23 15:32:47 +00:00
emojiForm + = \
' <center><h5> ' + \
translate [ ' Copy the text then paste it into your post ' ] + \
' </h5></center> '
2019-08-19 21:18:04 +00:00
headingShown = True
2020-02-23 15:32:47 +00:00
emojiForm + = \
' <h3>: ' + emojiName + \
' :<img loading= " lazy " class= " searchEmoji " src= " /emoji/ ' + \
filename + ' " /></h3> '
2019-08-19 19:02:28 +00:00
emojiForm + = ' </center> '
emojiForm + = htmlFooter ( )
return emojiForm
2019-09-11 18:10:32 +00:00
def getIconsDir ( baseDir : str ) - > str :
""" Returns the directory where icons exist
"""
iconsDir = ' icons '
theme = getConfigParam ( baseDir , ' theme ' )
if theme :
2019-09-11 19:59:18 +00:00
if os . path . isdir ( baseDir + ' /img/icons/ ' + theme ) :
2019-09-11 18:10:32 +00:00
iconsDir = ' icons/ ' + theme
return iconsDir
2019-09-07 10:13:12 +00:00
def htmlSearchSharedItems ( translate : { } , \
baseDir : str , searchStr : str , \
2019-08-25 21:59:42 +00:00
pageNumber : int , \
resultsPerPage : int , \
2019-09-07 10:13:12 +00:00
httpPrefix : str , \
domainFull : str , actor : str ) - > str :
2019-08-14 09:45:51 +00:00
""" Search results for shared items
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-08-14 09:45:51 +00:00
currPage = 1
ctr = 0
2019-08-13 21:32:18 +00:00
sharedItemsForm = ' '
2020-02-23 15:32:47 +00:00
searchStrLower = \
searchStr . replace ( ' % 2B ' , ' + ' ) . replace ( ' % 40 ' , ' @ ' ) . replace ( ' % 3A ' , ' : ' ) . replace ( ' % 23 ' , ' # ' ) . lower ( ) . strip ( ' \n ' )
2019-08-13 22:11:11 +00:00
searchStrLowerList = searchStrLower . split ( ' + ' )
2019-09-29 22:18:42 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
2019-09-11 09:58:43 +00:00
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
2019-12-10 14:48:08 +00:00
cssFilename = baseDir + ' /epicyon.css '
2019-09-11 09:58:43 +00:00
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-13 21:32:18 +00:00
sharedItemsCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
2020-02-23 15:32:47 +00:00
sharedItemsCSS = \
sharedItemsCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-11 09:58:43 +00:00
sharedItemsForm = htmlHeader ( cssFilename , sharedItemsCSS )
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <center><h1> ' + translate [ ' Shared Items Search ' ] + ' </h1></center> '
2019-08-13 21:32:18 +00:00
resultsExist = False
for subdir , dirs , files in os . walk ( baseDir + ' /accounts ' ) :
for handle in dirs :
if ' @ ' not in handle :
continue
2019-08-25 21:48:51 +00:00
contactNickname = handle . split ( ' @ ' ) [ 0 ]
2019-08-13 21:32:18 +00:00
sharesFilename = baseDir + ' /accounts/ ' + handle + ' /shares.json '
if not os . path . isfile ( sharesFilename ) :
continue
2019-09-17 12:14:36 +00:00
2019-10-22 11:55:06 +00:00
sharesJson = loadJson ( sharesFilename )
2019-09-17 12:14:36 +00:00
if not sharesJson :
continue
2019-08-13 21:32:18 +00:00
for name , sharedItem in sharesJson . items ( ) :
2019-08-13 22:11:11 +00:00
matched = True
for searchSubstr in searchStrLowerList :
subStrMatched = False
searchSubstr = searchSubstr . strip ( )
if searchSubstr in sharedItem [ ' location ' ] . lower ( ) :
subStrMatched = True
elif searchSubstr in sharedItem [ ' summary ' ] . lower ( ) :
subStrMatched = True
elif searchSubstr in sharedItem [ ' displayName ' ] . lower ( ) :
subStrMatched = True
elif searchSubstr in sharedItem [ ' category ' ] . lower ( ) :
subStrMatched = True
if not subStrMatched :
matched = False
break
2019-08-13 21:32:18 +00:00
if matched :
2019-08-14 09:45:51 +00:00
if currPage == pageNumber :
sharedItemsForm + = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <p class= " share-title " > ' + sharedItem [ ' displayName ' ] + ' </p> '
2019-08-25 21:16:38 +00:00
if sharedItem . get ( ' imageUrl ' ) :
2019-08-25 21:07:02 +00:00
sharedItemsForm + = ' <a href= " ' + sharedItem [ ' imageUrl ' ] + ' " > '
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <img loading= " lazy " src= " ' + sharedItem [ ' imageUrl ' ] + \
' " alt= " Item image " ></a> '
2019-08-14 09:45:51 +00:00
sharedItemsForm + = ' <p> ' + sharedItem [ ' summary ' ] + ' </p> '
2019-09-07 10:17:55 +00:00
sharedItemsForm + = ' <p><b> ' + translate [ ' Type ' ] + ' :</b> ' + sharedItem [ ' itemType ' ] + ' '
sharedItemsForm + = ' <b> ' + translate [ ' Category ' ] + ' :</b> ' + sharedItem [ ' category ' ] + ' '
sharedItemsForm + = ' <b> ' + translate [ ' Location ' ] + ' :</b> ' + sharedItem [ ' location ' ] + ' </p> '
2019-08-25 21:59:42 +00:00
contactActor = httpPrefix + ' :// ' + domainFull + ' /users/ ' + contactNickname
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <p><a href= " ' + actor + ' ?replydm=sharedesc: ' + \
sharedItem [ ' displayName ' ] + ' ?mention= ' + contactActor + \
' " ><button class= " button " > ' + translate [ ' Contact ' ] + ' </button></a> '
2019-08-26 09:13:26 +00:00
if actor . endswith ( ' /users/ ' + contactNickname ) :
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <a href= " ' + actor + ' ?rmshare= ' + name + ' " ><button class= " button " > ' + \
translate [ ' Remove ' ] + ' </button></a> '
2019-08-26 09:13:26 +00:00
sharedItemsForm + = ' </p></div> '
2019-08-14 09:45:51 +00:00
if not resultsExist and currPage > 1 :
# previous page link, needs to be a POST
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <form method= " POST " action= " ' + actor + \
' /searchhandle?page= ' + str ( pageNumber - 1 ) + ' " > '
sharedItemsForm + = \
' <input type= " hidden " name= " actor " value= " ' + actor + ' " > '
sharedItemsForm + = \
' <input type= " hidden " name= " searchtext " value= " ' + searchStrLower + ' " ><br> '
sharedItemsForm + = \
' <center><a href= " ' + actor + ' " type= " submit " name= " submitSearch " > '
sharedItemsForm + = \
' <img loading= " lazy " class= " pageicon " src= " / ' + iconsDir + \
' /pageup.png " title= " ' + translate [ ' Page up ' ] + \
' " alt= " ' + translate [ ' Page up ' ] + ' " /></a> '
2019-11-12 10:42:27 +00:00
sharedItemsForm + = ' </center> '
sharedItemsForm + = ' </form> '
resultsExist = True
2019-08-14 09:45:51 +00:00
ctr + = 1
if ctr > = resultsPerPage :
currPage + = 1
if currPage > pageNumber :
# next page link, needs to be a POST
2020-02-23 15:32:47 +00:00
sharedItemsForm + = \
' <form method= " POST " action= " ' + actor + \
' /searchhandle?page= ' + str ( pageNumber + 1 ) + ' " > '
sharedItemsForm + = \
' <input type= " hidden " name= " actor " value= " ' + actor + ' " > '
sharedItemsForm + = \
' <input type= " hidden " name= " searchtext " value= " ' + \
searchStrLower + ' " ><br> '
sharedItemsForm + = \
' <center><a href= " ' + actor + ' " type= " submit " name= " submitSearch " > '
sharedItemsForm + = \
' <img loading= " lazy " class= " pageicon " src= " / ' + iconsDir + \
' /pagedown.png " title= " ' + translate [ ' Page down ' ] + \
' " alt= " ' + translate [ ' Page down ' ] + ' " /></a> '
2019-11-12 10:42:27 +00:00
sharedItemsForm + = ' </center> '
sharedItemsForm + = ' </form> '
2019-08-14 09:45:51 +00:00
break
ctr = 0
2019-08-13 21:32:18 +00:00
if not resultsExist :
2019-09-07 10:13:12 +00:00
sharedItemsForm + = ' <center><h5> ' + translate [ ' No results ' ] + ' </h5></center> '
2019-08-13 21:32:18 +00:00
sharedItemsForm + = htmlFooter ( )
return sharedItemsForm
2019-12-10 14:48:08 +00:00
def htmlModerationInfo ( translate : { } , baseDir : str , httpPrefix : str ) - > str :
2019-08-13 17:25:39 +00:00
infoForm = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-13 17:25:39 +00:00
infoCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
infoCSS = infoCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-11 09:58:43 +00:00
infoForm = htmlHeader ( cssFilename , infoCSS )
2019-08-13 17:25:39 +00:00
2020-02-23 15:32:47 +00:00
infoForm + = \
' <center><h1> ' + translate [ ' Moderation Information ' ] + ' </h1></center> '
2019-08-13 17:25:39 +00:00
infoShown = False
suspendedFilename = baseDir + ' /accounts/suspended.txt '
if os . path . isfile ( suspendedFilename ) :
with open ( suspendedFilename , " r " ) as f :
suspendedStr = f . read ( )
2019-11-12 10:42:27 +00:00
infoForm + = ' <div class= " container " > '
infoForm + = ' <br><b> ' + translate [ ' Suspended accounts ' ] + ' </b> '
infoForm + = ' <br> ' + translate [ ' These are currently suspended ' ]
2020-02-23 15:32:47 +00:00
infoForm + = \
' <textarea id= " message " name= " suspended " style= " height:200px " > ' + \
suspendedStr + ' </textarea> '
2019-11-12 10:42:27 +00:00
infoForm + = ' </div> '
2019-08-13 17:25:39 +00:00
infoShown = True
blockingFilename = baseDir + ' /accounts/blocking.txt '
if os . path . isfile ( blockingFilename ) :
with open ( blockingFilename , " r " ) as f :
blockedStr = f . read ( )
2019-11-12 10:42:27 +00:00
infoForm + = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
infoForm + = \
' <br><b> ' + translate [ ' Blocked accounts and hashtags ' ] + ' </b> '
infoForm + = \
' <br> ' + translate [ ' These are globally blocked for all accounts on this instance ' ]
infoForm + = \
' <textarea id= " message " name= " blocked " style= " height:400px " > ' + \
blockedStr + ' </textarea> '
2019-11-12 10:42:27 +00:00
infoForm + = ' </div> '
2019-08-13 17:25:39 +00:00
infoShown = True
if not infoShown :
2020-02-23 15:32:47 +00:00
infoForm + = \
' <center><p> ' + \
translate [ ' Any blocks or suspensions made by moderators will be shown here. ' ] + \
' </p></center> '
2019-08-13 17:25:39 +00:00
infoForm + = htmlFooter ( )
return infoForm
2019-08-12 13:22:17 +00:00
2019-12-13 10:33:33 +00:00
def htmlHashtagSearch ( nickname : str , domain : str , port : int , \
2019-12-12 18:56:30 +00:00
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 17:40:31 +00:00
translate : { } , \
2019-12-12 18:56:30 +00:00
baseDir : str , hashtag : str , pageNumber : int , \
postsPerPage : int , \
2019-08-14 20:12:27 +00:00
session , wfRequest : { } , personCache : { } , \
httpPrefix : str , projectVersion : str ) - > str :
2019-08-10 10:54:52 +00:00
""" Show a page containing search results for a hashtag
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-08-10 10:54:52 +00:00
if hashtag . startswith ( ' # ' ) :
hashtag = hashtag [ 1 : ]
hashtagIndexFile = baseDir + ' /tags/ ' + hashtag + ' .txt '
if not os . path . isfile ( hashtagIndexFile ) :
return None
2019-12-13 10:33:33 +00:00
# check that the directory for the nickname exists
if nickname :
if not os . path . isdir ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain ) :
nickname = None
2019-08-10 10:54:52 +00:00
# read the index
with open ( hashtagIndexFile , " r " ) as f :
lines = f . readlines ( )
2019-12-17 09:58:22 +00:00
# read the css
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-10 10:54:52 +00:00
hashtagSearchCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
2020-02-23 15:32:47 +00:00
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 :
pageNumber = 1
elif pageNumber < 1 :
pageNumber = 1
# get the start end end within the index file
startIndex = int ( ( pageNumber - 1 ) * postsPerPage )
endIndex = startIndex + postsPerPage
noOfLines = len ( lines )
if endIndex > = noOfLines and noOfLines > 0 :
endIndex = noOfLines - 1
# add the page title
2019-09-11 09:58:43 +00:00
hashtagSearchForm = htmlHeader ( cssFilename , hashtagSearchCSS )
2019-12-12 22:36:59 +00:00
hashtagSearchForm + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-08-10 16:37:14 +00:00
hashtagSearchForm + = ' <center><h1># ' + hashtag + ' </h1></center> '
2019-12-17 09:58:22 +00:00
if startIndex > 0 :
2019-08-10 10:54:52 +00:00
# previous page link
2019-12-12 18:56:30 +00:00
hashtagSearchForm + = \
' <center><a href= " /tags/ ' + hashtag + ' ?page= ' + \
str ( pageNumber - 1 ) + ' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pageup.png " title= " ' + translate [ ' Page up ' ] + \
' " alt= " ' + translate [ ' Page up ' ] + ' " ></a></center> '
2019-08-10 10:54:52 +00:00
index = startIndex
2019-12-17 10:08:06 +00:00
while index < = endIndex :
2019-08-10 10:54:52 +00:00
postId = lines [ index ] . strip ( ' \n ' )
2019-12-12 18:56:30 +00:00
if ' ' not in postId :
nickname = getNicknameFromActor ( postId )
if not nickname :
2019-12-17 10:06:08 +00:00
index + = 1
2019-12-12 18:56:30 +00:00
continue
else :
2019-12-12 19:24:18 +00:00
postFields = postId . split ( ' ' )
if len ( postFields ) != 3 :
2019-12-17 10:06:08 +00:00
index = + 1
2019-12-12 19:24:18 +00:00
continue
postDaysSinceEposh = int ( postFields [ 0 ] )
nickname = postFields [ 1 ]
postId = postFields [ 2 ]
2019-08-10 10:54:52 +00:00
postFilename = locatePost ( baseDir , nickname , domain , postId )
if not postFilename :
2019-12-17 10:06:08 +00:00
index + = 1
2019-08-10 10:54:52 +00:00
continue
2019-10-22 11:55:06 +00:00
postJsonObject = loadJson ( postFilename )
if postJsonObject :
2019-08-10 11:31:42 +00:00
if not isPublicPost ( postJsonObject ) :
2019-12-17 10:06:08 +00:00
index + = 1
2019-12-13 10:33:33 +00:00
continue
showIndividualPostIcons = False
if nickname :
showIndividualPostIcons = True
allowDeletion = False
2019-08-10 11:31:42 +00:00
hashtagSearchForm + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
2019-09-07 08:57:52 +00:00
baseDir , session , wfRequest , personCache , \
2019-08-10 11:31:42 +00:00
nickname , domain , port , postJsonObject , \
2019-12-13 10:33:33 +00:00
None , True , allowDeletion , \
2019-12-13 20:53:13 +00:00
httpPrefix , projectVersion , ' search ' , \
2019-12-17 12:48:39 +00:00
showIndividualPostIcons , \
showIndividualPostIcons , \
False , False , False )
2019-12-17 09:58:22 +00:00
index + = 1
2019-08-10 10:54:52 +00:00
2019-12-17 09:58:22 +00:00
if endIndex < noOfLines - 1 :
2019-08-10 10:54:52 +00:00
# next page link
2019-12-12 18:56:30 +00:00
hashtagSearchForm + = \
' <center><a href= " /tags/ ' + hashtag + ' ?page= ' + str ( pageNumber + 1 ) + \
' " ><img loading= " lazy " class= " pageicon " src= " / ' + iconsDir + \
' /pagedown.png " title= " ' + translate [ ' Page down ' ] + \
' " alt= " ' + translate [ ' Page down ' ] + ' " ></a></center> '
2019-08-10 10:54:52 +00:00
hashtagSearchForm + = htmlFooter ( )
return hashtagSearchForm
2019-09-07 10:08:18 +00:00
def htmlSkillsSearch ( translate : { } , baseDir : str , \
2019-12-10 14:48:08 +00:00
httpPrefix : str , \
2019-09-07 10:08:18 +00:00
skillsearch : str , instanceOnly : bool , \
postsPerPage : int ) - > str :
2019-08-27 22:50:40 +00:00
""" Show a page containing search results for a skill
"""
if skillsearch . startswith ( ' * ' ) :
skillsearch = skillsearch [ 1 : ] . strip ( )
skillsearch = skillsearch . lower ( ) . strip ( ' \n ' )
results = [ ]
# search instance accounts
for subdir , dirs , files in os . walk ( baseDir + ' /accounts/ ' ) :
for f in files :
if not f . endswith ( ' .json ' ) :
continue
if ' @ ' not in f :
continue
if f . startswith ( ' inbox@ ' ) :
continue
actorFilename = os . path . join ( subdir , f )
2019-10-22 11:55:06 +00:00
actorJson = loadJson ( actorFilename )
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 ' ) :
actor = actorJson [ ' id ' ]
2019-08-27 22:50:40 +00:00
for skillName , skillLevel in actorJson [ ' skills ' ] . items ( ) :
2019-08-28 08:41:49 +00:00
skillName = skillName . lower ( )
if skillName in skillsearch or skillsearch in skillName :
2019-08-27 22:50:40 +00:00
skillLevelStr = str ( skillLevel )
if skillLevel < 100 :
skillLevelStr = ' 0 ' + skillLevelStr
if skillLevel < 10 :
skillLevelStr = ' 0 ' + skillLevelStr
2020-02-23 15:32:47 +00:00
indexStr = \
skillLevelStr + ' ; ' + actor + ' ; ' + actorJson [ ' name ' ] + \
' ; ' + actorJson [ ' icon ' ] [ ' url ' ]
2019-08-27 22:50:40 +00:00
if indexStr not in results :
2019-08-28 08:46:52 +00:00
results . append ( indexStr )
2019-08-28 09:59:19 +00:00
if not instanceOnly :
# search actor cache
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
actorFilename = os . path . join ( subdir , f )
2019-10-22 11:55:06 +00:00
cachedActorJson = loadJson ( actorFilename )
if cachedActorJson :
2019-08-28 09:59:19 +00:00
if cachedActorJson . get ( ' actor ' ) :
actorJson = cachedActorJson [ ' actor ' ]
if actorJson . get ( ' id ' ) and \
actorJson . get ( ' skills ' ) and \
actorJson . get ( ' name ' ) and \
actorJson . get ( ' icon ' ) :
actor = actorJson [ ' id ' ]
for skillName , skillLevel in actorJson [ ' skills ' ] . items ( ) :
skillName = skillName . lower ( )
if skillName in skillsearch or skillsearch in skillName :
skillLevelStr = str ( skillLevel )
if skillLevel < 100 :
skillLevelStr = ' 0 ' + skillLevelStr
if skillLevel < 10 :
skillLevelStr = ' 0 ' + skillLevelStr
2020-02-23 15:32:47 +00:00
indexStr = \
skillLevelStr + ' ; ' + actor + ' ; ' + actorJson [ ' name ' ] + \
' ; ' + actorJson [ ' icon ' ] [ ' url ' ]
2019-08-28 09:59:19 +00:00
if indexStr not in results :
results . append ( indexStr )
2019-08-27 22:50:40 +00:00
results . sort ( reverse = True )
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-27 22:50:40 +00:00
skillSearchCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
skillSearchCSS = skillSearchCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-11 09:58:43 +00:00
skillSearchForm = htmlHeader ( cssFilename , skillSearchCSS )
2020-02-23 15:32:47 +00:00
skillSearchForm + = \
' <center><h1> ' + translate [ ' Skills search ' ] + ' : ' + \
skillsearch + ' </h1></center> '
2019-08-27 22:50:40 +00:00
if len ( results ) == 0 :
2019-09-07 10:08:18 +00:00
skillSearchForm + = ' <center><h5> ' + translate [ ' No results ' ] + ' </h5></center> '
2019-08-27 22:50:40 +00:00
else :
skillSearchForm + = ' <center> '
ctr = 0
for skillMatch in results :
skillMatchFields = skillMatch . split ( ' ; ' )
if len ( skillMatchFields ) == 4 :
actor = skillMatchFields [ 1 ]
actorName = skillMatchFields [ 2 ]
avatarUrl = skillMatchFields [ 3 ]
2019-08-28 10:19:48 +00:00
skillSearchForm + = ' <div class= " search-result " " ><a href= " ' + actor + ' /skills " > '
2020-02-23 15:32:47 +00:00
skillSearchForm + = \
' <img loading= " lazy " src= " ' + avatarUrl + \
' " /><span class= " search-result-text " > ' + actorName + ' </span></a></div> '
2019-08-27 22:50:40 +00:00
ctr + = 1
if ctr > = postsPerPage :
break
skillSearchForm + = ' </center> '
skillSearchForm + = htmlFooter ( )
return skillSearchForm
2020-01-14 10:23:17 +00:00
def scheduledPostsExist ( baseDir : str , nickname : str , domain : str ) - > bool :
""" Returns true if there are posts scheduled to be delivered
"""
scheduleIndexFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /schedule.index '
if not os . path . isfile ( scheduleIndexFilename ) :
return False
if ' #users# ' in open ( scheduleIndexFilename ) . read ( ) :
return True
return False
2020-02-23 15:32:47 +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
"""
2019-11-14 13:38:17 +00:00
imageFormats = ' .png, .jpg, .jpeg, .gif, .webp '
2019-08-02 09:52:12 +00:00
pathOriginal = path
path = path . replace ( ' /inbox ' , ' ' ) . replace ( ' /outbox ' , ' ' ) . replace ( ' /shares ' , ' ' )
nickname = getNicknameFromActor ( path )
2019-09-02 09:43:43 +00:00
if not nickname :
return ' '
2019-08-02 09:52:12 +00:00
domainFull = domain
if port :
if port != 80 and port != 443 :
2019-08-16 20:35:11 +00:00
if ' : ' not in domain :
domainFull = domain + ' : ' + str ( port )
2019-08-02 09:52:12 +00:00
actorFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' .json '
if not os . path . isfile ( actorFilename ) :
return ' '
2019-08-07 20:13:44 +00:00
isBot = ' '
2019-10-04 09:23:38 +00:00
isGroup = ' '
2019-10-29 10:49:23 +00:00
followDMs = ' '
2020-02-05 14:57:10 +00:00
removeTwitter = ' '
2019-11-28 17:03:57 +00:00
mediaInstanceStr = ' '
2019-08-22 18:36:07 +00:00
displayNickname = nickname
2019-08-02 09:52:12 +00:00
bioStr = ' '
2019-11-06 22:56:55 +00:00
donateUrl = ' '
2019-12-17 20:44:18 +00:00
emailAddress = ' '
PGPpubKey = ' '
2019-12-17 14:57:16 +00:00
xmppAddress = ' '
2019-12-17 15:25:34 +00:00
matrixAddress = ' '
2019-08-07 11:42:06 +00:00
manuallyApprovesFollowers = ' '
2019-10-22 11:55:06 +00:00
actorJson = loadJson ( actorFilename )
if actorJson :
2019-11-06 22:56:55 +00:00
donateUrl = getDonationUrl ( actorJson )
2019-12-17 14:57:16 +00:00
xmppAddress = getXmppAddress ( actorJson )
2019-12-17 15:25:34 +00:00
matrixAddress = getMatrixAddress ( actorJson )
2019-12-17 20:44:18 +00:00
emailAddress = getEmailAddress ( actorJson )
PGPpubKey = getPGPpubKey ( actorJson )
2019-08-22 18:36:07 +00:00
if actorJson . get ( ' name ' ) :
displayNickname = actorJson [ ' name ' ]
2019-08-02 09:52:12 +00:00
if actorJson . get ( ' summary ' ) :
2019-08-20 21:41:58 +00:00
bioStr = actorJson [ ' summary ' ] . replace ( ' <p> ' , ' ' ) . replace ( ' </p> ' , ' ' )
2019-08-02 09:52:12 +00:00
if actorJson . get ( ' manuallyApprovesFollowers ' ) :
if actorJson [ ' manuallyApprovesFollowers ' ] :
manuallyApprovesFollowers = ' checked '
else :
manuallyApprovesFollowers = ' '
2019-08-07 20:13:44 +00:00
if actorJson . get ( ' type ' ) :
if actorJson [ ' type ' ] == ' Service ' :
isBot = ' checked '
2019-10-04 09:23:38 +00:00
isGroup = ' '
elif actorJson [ ' type ' ] == ' Group ' :
isGroup = ' checked '
isBot = ' '
2019-10-29 10:53:52 +00:00
if os . path . isfile ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /.followDMs ' ) :
2019-10-29 10:49:23 +00:00
followDMs = ' checked '
2020-02-05 14:57:10 +00:00
if os . path . isfile ( baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /.removeTwitter ' ) :
removeTwitter = ' checked '
2019-11-28 17:03:57 +00:00
mediaInstance = getConfigParam ( baseDir , " mediaInstance " )
if mediaInstance :
if mediaInstance == True :
mediaInstanceStr = ' checked '
2019-08-07 20:13:44 +00:00
2019-08-02 11:43:14 +00:00
filterStr = ' '
filterFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /filters.txt '
if os . path . isfile ( filterFilename ) :
with open ( filterFilename , ' r ' ) as filterfile :
filterStr = filterfile . read ( )
2020-02-19 19:06:23 +00:00
switchStr = ' '
switchFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /replacewords.txt '
if os . path . isfile ( switchFilename ) :
with open ( switchFilename , ' r ' ) as switchfile :
switchStr = switchfile . read ( )
2019-08-02 11:43:14 +00:00
blockedStr = ' '
blockedFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /blocking.txt '
if os . path . isfile ( blockedFilename ) :
with open ( blockedFilename , ' r ' ) as blockedfile :
blockedStr = blockedfile . read ( )
2019-08-02 12:11:32 +00:00
allowedInstancesStr = ' '
allowedInstancesFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /allowedinstances.txt '
if os . path . isfile ( allowedInstancesFilename ) :
with open ( allowedInstancesFilename , ' r ' ) as allowedInstancesFile :
allowedInstancesStr = allowedInstancesFile . read ( )
2019-08-09 08:46:38 +00:00
skills = getSkills ( baseDir , nickname , domain )
skillsStr = ' '
skillCtr = 1
if skills :
for skillDesc , skillValue in skills . items ( ) :
2020-02-23 15:32:47 +00:00
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> '
2019-08-09 08:46:38 +00:00
skillCtr + = 1
2020-02-23 15:32:47 +00:00
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> ' \
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-13 10:48:16 +00:00
editProfileCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
editProfileCSS = editProfileCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-08-02 09:52:12 +00:00
2019-11-13 20:50:23 +00:00
instanceStr = ' '
2019-08-12 21:20:47 +00:00
moderatorsStr = ' '
2019-11-23 14:13:25 +00:00
themesDropdown = ' '
2019-08-12 21:20:47 +00:00
adminNickname = getConfigParam ( baseDir , ' admin ' )
if path . startswith ( ' /users/ ' + adminNickname + ' / ' ) :
2019-11-13 20:50:23 +00:00
instanceDescription = getConfigParam ( baseDir , ' instanceDescription ' )
instanceDescriptionShort = getConfigParam ( baseDir , ' instanceDescriptionShort ' )
instanceTitle = getConfigParam ( baseDir , ' instanceTitle ' )
instanceStr = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
instanceStr + = \
' <label class= " labels " > ' + translate [ ' Instance Title ' ] + ' </label> '
instanceStr + = \
' <input type= " text " name= " instanceTitle " value= " ' + instanceTitle + ' " ><br> '
instanceStr + = \
' <label class= " labels " > ' + translate [ ' Instance Short Description ' ] + ' </label> '
instanceStr + = \
' <input type= " text " name= " instanceDescriptionShort " value= " ' + \
instanceDescriptionShort + ' " ><br> '
instanceStr + = \
' <label class= " labels " > ' + translate [ ' Instance Description ' ] + ' </label> '
instanceStr + = \
' <textarea id= " message " name= " instanceDescription " style= " height:200px " > ' + \
instanceDescription + ' </textarea> '
instanceStr + = \
' <label class= " labels " > ' + translate [ ' Instance Logo ' ] + ' </label> '
instanceStr + = \
' <input type= " file " id= " instanceLogo " name= " instanceLogo " '
2019-11-14 13:30:54 +00:00
instanceStr + = ' accept= " ' + imageFormats + ' " > '
2019-11-13 20:50:23 +00:00
instanceStr + = ' </div> '
2019-08-12 21:20:47 +00:00
moderators = ' '
moderatorsFile = baseDir + ' /accounts/moderators.txt '
if os . path . isfile ( moderatorsFile ) :
with open ( moderatorsFile , " r " ) as f :
moderators = f . read ( )
2019-11-12 10:42:27 +00:00
moderatorsStr = ' <div class= " container " > '
moderatorsStr + = ' <b> ' + translate [ ' Moderators ' ] + ' </b><br> '
moderatorsStr + = ' ' + translate [ ' A list of moderator nicknames. One per line. ' ]
2020-02-23 15:32:47 +00:00
moderatorsStr + = \
' <textarea id= " message " name= " moderators " placeholder= " ' + \
translate [ ' List of moderator nicknames ' ] + \
' ... " style= " height:200px " > ' + moderators + ' </textarea> '
2019-11-12 10:42:27 +00:00
moderatorsStr + = ' </div> '
2019-11-23 14:13:25 +00:00
themesDropdown = ' <div class= " container " > '
themesDropdown + = ' <b> ' + translate [ ' Theme ' ] + ' </b><br> '
2019-11-23 14:27:07 +00:00
themesDropdown + = ' <select id= " themeDropdown " name= " themeDropdown " class= " theme " > '
2019-11-23 14:13:25 +00:00
themesDropdown + = ' <option value= " default " > ' + translate [ ' Default ' ] + ' </option> '
themesDropdown + = ' <option value= " light " > ' + translate [ ' Light ' ] + ' </option> '
themesDropdown + = ' <option value= " purple " > ' + translate [ ' Purple ' ] + ' </option> '
themesDropdown + = ' <option value= " hacker " > ' + translate [ ' Hacker ' ] + ' </option> '
themesDropdown + = ' <option value= " highvis " > ' + translate [ ' HighVis ' ] + ' </option> '
2019-11-27 21:40:29 +00:00
themesDropdown + = ' </select><br> '
2019-11-23 14:13:25 +00:00
themesDropdown + = ' </div> '
2019-11-23 14:30:12 +00:00
themeName = getConfigParam ( baseDir , ' theme ' )
2020-02-23 15:32:47 +00:00
themesDropdown = \
themesDropdown . replace ( ' <option value= " ' + themeName + ' " > ' , \
' <option value= " ' + themeName + ' " selected> ' )
2019-11-23 14:13:25 +00:00
2019-09-11 09:58:43 +00:00
editProfileForm = htmlHeader ( cssFilename , editProfileCSS )
2020-02-23 15:32:47 +00:00
editProfileForm + = \
' <form enctype= " multipart/form-data " method= " POST " accept-charset= " UTF-8 " action= " ' + path + ' /profiledata " > '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <div class= " vertical-center " > '
2020-02-23 15:32:47 +00:00
editProfileForm + = \
' <p class= " new-post-text " > ' + translate [ ' Profile for ' ] + \
' ' + nickname + ' @ ' + domainFull + ' </p> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
editProfileForm + = \
' <input type= " submit " name= " submitProfile " value= " ' + \
translate [ ' Submit ' ] + ' " > '
editProfileForm + = \
' <a href= " ' + pathOriginal + ' " ><button class= " cancelbtn " > ' + \
translate [ ' Cancel ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' </div> '
2020-01-14 10:23:17 +00:00
if scheduledPostsExist ( baseDir , nickname , domain ) :
editProfileForm + = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
editProfileForm + = \
' <input type= " checkbox " class= " profilecheckbox " name= " removeScheduledPosts " > ' + \
translate [ ' Remove scheduled posts ' ] + ' <br> '
2020-01-14 10:23:17 +00:00
editProfileForm + = ' </div> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <div class= " container " > '
2019-11-27 21:40:29 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Nickname ' ] + ' </label> '
2019-11-27 22:02:10 +00:00
editProfileForm + = ' <input type= " text " name= " displayNickname " value= " ' + displayNickname + ' " ><br> '
2019-11-27 21:40:29 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Your bio ' ] + ' </label> '
editProfileForm + = ' <textarea id= " message " name= " bio " style= " height:200px " > ' + bioStr + ' </textarea> '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Donations link ' ] + ' </label><br> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <input type= " text " placeholder= " https://... " name= " donateUrl " value= " ' + donateUrl + ' " > '
2019-12-17 20:52:21 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' XMPP ' ] + ' </label><br> '
2019-12-17 14:57:16 +00:00
editProfileForm + = ' <input type= " text " name= " xmppAddress " value= " ' + xmppAddress + ' " > '
2019-12-17 20:52:21 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Matrix ' ] + ' </label><br> '
2019-12-17 15:25:34 +00:00
editProfileForm + = ' <input type= " text " name= " matrixAddress " value= " ' + matrixAddress + ' " > '
2019-12-17 20:52:21 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Email ' ] + ' </label><br> '
2019-12-17 20:44:18 +00:00
editProfileForm + = ' <input type= " text " name= " email " value= " ' + emailAddress + ' " > '
2019-12-17 20:52:21 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' PGP ' ] + ' </label><br> '
2019-12-17 23:06:11 +00:00
editProfileForm + = ' <textarea id= " message " placeholder= " -----BEGIN PGP PUBLIC KEY BLOCK----- " name= " pgp " style= " height:100px " > ' + PGPpubKey + ' </textarea> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' </div> '
editProfileForm + = ' <div class= " container " > '
2019-11-27 21:51:55 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' The files attached below should be no larger than 10MB in total uploaded at once. ' ] + ' </label><br><br> '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Avatar image ' ] + ' </label> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <input type= " file " id= " avatar " name= " avatar " '
2019-11-14 13:30:54 +00:00
editProfileForm + = ' accept= " ' + imageFormats + ' " > '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <br><label class= " labels " > ' + translate [ ' Background image ' ] + ' </label> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <input type= " file " id= " image " name= " image " '
2019-11-14 13:30:54 +00:00
editProfileForm + = ' accept= " ' + imageFormats + ' " > '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <br><label class= " labels " > ' + translate [ ' Timeline banner image ' ] + ' </label> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <input type= " file " id= " banner " name= " banner " '
2019-11-14 13:30:54 +00:00
editProfileForm + = ' accept= " ' + imageFormats + ' " > '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' </div> '
editProfileForm + = ' <div class= " container " > '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Change Password ' ] + ' </label><br> '
2019-11-27 21:40:29 +00:00
editProfileForm + = ' <input type= " text " name= " password " value= " " ><br> '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <label class= " labels " > ' + translate [ ' Confirm Password ' ] + ' </label><br> '
2019-11-12 10:59:17 +00:00
editProfileForm + = ' <input type= " text " name= " passwordconfirm " value= " " > '
editProfileForm + = ' </div> '
editProfileForm + = ' <div class= " container " > '
2020-01-12 10:28:33 +00:00
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " approveFollowers " ' + manuallyApprovesFollowers + ' > ' + translate [ ' Approve follower requests ' ] + ' <br> '
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " isBot " ' + isBot + ' > ' + translate [ ' This is a bot account ' ] + ' <br> '
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " isGroup " ' + isGroup + ' > ' + translate [ ' This is a group account ' ] + ' <br> '
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " followDMs " ' + followDMs + ' > ' + translate [ ' Only people I follow can send me DMs ' ] + ' <br> '
2020-02-05 14:57:10 +00:00
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " removeTwitter " ' + removeTwitter + ' > ' + translate [ ' Remove Twitter posts ' ] + ' <br> '
2019-11-28 17:03:57 +00:00
if path . startswith ( ' /users/ ' + adminNickname + ' / ' ) :
2020-01-12 10:28:33 +00:00
editProfileForm + = ' <input type= " checkbox " class= " profilecheckbox " name= " mediaInstance " ' + mediaInstanceStr + ' > ' + translate [ ' This is a media instance ' ] + ' <br> '
2020-02-19 19:06:23 +00:00
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <br><b><label class= " labels " > ' + translate [ ' Filtered words ' ] + ' </label></b> '
editProfileForm + = ' <br><label class= " labels " > ' + translate [ ' One per line ' ] + ' </label> '
2019-11-27 12:04:06 +00:00
editProfileForm + = ' <textarea id= " message " name= " filteredWords " style= " height:200px " > ' + filterStr + ' </textarea> '
2020-02-19 19:06:23 +00:00
editProfileForm + = ' <br><b><label class= " labels " > ' + translate [ ' Word Replacements ' ] + ' </label></b> '
editProfileForm + = ' <br><label class= " labels " >A -> B</label> '
editProfileForm + = ' <textarea id= " message " name= " switchWords " style= " height:200px " > ' + switchStr + ' </textarea> '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <br><b><label class= " labels " > ' + translate [ ' Blocked accounts ' ] + ' </label></b> '
editProfileForm + = ' <br><label class= " labels " > ' + translate [ ' Blocked accounts, one per line, in the form nickname@domain or *@blockeddomain ' ] + ' </label> '
2019-11-27 12:04:06 +00:00
editProfileForm + = ' <textarea id= " message " name= " blocked " style= " height:200px " > ' + blockedStr + ' </textarea> '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <br><b><label class= " labels " > ' + translate [ ' Federation list ' ] + ' </label></b> '
editProfileForm + = ' <br><label class= " labels " > ' + translate [ ' Federate only with a defined set of instances. One domain name per line. ' ] + ' </label> '
2019-11-27 12:04:06 +00:00
editProfileForm + = ' <textarea id= " message " name= " allowedInstances " style= " height:200px " > ' + allowedInstancesStr + ' </textarea> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' </div> '
editProfileForm + = ' <div class= " container " > '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <b><label class= " labels " > ' + translate [ ' Skills ' ] + ' </label></b><br> '
editProfileForm + = ' <label class= " labels " > ' + translate [ ' 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. ' ] + ' </label> '
2019-11-23 14:13:25 +00:00
editProfileForm + = skillsStr + themesDropdown + moderatorsStr
2019-11-13 20:50:23 +00:00
editProfileForm + = ' </div> ' + instanceStr
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <div class= " container " > '
2019-11-27 21:50:22 +00:00
editProfileForm + = ' <b><label class= " labels " > ' + translate [ ' Danger Zone ' ] + ' </label></b><br> '
2019-11-12 10:42:27 +00:00
editProfileForm + = ' <input type= " checkbox " class=dangercheckbox " name= " deactivateThisAccount " > ' + translate [ ' Deactivate this account ' ] + ' <br> '
editProfileForm + = ' </div> '
editProfileForm + = ' </div> '
editProfileForm + = ' </form> '
2019-08-02 09:52:12 +00:00
editProfileForm + = htmlFooter ( )
return editProfileForm
2019-08-08 13:38:33 +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= ' ) :
2019-08-08 13:38:33 +00:00
return None , None , None
2019-07-24 22:38:42 +00:00
# minimum time between login attempts
currTime = int ( time . time ( ) )
2019-08-08 13:38:33 +00:00
if currTime < lastLoginTime + 10 :
return None , None , None
2019-07-24 22:38:42 +00:00
if ' & ' not in loginParams :
2019-08-08 13:38:33 +00:00
return None , None , None
2019-07-24 22:38:42 +00:00
loginArgs = loginParams . split ( ' & ' )
nickname = None
password = None
2019-08-08 13:38:33 +00:00
register = False
2019-07-24 22:38:42 +00:00
for arg in loginArgs :
if ' = ' in arg :
2019-07-25 10:56:24 +00:00
if arg . split ( ' = ' , 1 ) [ 0 ] == ' username ' :
2019-07-24 22:38:42 +00:00
nickname = arg . split ( ' = ' , 1 ) [ 1 ]
elif arg . split ( ' = ' , 1 ) [ 0 ] == ' password ' :
password = arg . split ( ' = ' , 1 ) [ 1 ]
2019-08-08 13:38:33 +00:00
elif arg . split ( ' = ' , 1 ) [ 0 ] == ' register ' :
register = True
return nickname , password , register
2019-07-24 22:38:42 +00:00
2019-10-30 12:22:59 +00:00
def htmlLogin ( translate : { } , baseDir : str , autocomplete = True ) - > str :
2019-08-10 18:22:28 +00:00
""" Shows the login screen
"""
2019-08-08 11:24:26 +00:00
accounts = noOfAccounts ( baseDir )
2019-11-14 13:30:54 +00:00
loginImage = ' login.png '
loginImageFilename = None
2019-11-14 13:50:25 +00:00
if os . path . isfile ( baseDir + ' /accounts/ ' + loginImage ) :
2019-11-14 13:30:54 +00:00
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
2019-11-14 13:50:25 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.jpg ' ) :
2019-11-14 13:30:54 +00:00
loginImage = ' login.jpg '
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
2019-11-14 13:50:25 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.jpeg ' ) :
2019-11-14 13:30:54 +00:00
loginImage = ' login.jpeg '
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
2019-11-14 13:50:25 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.gif ' ) :
2019-11-14 13:30:54 +00:00
loginImage = ' login.gif '
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
2019-11-14 13:50:25 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.webp ' ) :
2019-11-14 13:30:54 +00:00
loginImage = ' login.webp '
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
if not loginImageFilename :
loginImageFilename = baseDir + ' /accounts/ ' + loginImage
copyfile ( baseDir + ' /img/login.png ' , loginImageFilename )
2019-07-25 19:56:25 +00:00
if os . path . isfile ( baseDir + ' /img/login-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/login-background.png ' ) :
2020-02-23 15:32:47 +00:00
copyfile ( baseDir + ' /img/login-background.png ' , \
baseDir + ' /accounts/login-background.png ' )
2019-07-25 19:22:19 +00:00
2019-08-08 11:24:26 +00:00
if accounts > 0 :
2020-02-23 15:32:47 +00:00
loginText = \
' <p class= " login-text " > ' + \
translate [ ' Welcome. Please enter your login details below. ' ] + ' </p> '
2019-08-08 11:24:26 +00:00
else :
2020-02-23 15:32:47 +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> '
2019-07-25 19:22:19 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.txt ' ) :
2019-08-10 18:22:28 +00:00
# custom login message
2019-07-25 19:22:19 +00:00
with open ( baseDir + ' /accounts/login.txt ' , ' r ' ) as file :
2019-07-25 19:56:25 +00:00
loginText = ' <p class= " login-text " > ' + file . read ( ) + ' </p> '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-login.css '
if os . path . isfile ( baseDir + ' /login.css ' ) :
cssFilename = baseDir + ' /login.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-25 19:56:25 +00:00
loginCSS = cssFile . read ( )
2019-08-08 11:24:26 +00:00
# show the register button
registerButtonStr = ' '
if getConfigParam ( baseDir , ' registration ' ) == ' open ' :
if int ( getConfigParam ( baseDir , ' registrationsRemaining ' ) ) > 0 :
2019-08-08 13:38:33 +00:00
if accounts > 0 :
2020-02-23 15:32:47 +00:00
loginText = \
' <p class= " login-text " > ' + \
translate [ ' Welcome. Please login or register a new account. ' ] + ' </p> '
registerButtonStr = \
' <button type= " submit " name= " register " >Register</button> '
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
2019-08-08 11:24:26 +00:00
loginButtonStr = ' '
if accounts > 0 :
2020-02-23 15:32:47 +00:00
loginButtonStr = \
' <button type= " submit " name= " submit " > ' + \
translate [ ' Login ' ] + ' </button> '
2019-10-30 12:22:59 +00:00
autocompleteStr = ' '
if not autocomplete :
2019-10-30 12:29:16 +00:00
autocompleteStr = ' autocomplete= " off " value= " " '
2019-10-30 12:22:59 +00:00
2019-09-11 09:58:43 +00:00
loginForm = htmlHeader ( cssFilename , loginCSS )
2019-11-12 10:42:27 +00:00
loginForm + = ' <form method= " POST " action= " /login " > '
loginForm + = ' <div class= " imgcontainer " > '
2020-02-23 15:32:47 +00:00
loginForm + = \
' <img loading= " lazy " src= " ' + loginImage + \
' " alt= " login image " class= " loginimage " > '
2019-11-12 10:42:27 +00:00
loginForm + = loginText + TOSstr
loginForm + = ' </div> '
loginForm + = ' '
loginForm + = ' <div class= " container " > '
loginForm + = ' <label for= " nickname " ><b> ' + translate [ ' Nickname ' ] + ' </b></label> '
2020-02-23 15:32:47 +00:00
loginForm + = \
' <input type= " text " ' + autocompleteStr + ' placeholder= " ' + \
translate [ ' Enter Nickname ' ] + ' " name= " username " required autofocus> '
2019-11-12 10:42:27 +00:00
loginForm + = ' '
loginForm + = ' <label for= " password " ><b> ' + translate [ ' Password ' ] + ' </b></label> '
2020-02-23 15:32:47 +00:00
loginForm + = \
' <input type= " password " ' + autocompleteStr + \
' placeholder= " ' + translate [ ' Enter Password ' ] + ' " name= " password " required> '
2019-11-12 10:42:27 +00:00
loginForm + = registerButtonStr + loginButtonStr
loginForm + = ' </div> '
loginForm + = ' </form> '
2020-02-23 15:32:47 +00:00
loginForm + = \
' <a href= " https://gitlab.com/bashrc2/epicyon " ><img loading= " lazy " class= " license " title= " ' + \
translate [ ' Get the source code ' ] + ' " alt= " ' + \
translate [ ' Get the source code ' ] + ' " src= " /icons/agpl.png " /></a> '
2019-07-24 22:38:42 +00:00
loginForm + = htmlFooter ( )
return loginForm
2019-08-10 15:33:18 +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
"""
2019-08-10 15:33:18 +00:00
adminNickname = getConfigParam ( baseDir , ' admin ' )
2019-08-08 13:38:33 +00:00
if not os . path . isfile ( baseDir + ' /accounts/tos.txt ' ) :
copyfile ( baseDir + ' /default_tos.txt ' , baseDir + ' /accounts/tos.txt ' )
if os . path . isfile ( baseDir + ' /img/login-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/login-background.png ' ) :
2020-02-23 15:32:47 +00:00
copyfile ( baseDir + ' /img/login-background.png ' , \
baseDir + ' /accounts/login-background.png ' )
2019-08-08 13:38:33 +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 = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-08 13:38:33 +00:00
termsCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
termsCSS = termsCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-08-08 13:38:33 +00:00
2019-09-11 09:58:43 +00:00
TOSForm = htmlHeader ( cssFilename , termsCSS )
2019-08-08 13:38:33 +00:00
TOSForm + = ' <div class= " container " > ' + TOSText + ' </div> '
2019-08-10 15:33:18 +00:00
if adminNickname :
adminActor = httpPrefix + ' :// ' + domainFull + ' /users/ ' + adminNickname
2020-02-23 15:32:47 +00:00
TOSForm + = \
' <div class= " container " ><center><p class= " administeredby " >Administered by <a href= " ' + \
adminActor + ' " > ' + adminNickname + ' </a></p></center></div> '
2019-08-08 13:38:33 +00:00
TOSForm + = htmlFooter ( )
return TOSForm
2019-08-26 16:02:47 +00:00
def htmlAbout ( baseDir : str , httpPrefix : str , domainFull : str ) - > str :
""" Show the about screen
"""
adminNickname = getConfigParam ( baseDir , ' admin ' )
if not os . path . isfile ( baseDir + ' /accounts/about.txt ' ) :
copyfile ( baseDir + ' /default_about.txt ' , baseDir + ' /accounts/about.txt ' )
if os . path . isfile ( baseDir + ' /img/login-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/login-background.png ' ) :
2020-02-23 15:32:47 +00:00
copyfile ( baseDir + ' /img/login-background.png ' , \
baseDir + ' /accounts/login-background.png ' )
2019-08-26 16:02:47 +00:00
aboutText = ' Information about this instance goes here. '
if os . path . isfile ( baseDir + ' /accounts/about.txt ' ) :
with open ( baseDir + ' /accounts/about.txt ' , ' r ' ) as file :
aboutText = file . read ( )
aboutForm = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-26 16:02:47 +00:00
termsCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' http ' :
termsCSS = termsCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-11 09:58:43 +00:00
aboutForm = htmlHeader ( cssFilename , termsCSS )
2019-08-26 16:02:47 +00:00
aboutForm + = ' <div class= " container " > ' + aboutText + ' </div> '
if adminNickname :
adminActor = httpPrefix + ' :// ' + domainFull + ' /users/ ' + adminNickname
2020-02-23 15:32:47 +00:00
aboutForm + = \
' <div class= " container " ><center><p class= " administeredby " >Administered by <a href= " ' + \
adminActor + ' " > ' + adminNickname + ' </a></p></center></div> '
2019-08-26 16:02:47 +00:00
aboutForm + = htmlFooter ( )
return aboutForm
2019-08-14 10:32:15 +00:00
def htmlHashtagBlocked ( baseDir : str ) - > str :
""" Show the screen for a blocked hashtag
"""
blockedHashtagForm = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-suspended.css '
if os . path . isfile ( baseDir + ' /suspended.css ' ) :
cssFilename = baseDir + ' /suspended.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-14 10:32:15 +00:00
blockedHashtagCSS = cssFile . read ( )
2019-09-11 09:58:43 +00:00
blockedHashtagForm = htmlHeader ( cssFilename , blockedHashtagCSS )
2019-08-14 10:32:15 +00:00
blockedHashtagForm + = ' <div><center> '
blockedHashtagForm + = ' <p class= " screentitle " >Hashtag Blocked</p> '
blockedHashtagForm + = ' <p>See <a href= " /terms " >Terms of Service</a></p> '
blockedHashtagForm + = ' </center></div> '
blockedHashtagForm + = htmlFooter ( )
return blockedHashtagForm
2019-08-13 09:24:55 +00:00
def htmlSuspended ( baseDir : str ) - > str :
""" Show the screen for suspended accounts
"""
suspendedForm = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-suspended.css '
if os . path . isfile ( baseDir + ' /suspended.css ' ) :
cssFilename = baseDir + ' /suspended.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-13 09:24:55 +00:00
suspendedCSS = cssFile . read ( )
2019-09-11 09:58:43 +00:00
suspendedForm = htmlHeader ( cssFilename , suspendedCSS )
2019-08-13 09:24:55 +00:00
suspendedForm + = ' <div><center> '
suspendedForm + = ' <p class= " screentitle " >Account Suspended</p> '
suspendedForm + = ' <p>See <a href= " /terms " >Terms of Service</a></p> '
suspendedForm + = ' </center></div> '
suspendedForm + = htmlFooter ( )
return suspendedForm
2019-12-10 16:05:07 +00:00
def htmlNewPost ( mediaInstance : bool , translate : { } , \
baseDir : str , httpPrefix : str , \
2019-09-07 08:57:52 +00:00
path : str , inReplyTo : str , \
mentions : [ ] , \
2019-12-10 17:19:53 +00:00
reportUrl : str , pageNumber : int , \
nickname : str , domain : str ) - > str :
2019-08-19 19:50:07 +00:00
""" New post screen
2019-12-10 17:19:53 +00:00
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-07-31 13:51:10 +00:00
replyStr = ' '
2019-12-10 17:19:53 +00:00
showPublicOnDropdown = True
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 :
2019-09-07 08:57:52 +00:00
newPostText = ' <p class= " new-post-text " > ' + translate [ ' Write your post text below. ' ] + ' </p> '
2019-08-11 11:25:27 +00:00
else :
2020-02-23 15:32:47 +00:00
newPostText = \
' <p class= " new-post-text " > ' + translate [ ' Write your reply to ' ] + \
' <a href= " ' + inReplyTo + ' " > ' + translate [ ' this post ' ] + ' </a></p> '
2019-08-11 11:25:27 +00:00
replyStr = ' <input type= " hidden " name= " replyTo " value= " ' + inReplyTo + ' " > '
2019-12-10 17:19:53 +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 :
newPostPath = newPostPath . split ( ' ? ' ) [ 0 ]
if newPostPath . endswith ( ' /newpost ' ) :
path = path . replace ( ' /newpost ' , ' /newfollowers ' )
elif newPostPath . endswith ( ' /newunlisted ' ) :
path = path . replace ( ' /newunlisted ' , ' /newfollowers ' )
showPublicOnDropdown = False
2019-07-31 13:51:10 +00:00
else :
2019-08-11 11:25:27 +00:00
newPostText = \
2019-09-07 08:57:52 +00:00
' <p class= " new-post-text " > ' + translate [ ' Write your report below. ' ] + ' </p> '
2019-08-11 14:14:12 +00:00
# custom report header with any additional instructions
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-02-23 15:32:47 +00:00
customReportText = \
' <p class= " login-subtext " > ' + customReportText + ' </p> '
customReportText = \
customReportText . replace ( ' <p> ' , ' <p class= " login-subtext " > ' )
2019-08-11 14:14:12 +00:00
newPostText + = customReportText
2020-02-23 15:32:47 +00:00
newPostText + = \
' <p class= " new-post-subtext " > ' + \
translate [ ' This message only goes to moderators, even if it mentions other fediverse addresses. ' ] + \
' </p><p class= " new-post-subtext " > ' + translate [ ' Also see ' ] + \
' <a href= " /terms " > ' + translate [ ' Terms of Service ' ] + ' </a></p> '
2019-07-28 11:35:57 +00:00
else :
2020-02-23 15:32:47 +00:00
newPostText = \
' <p class= " new-post-text " > ' + \
translate [ ' Enter the details for your shared item below. ' ] + ' </p> '
2019-11-25 20:46:52 +00:00
2019-11-28 12:38:37 +00:00
if path . endswith ( ' /newquestion ' ) :
2020-02-23 15:32:47 +00:00
newPostText = \
' <p class= " new-post-text " > ' + \
translate [ ' Enter the choices for your question below. ' ] + ' </p> '
2019-11-25 20:46:52 +00:00
2019-07-25 21:39:09 +00:00
if os . path . isfile ( baseDir + ' /accounts/newpost.txt ' ) :
with open ( baseDir + ' /accounts/newpost.txt ' , ' r ' ) as file :
newPostText = ' <p class= " new-post-text " > ' + file . read ( ) + ' </p> '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-25 21:39:09 +00:00
newPostCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
newPostCSS = newPostCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-07-25 21:39:09 +00:00
2019-08-25 10:02:28 +00:00
if ' ? ' in path :
path = path . split ( ' ? ' ) [ 0 ]
2020-02-23 15:32:47 +00:00
pathBase = \
2020-02-24 13:32:19 +00:00
path . replace ( ' /newreport ' , ' ' ) . replace ( ' /newpost ' , ' ' ) . replace ( ' /newblog ' , ' ' ) . replace ( ' /newshare ' , ' ' ) . replace ( ' /newunlisted ' , ' ' ) . replace ( ' /newfollowers ' , ' ' ) . replace ( ' /newdm ' , ' ' )
2019-07-26 12:26:41 +00:00
2019-11-28 17:45:01 +00:00
newPostImageSection = ' <div class= " container " > '
newPostImageSection + = ' <label class= " labels " > ' + translate [ ' Image description ' ] + ' </label> '
newPostImageSection + = ' <input type= " text " name= " imageDescription " > '
newPostImageSection + = ' <input type= " file " id= " attachpic " name= " attachpic " '
newPostImageSection + = ' accept= " .png, .jpg, .jpeg, .gif, .webp, .mp4, .webm, .ogv, .mp3, .ogg " > '
newPostImageSection + = ' </div> '
2019-07-26 12:26:41 +00:00
scopeIcon = ' scope_public.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' Public ' ]
2019-09-07 08:57:52 +00:00
placeholderSubject = translate [ ' Subject or Content Warning (optional) ' ] + ' ... '
placeholderMessage = translate [ ' Write something ' ] + ' ... '
2019-07-26 12:59:30 +00:00
extraFields = ' '
2019-07-27 20:30:58 +00:00
endpoint = ' newpost '
2020-02-24 13:32:19 +00:00
if path . endswith ( ' /newblog ' ) :
2020-02-24 15:18:36 +00:00
placeholderSubject = translate [ ' Title ' ]
2020-02-24 13:32:19 +00:00
scopeIcon = ' scope_blog.png '
scopeDescription = translate [ ' Blog ' ]
endpoint = ' newblog '
elif path . endswith ( ' /newunlisted ' ) :
2019-07-26 12:26:41 +00:00
scopeIcon = ' scope_unlisted.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' Unlisted ' ]
2019-07-27 20:30:58 +00:00
endpoint = ' newunlisted '
2020-02-24 13:32:19 +00:00
elif path . endswith ( ' /newfollowers ' ) :
2019-07-26 12:26:41 +00:00
scopeIcon = ' scope_followers.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' Followers ' ]
2019-07-27 20:30:58 +00:00
endpoint = ' newfollowers '
2020-02-24 13:32:19 +00:00
elif path . endswith ( ' /newdm ' ) :
2019-07-26 12:26:41 +00:00
scopeIcon = ' scope_dm.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' DM ' ]
2019-07-27 20:30:58 +00:00
endpoint = ' newdm '
2020-02-24 13:32:19 +00:00
elif path . endswith ( ' /newreport ' ) :
2019-08-11 11:25:27 +00:00
scopeIcon = ' scope_report.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' Report ' ]
2019-08-11 11:25:27 +00:00
endpoint = ' newreport '
2020-02-24 13:32:19 +00:00
elif path . endswith ( ' /newquestion ' ) :
2019-11-25 20:46:52 +00:00
scopeIcon = ' scope_question.png '
scopeDescription = translate [ ' Question ' ]
placeholderMessage = translate [ ' Enter your question ' ] + ' ... '
endpoint = ' newquestion '
extraFields = ' <div class= " container " > '
2019-11-27 12:31:25 +00:00
extraFields + = ' <label class= " labels " > ' + translate [ ' Possible answers ' ] + ' :</label><br> '
2019-11-26 12:17:52 +00:00
for questionCtr in range ( 8 ) :
2020-02-23 15:32:47 +00:00
extraFields + = \
' <input type= " text " class= " questionOption " placeholder= " ' + \
str ( questionCtr + 1 ) + ' " name= " questionOption ' + str ( questionCtr ) + ' " ><br> '
extraFields + = \
' <label class= " labels " > ' + translate [ ' Duration of listing in days ' ] + \
' :</label> <input type= " number " name= " duration " min= " 1 " max= " 365 " step= " 1 " value= " 14 " ><br> '
2019-11-25 20:46:52 +00:00
extraFields + = ' </div> '
2020-02-24 13:32:19 +00:00
elif path . endswith ( ' /newshare ' ) :
2019-07-26 12:26:41 +00:00
scopeIcon = ' scope_share.png '
2019-09-07 11:19:44 +00:00
scopeDescription = translate [ ' Shared Item ' ]
2019-09-07 08:57:52 +00:00
placeholderSubject = translate [ ' Name of the shared item ' ] + ' ... '
placeholderMessage = translate [ ' Description of the item being shared ' ] + ' ... '
2019-11-26 12:13:57 +00:00
endpoint = ' newshare '
2019-11-12 10:42:27 +00:00
extraFields = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
extraFields + = \
' <label class= " labels " > ' + \
translate [ ' Type of shared item. eg. hat ' ] + ' :</label> '
2019-11-27 12:04:06 +00:00
extraFields + = ' <input type= " text " class= " itemType " name= " itemType " > '
2020-02-23 15:32:47 +00:00
extraFields + = \
' <br><label class= " labels " > ' + \
translate [ ' Category of shared item. eg. clothing ' ] + ' :</label> '
2019-11-27 12:04:06 +00:00
extraFields + = ' <input type= " text " class= " category " name= " category " > '
2020-02-23 15:32:47 +00:00
extraFields + = \
' <br><label class= " labels " > ' + \
translate [ ' Duration of listing in days ' ] + ' :</label> '
2019-11-27 12:04:06 +00:00
extraFields + = ' <input type= " number " name= " duration " min= " 1 " max= " 365 " step= " 1 " value= " 14 " > '
2019-11-12 10:42:27 +00:00
extraFields + = ' </div> '
2019-11-27 12:26:43 +00:00
extraFields + = ' <div class= " container " > '
2020-02-23 15:32:47 +00:00
extraFields + = \
' <label class= " labels " > ' + \
translate [ ' City or location of the shared item ' ] + ' :</label> '
2019-11-27 12:04:06 +00:00
extraFields + = ' <input type= " text " name= " location " > '
2019-11-27 12:25:19 +00:00
extraFields + = ' </div> '
2019-07-26 12:59:30 +00:00
2019-10-10 10:26:33 +00:00
dateAndLocation = ' '
2019-11-26 12:13:57 +00:00
if endpoint != ' newshare ' and endpoint != ' newreport ' and endpoint != ' newquestion ' :
2019-11-12 10:42:27 +00:00
dateAndLocation = ' <div class= " container " > '
2020-01-12 13:05:36 +00:00
if not inReplyTo :
2020-02-23 15:32:47 +00:00
dateAndLocation + = \
' <p><input type= " checkbox " class= " profilecheckbox " name= " schedulePost " ><label class= " labels " > ' + \
translate [ ' This is a scheduled post. ' ] + ' </label></p> '
2020-01-12 13:05:36 +00:00
2020-02-23 15:32:47 +00:00
dateAndLocation + = \
' <p><img loading= " lazy " alt= " " title= " " class= " emojicalendar " src= " / ' + \
iconsDir + ' /calendar.png " /> '
2019-11-12 10:42:27 +00:00
dateAndLocation + = ' <label class= " labels " > ' + translate [ ' Date ' ] + ' : </label> '
dateAndLocation + = ' <input type= " date " name= " eventDate " > '
dateAndLocation + = ' <label class= " labelsright " > ' + translate [ ' Time ' ] + ' : '
dateAndLocation + = ' <input type= " time " name= " eventTime " ></label></p> '
2019-11-27 12:11:44 +00:00
dateAndLocation + = ' </div> '
dateAndLocation + = ' <div class= " container " > '
2019-11-27 12:09:13 +00:00
dateAndLocation + = ' <br><label class= " labels " > ' + translate [ ' Location ' ] + ' : </label> '
2019-11-27 12:04:06 +00:00
dateAndLocation + = ' <input type= " text " name= " location " > '
2019-11-12 10:42:27 +00:00
dateAndLocation + = ' </div> '
2019-10-10 10:26:33 +00:00
2019-09-11 09:58:43 +00:00
newPostForm = htmlHeader ( cssFilename , newPostCSS )
2019-07-31 15:12:30 +00:00
# only show the share option if this is not a reply
shareOptionOnDropdown = ' '
2019-11-25 20:46:52 +00:00
questionOptionOnDropdown = ' '
2019-07-31 15:12:30 +00:00
if not replyStr :
2020-02-23 15:32:47 +00:00
shareOptionOnDropdown = \
' <a href= " ' + pathBase + \
' /newshare " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_share.png " /><b> ' + translate [ ' Shares ' ] + \
' </b><br> ' + translate [ ' Describe a shared item ' ] + ' </a> '
questionOptionOnDropdown = \
' <a href= " ' + pathBase + \
' /newquestion " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_question.png " /><b> ' + translate [ ' Question ' ] + \
' </b><br> ' + translate [ ' Ask a question ' ] + ' </a> '
2019-08-05 19:13:15 +00:00
mentionsStr = ' '
for m in mentions :
mentionNickname = getNicknameFromActor ( m )
if not mentionNickname :
continue
mentionDomain , mentionPort = getDomainFromActor ( m )
if not mentionDomain :
continue
2019-09-22 17:54:33 +00:00
if mentionPort :
2020-02-23 15:32:47 +00:00
mentionsHandle = \
' @ ' + mentionNickname + ' @ ' + mentionDomain + ' : ' + str ( mentionPort )
2019-08-05 19:13:15 +00:00
else :
2019-09-22 17:54:33 +00:00
mentionsHandle = ' @ ' + mentionNickname + ' @ ' + mentionDomain
if mentionsHandle not in mentionsStr :
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
2019-09-03 13:02:08 +00:00
dropdownNewPostSuffix = ' /newpost '
2020-02-24 13:32:19 +00:00
dropdownNewBlogSuffix = ' /newblog '
2019-09-03 13:02:08 +00:00
dropdownUnlistedSuffix = ' /newunlisted '
dropdownFollowersSuffix = ' /newfollowers '
dropdownDMSuffix = ' /newdm '
dropdownReportSuffix = ' /newreport '
if inReplyTo or mentions :
dropdownNewPostSuffix = ' '
2020-02-24 13:32:19 +00:00
dropdownNewBlogSuffix = ' '
2019-09-03 13:02:08 +00:00
dropdownUnlistedSuffix = ' '
dropdownFollowersSuffix = ' '
dropdownDMSuffix = ' '
dropdownReportSuffix = ' '
if inReplyTo :
dropdownNewPostSuffix + = ' ?replyto= ' + inReplyTo
2020-02-24 13:32:19 +00:00
dropdownNewBlogSuffix + = ' ?replyto= ' + inReplyTo
2019-09-03 13:02:08 +00:00
dropdownUnlistedSuffix + = ' ?replyto= ' + inReplyTo
dropdownFollowersSuffix + = ' ?replyfollowers= ' + inReplyTo
dropdownDMSuffix + = ' ?replydm= ' + inReplyTo
for mentionedActor in mentions :
dropdownNewPostSuffix + = ' ?mention= ' + mentionedActor
2020-02-24 13:32:19 +00:00
dropdownNewBlogSuffix + = ' ?mention= ' + mentionedActor
2019-09-03 13:02:08 +00:00
dropdownUnlistedSuffix + = ' ?mention= ' + mentionedActor
dropdownFollowersSuffix + = ' ?mention= ' + mentionedActor
dropdownDMSuffix + = ' ?mention= ' + mentionedActor
dropdownReportSuffix + = ' ?mention= ' + mentionedActor
2019-08-25 10:02:28 +00:00
dropDownContent = ' '
if not reportUrl :
2019-11-12 10:42:27 +00:00
dropDownContent + = ' <div id= " myDropdown " class= " dropdown-content " > '
2019-12-10 17:19:53 +00:00
if showPublicOnDropdown :
2020-02-23 15:32:47 +00:00
dropDownContent + = \
' <a href= " ' + pathBase + dropdownNewPostSuffix + \
' " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_public.png " /><b> ' + \
translate [ ' Public ' ] + ' </b><br> ' + \
translate [ ' Visible to anyone ' ] + ' </a> '
2020-02-24 13:32:19 +00:00
dropDownContent + = \
' <a href= " ' + pathBase + dropdownNewBlogSuffix + \
' " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_blog.png " /><b> ' + \
translate [ ' Blog ' ] + ' </b><br> ' + \
translate [ ' Visible to anyone ' ] + ' </a> '
2020-02-23 15:32:47 +00:00
dropDownContent + = \
' <a href= " ' + pathBase + dropdownUnlistedSuffix + \
' " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_unlisted.png " /><b> ' + \
translate [ ' Unlisted ' ] + ' </b><br> ' + \
translate [ ' Not on public timeline ' ] + ' </a> '
dropDownContent + = \
' <a href= " ' + pathBase + dropdownFollowersSuffix + \
' " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_followers.png " /><b> ' + \
translate [ ' Followers ' ] + ' </b><br> ' + \
translate [ ' Only to followers ' ] + ' </a> '
dropDownContent + = \
' <a href= " ' + \
pathBase + dropdownDMSuffix + ' " ><img loading= " lazy " alt= " " title= " " src= " / ' + \
iconsDir + ' /scope_dm.png " /><b> ' + translate [ ' DM ' ] + \
' </b><br> ' + translate [ ' Only to mentioned people ' ] + ' </a> '
dropDownContent + = \
' <a href= " ' + pathBase + dropdownReportSuffix + \
' " ><img loading= " lazy " alt= " " title= " " src= " / ' + iconsDir + \
' /scope_report.png " /><b> ' + translate [ ' Report ' ] + \
' </b><br> ' + translate [ ' Send to moderators ' ] + ' </a> '
2019-11-25 20:46:52 +00:00
dropDownContent + = questionOptionOnDropdown + shareOptionOnDropdown
2019-11-12 10:42:27 +00:00
dropDownContent + = ' </div> '
2019-08-25 10:18:33 +00:00
else :
mentionsStr = ' Re: ' + reportUrl + ' \n \n ' + mentionsStr
2019-10-10 10:26:33 +00:00
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <form enctype= " multipart/form-data " method= " POST " accept-charset= " UTF-8 " action= " ' + \
path + ' ? ' + endpoint + ' ?page= ' + str ( pageNumber ) + ' " > '
2019-11-12 10:42:27 +00:00
newPostForm + = ' <div class= " vertical-center " > '
newPostForm + = ' <label for= " nickname " ><b> ' + newPostText + ' </b></label> '
2019-11-26 13:27:41 +00:00
newPostForm + = ' <div class= " container " > '
newPostForm + = ' <div class= " dropbtn " onclick= " dropdown() " > '
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <img loading= " lazy " alt= " " title= " " src= " / ' + iconsDir + \
' / ' + scopeIcon + ' " /><b class= " scope-desc " > ' + scopeDescription + ' </b> '
2019-11-12 10:42:27 +00:00
newPostForm + = dropDownContent
2019-11-26 13:27:41 +00:00
newPostForm + = ' </div> '
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <a href= " ' + pathBase + \
' /searchemoji " ><img loading= " lazy " class= " emojisearch " src= " /emoji/1F601.png " title= " ' + \
translate [ ' Search for emoji ' ] + ' " alt= " ' + \
translate [ ' Search for emoji ' ] + ' " /></a> '
2019-11-26 13:27:41 +00:00
newPostForm + = ' </div> '
2019-11-26 13:42:00 +00:00
newPostForm + = ' <div class= " container " ><center> '
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <a href= " ' + pathBase + \
' /inbox " ><button class= " cancelbtn " > ' + \
translate [ ' Cancel ' ] + ' </button></a> '
newPostForm + = \
' <input type= " submit " name= " submitPost " value= " ' + \
translate [ ' Submit ' ] + ' " > '
2019-11-26 13:42:00 +00:00
newPostForm + = ' </center></div> '
2019-11-12 10:42:27 +00:00
newPostForm + = replyStr
2019-11-28 17:45:01 +00:00
if mediaInstance and not replyStr :
newPostForm + = newPostImageSection
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <label class= " labels " > ' + placeholderSubject + ' </label><br> '
2019-11-27 12:04:06 +00:00
newPostForm + = ' <input type= " text " name= " subject " > '
2019-11-12 10:42:27 +00:00
newPostForm + = ' '
2019-11-27 12:18:46 +00:00
newPostForm + = ' <br><label class= " labels " > ' + placeholderMessage + ' </label> '
2019-11-28 17:35:32 +00:00
messageBoxHeight = 400
if mediaInstance :
messageBoxHeight = 200
2020-02-24 13:35:20 +00:00
2019-11-28 17:35:32 +00:00
if endpoint == ' newquestion ' :
messageBoxHeight = 100
2020-02-24 13:35:20 +00:00
elif endpoint == ' newblog ' :
messageBoxHeight = 800
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <textarea id= " message " name= " message " style= " height: ' + \
str ( messageBoxHeight ) + ' px " > ' + mentionsStr + ' </textarea> '
2019-11-12 10:42:27 +00:00
newPostForm + = extraFields + dateAndLocation
2019-11-28 17:45:01 +00:00
if not mediaInstance or replyStr :
newPostForm + = newPostImageSection
2019-11-12 10:42:27 +00:00
newPostForm + = ' </div> '
newPostForm + = ' </form> '
2019-08-24 18:00:15 +00:00
2019-08-25 10:14:36 +00:00
if not reportUrl :
2020-02-23 15:32:47 +00:00
newPostForm + = \
' <script> ' + clickToDropDownScript ( ) + \
cursorToEndOfMessageScript ( ) + ' </script> '
newPostForm = \
newPostForm . replace ( ' <body> ' , ' <body onload= " focusOnMessage() " > ' )
2019-08-24 18:00:15 +00:00
2019-07-25 21:39:09 +00:00
newPostForm + = htmlFooter ( )
return newPostForm
2019-09-11 09:58:43 +00:00
def htmlHeader ( cssFilename : str , css = None , refreshSec = 0 , lang = ' en ' ) - > str :
2019-08-22 12:14:24 +00:00
if refreshSec == 0 :
meta = ' <meta charset= " utf-8 " > \n '
else :
2020-02-23 15:32:47 +00:00
meta = \
' <meta http-equiv= " Refresh " content= " ' + \
str ( refreshSec ) + ' " charset= " utf-8 " > \n '
2019-08-22 12:14:24 +00:00
2019-09-11 09:58:43 +00:00
if not css :
if ' / ' in cssFilename :
cssFilename = cssFilename . split ( ' / ' ) [ - 1 ]
2019-11-12 10:42:27 +00:00
htmlStr = ' <!DOCTYPE html> \n '
htmlStr + = ' <html lang= " ' + lang + ' " > \n '
htmlStr + = meta
htmlStr + = ' <style> \n '
htmlStr + = ' @import url( " ' + cssFilename + ' " ); \n '
htmlStr + = ' background-color: #282c37 '
htmlStr + = ' </style> \n '
htmlStr + = ' <body> \n '
2019-07-21 18:18:58 +00:00
else :
2019-11-12 10:42:27 +00:00
htmlStr = ' <!DOCTYPE html> \n '
htmlStr + = ' <html lang= " ' + lang + ' " > \n '
htmlStr + = meta
htmlStr + = ' <style> \n ' + css + ' </style> \n '
htmlStr + = ' <body> \n '
2019-07-20 21:13:36 +00:00
return htmlStr
def htmlFooter ( ) - > str :
2019-11-12 10:42:27 +00:00
htmlStr = ' </body> \n '
htmlStr + = ' </html> \n '
2019-07-20 21:13:36 +00:00
return htmlStr
2019-11-24 17:40:31 +00:00
def htmlProfilePosts ( recentPostsCache : { } , maxRecentPosts : int , \
translate : { } , \
2019-09-07 08:57:52 +00:00
baseDir : str , httpPrefix : str , \
2019-07-22 14:09:21 +00:00
authorized : bool , ocapAlways : bool , \
nickname : str , domain : str , port : int , \
2019-08-14 20:12:27 +00:00
session , wfRequest : { } , personCache : { } , \
projectVersion : 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
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-07-22 09:38:02 +00:00
profileStr = ' '
2019-09-02 18:58:20 +00:00
maxItems = 4
2019-07-31 12:44:08 +00:00
profileStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-09-02 18:58:20 +00:00
ctr = 0
currPage = 1
while ctr < maxItems and currPage < 4 :
outboxFeed = \
2019-11-24 12:12:29 +00:00
personBoxJson ( { } , session , baseDir , domain , \
2019-09-02 18:58:20 +00:00
port , ' /users/ ' + nickname + ' /outbox?page= ' + str ( currPage ) , \
httpPrefix , \
10 , ' outbox ' , \
authorized , \
ocapAlways )
if not outboxFeed :
break
if len ( outboxFeed [ ' orderedItems ' ] ) == 0 :
break
for item in outboxFeed [ ' orderedItems ' ] :
2019-09-04 15:00:27 +00:00
if item [ ' type ' ] == ' Create ' :
2019-11-24 17:40:31 +00:00
postStr = \
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
baseDir , session , wfRequest , personCache , \
nickname , domain , port , item , None , True , False , \
httpPrefix , projectVersion , ' inbox ' , \
False , False , False , True , False )
2019-09-02 18:58:20 +00:00
if postStr :
profileStr + = postStr
ctr + = 1
if ctr > = maxItems :
break
currPage + = 1
2019-07-22 09:38:02 +00:00
return profileStr
2019-09-07 08:57:52 +00:00
def htmlProfileFollowing ( translate : { } , baseDir : str , httpPrefix : str , \
2019-07-22 14:09:21 +00:00
authorized : bool , ocapAlways : bool , \
nickname : str , domain : str , port : int , \
session , wfRequest : { } , personCache : { } , \
2019-08-14 20:12:27 +00:00
followingJson : { } , projectVersion : str , \
2019-09-05 16:17:09 +00:00
buttons : [ ] , \
feedName : str , actor : str , \
pageNumber : int , \
maxItemsPerPage : int ) - > str :
2019-07-22 11:44:31 +00:00
""" Shows following on the profile screen
"""
profileStr = ' '
2019-09-05 16:17:09 +00:00
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-09-05 16:17:09 +00:00
if authorized and pageNumber :
if authorized and pageNumber > 1 :
# page up arrow
profileStr + = \
2020-02-23 15:32:47 +00:00
' <center><a href= " ' + actor + ' / ' + feedName + \
' ?page= ' + str ( pageNumber - 1 ) + \
' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pageup.png " title= " ' + \
translate [ ' Page up ' ] + ' " alt= " ' + \
translate [ ' Page up ' ] + ' " ></a></center> '
2019-07-22 11:44:31 +00:00
for item in followingJson [ ' orderedItems ' ] :
2019-08-07 21:36:54 +00:00
profileStr + = \
2019-09-07 09:25:16 +00:00
individualFollowAsHtml ( translate , baseDir , session , \
2019-08-20 09:16:03 +00:00
wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
domain , item , authorized , nickname , \
httpPrefix , projectVersion , \
buttons )
2019-09-05 16:17:09 +00:00
if authorized and maxItemsPerPage and pageNumber :
if len ( followingJson [ ' orderedItems ' ] ) > = maxItemsPerPage :
# page down arrow
profileStr + = \
2020-02-23 15:32:47 +00:00
' <center><a href= " ' + actor + ' / ' + feedName + \
' ?page= ' + str ( pageNumber + 1 ) + \
' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pagedown.png " title= " ' + \
translate [ ' Page down ' ] + ' " alt= " ' + \
translate [ ' Page down ' ] + ' " ></a></center> '
2019-07-22 11:44:31 +00:00
return profileStr
2019-09-07 08:57:52 +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
"""
profileStr = ' '
for project , rolesList in rolesJson . items ( ) :
2020-02-23 15:32:47 +00:00
profileStr + = \
' <div class= " roles " ><h2> ' + project + ' </h2><div class= " roles-inner " > '
2019-07-22 17:21:45 +00:00
for role in rolesList :
profileStr + = ' <h3> ' + role + ' </h3> '
profileStr + = ' </div></div> '
if len ( profileStr ) == 0 :
profileStr + = ' <p>@ ' + nickname + ' @ ' + domain + ' has no roles assigned</p> '
else :
profileStr = ' <div> ' + profileStr + ' </div> '
return profileStr
2020-02-23 15:32:47 +00:00
def htmlProfileSkills ( translate : { } , nickname : str , domain : str , \
skillsJson : { } ) - > str :
2019-07-22 20:01:46 +00:00
""" Shows skills on the profile screen
"""
profileStr = ' '
for skill , level in skillsJson . items ( ) :
2020-02-23 15:32:47 +00:00
profileStr + = \
' <div> ' + skill + ' <br><div id= " myProgress " ><div id= " myBar " style= " width: ' + \
str ( level ) + ' % " ></div></div></div><br> '
2019-09-07 08:57:52 +00:00
if len ( profileStr ) > 0 :
2019-07-22 20:01:46 +00:00
profileStr = ' <center><div class= " skill-title " > ' + profileStr + ' </div></center> '
return profileStr
2020-02-23 15:32:47 +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
"""
profileStr = ' <div class= " container " > '
profileStr + = ' <p class= " share-title " > ' + item [ ' displayName ' ] + ' </p> '
if item . get ( ' imageUrl ' ) :
profileStr + = ' <a href= " ' + item [ ' imageUrl ' ] + ' " > '
2020-02-23 15:32:47 +00:00
profileStr + = \
' <img loading= " lazy " src= " ' + item [ ' imageUrl ' ] + \
' " alt= " ' + translate [ ' Item image ' ] + ' " ></a> '
2019-11-02 14:19:51 +00:00
profileStr + = ' <p> ' + item [ ' summary ' ] + ' </p> '
profileStr + = ' <p><b> ' + translate [ ' Type ' ] + ' :</b> ' + item [ ' itemType ' ] + ' '
profileStr + = ' <b> ' + translate [ ' Category ' ] + ' :</b> ' + item [ ' category ' ] + ' '
profileStr + = ' <b> ' + translate [ ' Location ' ] + ' :</b> ' + item [ ' location ' ] + ' </p> '
if showContact :
contactActor = item [ ' actor ' ]
2020-02-23 15:32:47 +00:00
profileStr + = \
' <p><a href= " ' + actor + ' ?replydm=sharedesc: ' + item [ ' displayName ' ] + \
' ?mention= ' + contactActor + ' " ><button class= " button " > ' + \
translate [ ' Contact ' ] + ' </button></a> '
2019-11-03 09:19:37 +00:00
if removeButton :
2020-02-23 15:32:47 +00:00
profileStr + = \
' <a href= " ' + actor + ' ?rmshare= ' + item [ ' displayName ' ] + \
' " ><button class= " button " > ' + translate [ ' Remove ' ] + ' </button></a> '
2019-11-02 14:19:51 +00:00
profileStr + = ' </div> '
return profileStr
2020-02-23 15:32:47 +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
"""
profileStr = ' '
for item in sharesJson [ ' orderedItems ' ] :
2019-11-03 09:19:37 +00:00
profileStr + = htmlIndividualShare ( actor , item , translate , False , False )
2019-09-07 08:57:52 +00:00
if len ( profileStr ) > 0 :
2019-07-24 09:02:38 +00:00
profileStr = ' <div class= " share-title " > ' + profileStr + ' </div> '
2019-07-23 12:33:09 +00:00
return profileStr
2019-11-02 14:19:51 +00:00
def sharesTimelineJson ( actor : str , pageNumber : int , itemsPerPage : int , \
baseDir : str , maxSharesPerAccount : int ) - > ( { } , bool ) :
""" 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
"""
allSharesJson = { }
for subdir , dirs , files in os . walk ( baseDir + ' /accounts ' ) :
for handle in dirs :
if ' @ ' in handle :
accountDir = baseDir + ' /accounts/ ' + handle
sharesFilename = accountDir + ' /shares.json '
if os . path . isfile ( sharesFilename ) :
sharesJson = loadJson ( sharesFilename )
if not sharesJson :
continue
2019-11-03 09:36:04 +00:00
nickname = handle . split ( ' @ ' ) [ 0 ]
# actor who owns this share
owner = actor . split ( ' /users/ ' ) [ 0 ] + ' /users/ ' + nickname
2019-11-02 14:19:51 +00:00
ctr = 0
for itemID , item in sharesJson . items ( ) :
2019-11-03 09:36:04 +00:00
# assign owner to the item
2019-11-03 09:19:37 +00:00
item [ ' actor ' ] = owner
2019-11-02 14:19:51 +00:00
allSharesJson [ str ( item [ ' published ' ] ) ] = item
ctr + = 1
if ctr > = maxSharesPerAccount :
break
# sort the shared items in descending order of publication date
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 :
break
if ctr < startIndex :
ctr + = 1
continue
resultJson [ published ] = item
ctr + = 1
return resultJson , lastPage
def htmlSharesTimeline ( translate : { } , pageNumber : int , itemsPerPage : int , \
2019-11-02 14:36:04 +00:00
baseDir : str , actor : str , \
2019-11-02 14:19:51 +00:00
nickname : str , domain : str , port : int , \
maxSharesPerAccount : int , httpPrefix : str ) - > str :
""" Show shared items timeline as html
"""
sharesJson , lastPage = \
sharesTimelineJson ( actor , pageNumber , itemsPerPage , \
baseDir , maxSharesPerAccount )
domainFull = domain
if port != 80 and port != 443 :
if ' : ' not in domain :
domainFull = domain + ' : ' + str ( port )
actor = httpPrefix + ' :// ' + domainFull + ' /users/ ' + nickname
timelineStr = ' '
if pageNumber > 1 :
2020-02-23 15:32:47 +00:00
timelineStr + = \
' <center><a href= " ' + actor + ' /tlshares?page= ' + \
str ( pageNumber - 1 ) + \
' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pageup.png " title= " ' + translate [ ' Page up ' ] + \
' " alt= " ' + translate [ ' Page up ' ] + ' " ></a></center> '
2019-11-02 14:19:51 +00:00
for published , item in sharesJson . items ( ) :
2019-11-03 09:19:37 +00:00
showContactButton = False
if item [ ' actor ' ] != actor :
showContactButton = True
showRemoveButton = False
if item [ ' actor ' ] == actor :
showRemoveButton = True
2020-02-23 15:32:47 +00:00
timelineStr + = \
htmlIndividualShare ( actor , item , translate , \
showContactButton , showRemoveButton )
2019-11-02 14:19:51 +00:00
if not lastPage :
2020-02-23 15:32:47 +00:00
timelineStr + = \
' <center><a href= " ' + actor + ' /tlshares?page= ' + \
str ( pageNumber + 1 ) + ' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pagedown.png " title= " ' + translate [ ' Page down ' ] + \
' " alt= " ' + translate [ ' Page down ' ] + ' " ></a></center> '
2019-11-02 14:19:51 +00:00
return timelineStr
2019-11-28 17:09:57 +00:00
def htmlProfile ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 17:40:31 +00:00
translate : { } , projectVersion : str , \
2019-08-14 20:12:27 +00:00
baseDir : str , httpPrefix : str , authorized : bool , \
2019-07-22 14:09:21 +00:00
ocapAlways : bool , profileJson : { } , selected : str , \
session , wfRequest : { } , personCache : { } , \
2019-09-05 16:17:09 +00:00
extraJson = None , \
pageNumber = None , maxItemsPerPage = None ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the profile page as html
"""
2019-08-22 19:04:41 +00:00
nickname = profileJson [ ' preferredUsername ' ]
2019-07-21 18:18:58 +00:00
if not nickname :
return " "
domain , port = getDomainFromActor ( profileJson [ ' id ' ] )
if not domain :
return " "
2019-09-23 12:19:00 +00:00
displayName = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
2019-09-23 12:33:32 +00:00
profileJson [ ' name ' ] , True )
2019-07-21 18:18:58 +00:00
domainFull = domain
if port :
domainFull = domain + ' : ' + str ( port )
2019-09-23 12:33:32 +00:00
profileDescription = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
profileJson [ ' summary ' ] , False )
2019-07-22 09:38:02 +00:00
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 '
2019-07-28 15:52:59 +00:00
loginButton = ' '
2019-07-29 18:48:23 +00:00
followApprovalsSection = ' '
2019-08-02 10:37:03 +00:00
followApprovals = False
2019-07-30 12:03:40 +00:00
linkToTimelineStart = ' '
linkToTimelineEnd = ' '
2019-08-02 09:52:12 +00:00
editProfileStr = ' '
2019-11-07 18:57:50 +00:00
logoutStr = ' '
2019-08-02 09:52:12 +00:00
actor = profileJson [ ' id ' ]
2019-07-29 18:48:23 +00:00
2019-11-07 10:11:32 +00:00
donateSection = ' '
2019-11-07 09:52:00 +00:00
donateUrl = getDonationUrl ( profileJson )
2019-12-17 20:44:18 +00:00
PGPpubKey = getPGPpubKey ( profileJson )
emailAddress = getEmailAddress ( profileJson )
2019-12-17 14:57:16 +00:00
xmppAddress = getXmppAddress ( profileJson )
2019-12-17 15:25:34 +00:00
matrixAddress = getMatrixAddress ( profileJson )
2020-02-23 15:32:47 +00:00
if donateUrl or xmppAddress or matrixAddress or \
PGPpubKey or emailAddress :
2019-11-12 10:42:27 +00:00
donateSection = ' <div class= " container " > \n '
donateSection + = ' <center> \n '
2019-12-17 14:57:16 +00:00
if donateUrl :
2020-02-23 15:32:47 +00:00
donateSection + = \
' <p><a href= " ' + donateUrl + \
' " ><button class= " donateButton " > ' + translate [ ' Donate ' ] + \
' </button></a></p> \n '
2019-12-17 20:44:18 +00:00
if emailAddress :
2020-02-23 15:32:47 +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-02-23 15:32:47 +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-02-23 15:32:47 +00:00
donateSection + = \
' <p> ' + translate [ ' Matrix ' ] + ' : ' + matrixAddress + ' </p> \n '
2019-12-17 20:44:18 +00:00
if PGPpubKey :
2020-02-23 15:32:47 +00:00
donateSection + = \
' <p class= " pgp " > ' + PGPpubKey . replace ( ' \n ' , ' <br> ' ) + ' </p> \n '
2019-12-17 21:33:30 +00:00
donateSection + = ' </center> \n '
2019-11-12 10:42:27 +00:00
donateSection + = ' </div> \n '
2019-11-07 09:52:00 +00:00
2019-07-28 15:52:59 +00:00
if not authorized :
2020-02-23 15:32:47 +00:00
loginButton = \
' <br><a href= " /login " ><button class= " loginButton " > ' + \
translate [ ' Login ' ] + ' </button></a> '
2019-07-29 18:48:23 +00:00
else :
2020-02-23 15:32:47 +00:00
editProfileStr = \
' <a href= " ' + actor + \
' /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 ' ] + ' " > '
2019-07-30 12:03:40 +00:00
linkToTimelineEnd = ' </a> '
2019-07-29 18:48:23 +00:00
# are there any follow requests?
2020-02-23 15:32:47 +00:00
followRequestsFilename = \
baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /followrequests.txt '
2019-07-29 18:48:23 +00:00
if os . path . isfile ( followRequestsFilename ) :
with open ( followRequestsFilename , ' r ' ) as f :
for line in f :
if len ( line ) > 0 :
2019-08-02 10:37:03 +00:00
followApprovals = True
followersButton = ' buttonhighlighted '
if selected == ' followers ' :
followersButton = ' buttonselectedhighlighted '
2019-07-29 18:48:23 +00:00
break
if selected == ' followers ' :
2019-08-02 10:37:03 +00:00
if followApprovals :
2019-07-29 18:48:23 +00:00
with open ( followRequestsFilename , ' r ' ) as f :
for followerHandle in f :
if len ( line ) > 0 :
if ' :// ' in followerHandle :
followerActor = followerHandle
else :
2020-02-23 15:32:47 +00:00
followerActor = \
httpPrefix + ' :// ' + followerHandle . split ( ' @ ' ) [ 1 ] + \
' /users/ ' + followerHandle . split ( ' @ ' ) [ 0 ]
2019-07-29 18:48:23 +00:00
basePath = httpPrefix + ' :// ' + domainFull + ' /users/ ' + nickname
followApprovalsSection + = ' <div class= " container " > '
followApprovalsSection + = ' <a href= " ' + followerActor + ' " > '
2020-02-23 15:32:47 +00:00
followApprovalsSection + = \
' <span class= " followRequestHandle " > ' + followerHandle + ' </span></a> '
followApprovalsSection + = \
' <a href= " ' + basePath + ' /followapprove= ' + followerHandle + ' " > '
followApprovalsSection + = \
' <button class= " followApprove " > ' + translate [ ' Approve ' ] + ' </button></a> '
followApprovalsSection + = \
' <a href= " ' + basePath + ' /followdeny= ' + followerHandle + ' " > '
followApprovalsSection + = \
' <button class= " followDeny " > ' + translate [ ' Deny ' ] + ' </button></a> '
2019-07-29 18:48:23 +00:00
followApprovalsSection + = ' </div> '
2019-10-23 14:27:43 +00:00
profileDescriptionShort = profileDescription
if ' \n ' in profileDescription :
if len ( profileDescription . split ( ' \n ' ) ) > 2 :
profileDescriptionShort = ' '
else :
if ' <br> ' in profileDescription :
if len ( profileDescription . split ( ' <br> ' ) ) > 2 :
profileDescriptionShort = ' '
2019-10-23 14:32:48 +00:00
profileDescription = profileDescription . replace ( ' <br> ' , ' \n ' )
2019-10-23 15:09:20 +00:00
# keep the profile description short
if len ( profileDescriptionShort ) > 256 :
profileDescriptionShort = ' '
# remove formatting from profile description used on title
2019-10-23 15:06:22 +00:00
avatarDescription = ' '
if profileJson . get ( ' summary ' ) :
2020-02-23 15:32:47 +00:00
avatarDescription = \
profileJson [ ' summary ' ] . replace ( ' <br> ' , ' \n ' ) . replace ( ' <p> ' , ' ' ) . replace ( ' </p> ' , ' ' )
2019-11-12 10:42:27 +00:00
profileHeaderStr = ' <div class= " hero-image " > '
profileHeaderStr + = ' <div class= " hero-text " > '
2020-02-23 15:32:47 +00:00
profileHeaderStr + = \
' <img loading= " lazy " src= " ' + profileJson [ ' icon ' ] [ ' url ' ] + \
' " title= " ' + avatarDescription + ' " alt= " ' + \
avatarDescription + ' " class= " title " > '
2019-11-12 10:42:27 +00:00
profileHeaderStr + = ' <h1> ' + displayName + ' </h1> '
profileHeaderStr + = ' <p><b>@ ' + nickname + ' @ ' + domainFull + ' </b></p> '
profileHeaderStr + = ' <p> ' + profileDescriptionShort + ' </p> '
profileHeaderStr + = loginButton
profileHeaderStr + = ' </div> '
profileHeaderStr + = ' </div> '
2020-02-23 15:32:47 +00:00
profileStr = \
linkToTimelineStart + profileHeaderStr + \
linkToTimelineEnd + donateSection
2019-11-12 10:42:27 +00:00
profileStr + = ' <div class= " container " > \n '
profileStr + = ' <center> '
2020-02-23 15:32:47 +00:00
profileStr + = \
' <a href= " ' + actor + ' " ><button class= " ' + postsButton + \
' " ><span> ' + translate [ ' Posts ' ] + ' </span></button></a> '
profileStr + = \
' <a href= " ' + actor + ' /following " ><button class= " ' + \
followingButton + ' " ><span> ' + translate [ ' Following ' ] + \
' </span></button></a> '
profileStr + = \
' <a href= " ' + actor + ' /followers " ><button class= " ' + \
followersButton + ' " ><span> ' + translate [ ' Followers ' ] + \
' </span></button></a> '
profileStr + = \
' <a href= " ' + actor + ' /roles " ><button class= " ' + \
rolesButton + ' " ><span> ' + translate [ ' Roles ' ] + ' </span></button></a> '
profileStr + = \
' <a href= " ' + actor + ' /skills " ><button class= " ' + \
skillsButton + ' " ><span> ' + translate [ ' Skills ' ] + \
' </span></button></a> '
profileStr + = \
' <a href= " ' + actor + ' /shares " ><button class= " ' + \
sharesButton + ' " ><span> ' + translate [ ' Shares ' ] + \
' </span></button></a> '
2019-11-12 10:42:27 +00:00
profileStr + = editProfileStr + logoutStr
profileStr + = ' </center> '
profileStr + = ' </div> '
2019-07-21 18:18:58 +00:00
2019-07-29 18:48:23 +00:00
profileStr + = followApprovalsSection
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2020-02-23 15:32:47 +00:00
profileStyle = \
cssFile . read ( ) . replace ( ' image.png ' , \
profileJson [ ' image ' ] [ ' url ' ] )
2019-07-21 22:38:44 +00:00
2020-02-23 15:32:47 +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> '
2019-10-03 14:28:52 +00:00
2019-07-22 17:42:39 +00:00
if selected == ' posts ' :
profileStr + = \
2019-11-24 17:40:31 +00:00
htmlProfilePosts ( recentPostsCache , maxRecentPosts , \
translate , \
2019-09-07 08:57:52 +00:00
baseDir , httpPrefix , authorized , \
2019-07-22 17:42:39 +00:00
ocapAlways , nickname , domain , port , \
2019-08-14 20:12:27 +00:00
session , wfRequest , personCache , \
2019-10-03 14:28:52 +00:00
projectVersion ) + licenseStr
2019-08-07 21:36:54 +00:00
if selected == ' following ' :
2019-07-22 17:42:39 +00:00
profileStr + = \
2019-09-07 08:57:52 +00:00
htmlProfileFollowing ( translate , baseDir , httpPrefix , \
2019-07-22 17:42:39 +00:00
authorized , ocapAlways , nickname , \
domain , port , session , \
2019-08-07 21:36:54 +00:00
wfRequest , personCache , extraJson , \
2019-08-14 20:12:27 +00:00
projectVersion , \
2019-09-05 16:17:09 +00:00
[ " unfollow " ] , \
selected , actor , \
pageNumber , maxItemsPerPage )
2019-08-07 21:36:54 +00:00
if selected == ' followers ' :
profileStr + = \
2019-09-07 08:57:52 +00:00
htmlProfileFollowing ( translate , baseDir , httpPrefix , \
2019-08-07 21:36:54 +00:00
authorized , ocapAlways , nickname , \
domain , port , session , \
wfRequest , personCache , extraJson , \
2019-09-05 16:17:09 +00:00
projectVersion , \
[ " block " ] , \
selected , actor , \
pageNumber , maxItemsPerPage )
2019-07-22 17:42:39 +00:00
if selected == ' roles ' :
profileStr + = \
2019-10-03 14:36:22 +00:00
htmlProfileRoles ( translate , nickname , domainFull , extraJson )
2019-07-22 20:01:46 +00:00
if selected == ' skills ' :
profileStr + = \
2019-10-03 14:36:22 +00:00
htmlProfileSkills ( translate , nickname , domainFull , extraJson )
2019-07-23 12:33:09 +00:00
if selected == ' shares ' :
profileStr + = \
2020-02-23 15:32:47 +00:00
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
2019-09-07 09:25:16 +00:00
def individualFollowAsHtml ( translate : { } , \
baseDir : str , session , wfRequest : { } , \
2019-07-22 14:09:21 +00:00
personCache : { } , domain : str , \
2019-08-07 21:36:54 +00:00
followUrl : str , \
authorized : bool , \
2019-08-08 08:38:40 +00:00
actorNickname : str , \
2019-08-14 20:12:27 +00:00
httpPrefix : str , \
projectVersion : str , \
2019-08-07 21:36:54 +00:00
buttons = [ ] ) - > str :
2019-07-22 11:44:31 +00:00
nickname = getNicknameFromActor ( followUrl )
domain , port = getDomainFromActor ( followUrl )
titleStr = ' @ ' + nickname + ' @ ' + domain
2019-08-20 09:37:09 +00:00
avatarUrl = getPersonAvatarUrl ( baseDir , followUrl , personCache )
2019-08-18 13:30:40 +00:00
if not avatarUrl :
avatarUrl = followUrl + ' /avatar.png '
2019-07-22 14:09:21 +00:00
if domain not in followUrl :
2019-08-22 18:36:07 +00:00
inboxUrl , pubKeyId , pubKey , fromPersonId , sharedInbox , capabilityAcquisition , avatarUrl2 , displayName = \
2019-08-20 09:16:03 +00:00
getPersonBox ( baseDir , session , wfRequest , personCache , \
2019-10-17 15:55:05 +00:00
projectVersion , httpPrefix , nickname , domain , ' outbox ' )
2019-07-22 14:09:21 +00:00
if avatarUrl2 :
avatarUrl = avatarUrl2
2019-08-22 18:36:07 +00:00
if displayName :
titleStr = displayName + ' ' + titleStr
2019-08-07 21:36:54 +00:00
buttonsStr = ' '
if authorized :
for b in buttons :
if b == ' block ' :
2020-02-23 15:32:47 +00:00
buttonsStr + = \
' <a href= " /users/ ' + actorNickname + ' ?options= ' + followUrl + \
' ;1; ' + avatarUrl + ' " ><button class= " buttonunfollow " > ' + \
translate [ ' Block ' ] + ' </button></a> '
2019-09-29 12:46:49 +00:00
#buttonsStr+='<a href="/users/'+actorNickname+'?block='+followUrl+';'+avatarUrl+'"><button class="buttonunfollow">'+translate['Block']+'</button></a>'
2019-08-07 21:36:54 +00:00
if b == ' unfollow ' :
2020-02-23 15:32:47 +00:00
buttonsStr + = \
' <a href= " /users/ ' + actorNickname + ' ?options= ' + followUrl + \
' ;1; ' + avatarUrl + ' " ><button class= " buttonunfollow " > ' + \
translate [ ' Unfollow ' ] + ' </button></a> '
2019-09-29 12:46:49 +00:00
#buttonsStr+='<a href="/users/'+actorNickname+'?unfollow='+followUrl+';'+avatarUrl+'"><button class="buttonunfollow">'+translate['Unfollow']+'</button></a>'
2019-08-07 21:36:54 +00:00
2019-11-12 10:42:27 +00:00
resultStr = ' <div class= " container " > \n '
2020-02-23 15:32:47 +00:00
resultStr + = \
' <a href= " /users/ ' + actorNickname + ' ?options= ' + \
followUrl + ' ;1; ' + avatarUrl + ' " > '
2019-11-12 10:42:27 +00:00
resultStr + = ' <p><img loading= " lazy " src= " ' + avatarUrl + ' " alt= " " > \n '
resultStr + = titleStr + ' </a> ' + buttonsStr + ' </p> '
resultStr + = ' </div> \n '
return resultStr
2019-07-22 11:44:31 +00:00
2019-08-24 18:00:15 +00:00
def clickToDropDownScript ( ) - > str :
""" Function run onclick to create a dropdown
"""
2019-11-12 10:42:27 +00:00
script = ' function dropdown() { \n '
script + = ' document.getElementById( " myDropdown " ).classList.toggle( " show " ); \n '
script + = ' } \n '
2019-08-24 18:00:15 +00:00
return script
2019-09-22 09:26:52 +00:00
def cursorToEndOfMessageScript ( ) - > str :
2019-09-22 09:14:39 +00:00
""" Moves the cursor to the end of the text in a textarea
This avoids the cursor being in the wrong position when replying
"""
2019-11-12 10:42:27 +00:00
script = ' function focusOnMessage() { \n '
script + = " var replyTextArea = document.getElementById( ' message ' ); \n "
script + = ' val = replyTextArea.value; \n '
script + = ' if ((val.length>0) && (val.charAt(val.length-1) != " " )) { \n '
script + = ' val += " " ; \n '
script + = ' } \n '
script + = ' replyTextArea.focus(); \n '
script + = ' replyTextArea.value= " " ; \n '
script + = ' replyTextArea.value=val; \n '
script + = ' } \n '
script + = " var replyTextArea = document.getElementById( ' message ' ) \n "
script + = ' replyTextArea.onFocus = function() { \n '
script + = ' focusOnMessage(); '
script + = ' } \n '
2019-09-22 09:14:39 +00:00
return script
2019-07-31 12:44:08 +00:00
def contentWarningScript ( ) - > str :
""" Returns a script used for content warnings
"""
2019-11-12 10:42:27 +00:00
script = ' function showContentWarning(postID) { \n '
script + = ' var x = document.getElementById(postID); \n '
script + = ' if (x.style.display !== " block " ) { \n '
script + = ' x.style.display = " block " ; \n '
script + = ' } else { \n '
script + = ' x.style.display = " none " ; \n '
script + = ' } \n '
script + = ' } \n '
2019-07-31 12:44:08 +00:00
return script
2019-09-07 08:57:52 +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
2019-08-30 11:32:48 +00:00
extension = ' .mp3 '
if ' .ogg ' in content :
extension = ' .ogg '
words = content . strip ( ' \n ' ) . split ( ' ' )
for w in words :
if extension not in w :
continue
2019-08-30 16:20:34 +00:00
w = w . replace ( ' href= " ' , ' ' ) . replace ( ' " > ' , ' ' )
2019-08-30 11:32:48 +00:00
if w . endswith ( ' . ' ) :
w = w [ : - 1 ]
2019-08-30 16:18:34 +00:00
if w . endswith ( ' " ' ) :
w = w [ : - 1 ]
2019-08-30 11:32:48 +00:00
if w . endswith ( ' ; ' ) :
w = w [ : - 1 ]
if w . endswith ( ' : ' ) :
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
2019-08-30 16:18:34 +00:00
2020-02-23 15:32:47 +00:00
if not ( w . startswith ( ' http ' ) or w . startswith ( ' dat: ' ) or \
w . startswith ( ' i2p: ' ) or ' / ' in w ) :
2019-08-30 11:32:48 +00:00
continue
url = w
2019-08-30 16:24:40 +00:00
content + = ' <center><audio controls> '
2019-08-30 11:32:48 +00:00
content + = ' <source src= " ' + url + ' " type= " audio/ ' + extension . replace ( ' . ' , ' ' ) + ' " > '
2019-09-07 08:57:52 +00:00
content + = translate [ ' Your browser does not support the audio element. ' ]
2019-08-30 12:20:30 +00:00
content + = ' </audio></center> '
2019-08-30 11:32:48 +00:00
return content
2019-09-07 08:57:52 +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
2019-08-30 11:45:21 +00:00
extension = ' .mp4 '
if ' .webm ' in content :
extension = ' .webm '
elif ' .ogv ' in content :
extension = ' .ogv '
words = content . strip ( ' \n ' ) . split ( ' ' )
for w in words :
if extension not in w :
continue
2019-08-30 16:20:34 +00:00
w = w . replace ( ' href= " ' , ' ' ) . replace ( ' " > ' , ' ' )
2019-08-30 11:45:21 +00:00
if w . endswith ( ' . ' ) :
w = w [ : - 1 ]
2019-08-30 16:18:34 +00:00
if w . endswith ( ' " ' ) :
w = w [ : - 1 ]
2019-08-30 11:45:21 +00:00
if w . endswith ( ' ; ' ) :
w = w [ : - 1 ]
if w . endswith ( ' : ' ) :
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-02-23 15:32:47 +00:00
if not ( w . startswith ( ' http ' ) or w . startswith ( ' dat: ' ) or \
w . startswith ( ' i2p: ' ) or ' / ' in w ) :
2019-08-30 11:45:21 +00:00
continue
url = w
2020-02-23 15:32:47 +00:00
content + = \
' <center><video width= " ' + str ( width ) + ' " height= " ' + \
str ( height ) + ' " controls> '
content + = \
' <source src= " ' + url + ' " type= " video/ ' + extension . replace ( ' . ' , ' ' ) + ' " > '
2019-09-07 08:57:52 +00:00
content + = translate [ ' Your browser does not support the video element. ' ]
2019-08-30 12:20:30 +00:00
content + = ' </video></center> '
2019-08-30 11:45:21 +00:00
return content
2019-09-07 08:57:52 +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 :
url = content . split ( ' >vimeo.com/ ' ) [ 1 ]
if ' < ' in url :
2019-08-20 16:48:37 +00:00
url = url . split ( ' < ' ) [ 0 ]
2020-02-23 15:32:47 +00:00
content = \
content + " <center><iframe loading= \" lazy \" src= \" https://player.vimeo.com/video/ " + \
url + " \" width= \" " + str ( width ) + " \" height= \" " + str ( height ) + \
" \" frameborder= \" 0 \" allow= \" autoplay; fullscreen \" allowfullscreen></iframe></center> "
2019-08-20 17:27:23 +00:00
return content
2019-08-21 11:34:02 +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 :
2019-08-21 11:34:02 +00:00
url = url . split ( ' " ' ) [ 0 ] . replace ( ' /watch?v= ' , ' /embed/ ' )
2019-10-22 08:44:33 +00:00
if ' & ' in url :
url = url . split ( ' & ' ) [ 0 ]
2020-02-23 15:32:47 +00:00
content = \
content + " <center><iframe loading= \" lazy \" src= \" " + \
videoSite + url + " \" width= \" " + str ( width ) + \
" \" height= \" " + str ( height ) + \
" \" frameborder= \" 0 \" allow= \" autoplay; fullscreen \" allowfullscreen></iframe></center> "
2019-08-21 11:34:02 +00:00
return content
2020-02-23 15:32:47 +00:00
invidiousSites = ( ' https://invidio.us ' , \
' axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4bzzsg2ii4fv2iid.onion ' )
2019-10-01 11:31:29 +00:00
for videoSite in invidiousSites :
if ' " ' + videoSite in content :
url = content . split ( ' " ' + videoSite ) [ 1 ]
if ' " ' in url :
url = url . split ( ' " ' ) [ 0 ] . replace ( ' /watch?v= ' , ' /embed/ ' )
2019-10-22 08:44:33 +00:00
if ' & ' in url :
url = url . split ( ' & ' ) [ 0 ]
2020-02-23 15:32:47 +00:00
content = \
content + " <center><iframe loading= \" lazy \" src= \" " + \
videoSite + url + " \" width= \" " + str ( width ) + " \" height= \" " + \
str ( height ) + \
" \" frameborder= \" 0 \" allow= \" autoplay; fullscreen \" allowfullscreen></iframe></center> "
2019-10-01 11:31:29 +00:00
return content
2019-10-01 11:25:10 +00:00
2019-08-21 11:34:02 +00:00
videoSite = ' https://media.ccc.de '
if ' " ' + videoSite in content :
url = content . split ( ' " ' + videoSite ) [ 1 ]
if ' " ' in url :
url = url . split ( ' " ' ) [ 0 ]
2019-08-21 11:40:43 +00:00
if not url . endswith ( ' /oembed ' ) :
url = url + ' /oembed '
2020-02-23 15:32:47 +00:00
content = \
content + " <center><iframe loading= \" lazy \" src= \" " + \
videoSite + url + " \" width= \" " + str ( width ) + " \" height= \" " + \
str ( height ) + \
" \" frameborder= \" 0 \" allow= \" fullscreen \" allowfullscreen></iframe></center> "
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 :
# 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
2020-02-23 15:32:47 +00:00
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 :
if ' " https:// ' + site in content :
url = content . split ( ' " https:// ' + site ) [ 1 ]
if ' " ' in url :
url = url . split ( ' " ' ) [ 0 ] . replace ( ' /watch/ ' , ' /embed/ ' )
2020-02-23 15:32:47 +00:00
content = \
content + " <center><iframe loading= \" lazy \" sandbox= \" allow-same-origin allow-scripts \" src= \" https:// " + \
site + url + " \" width= \" " + str ( width ) + " \" height= \" " + str ( height ) + \
" \" frameborder= \" 0 \" allow= \" autoplay; fullscreen \" allowfullscreen></iframe></center> "
2019-10-18 16:57:33 +00:00
return content
2019-08-20 16:40:39 +00:00
return content
2019-09-07 08:57:52 +00:00
def addEmbeddedElements ( translate : { } , content : str ) - > str :
2019-08-30 11:47:30 +00:00
""" Adds embedded elements for various media types
"""
2019-09-07 08:57:52 +00:00
content = addEmbeddedVideoFromSites ( translate , content )
content = addEmbeddedAudio ( translate , content )
return addEmbeddedVideo ( translate , content )
2019-09-02 10:51:49 +00:00
def followerApprovalActive ( baseDir : str , nickname : str , domain : str ) - > bool :
""" Returns true if the given account requires follower approval
"""
manuallyApprovesFollowers = False
actorFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' .json '
if os . path . isfile ( actorFilename ) :
2019-10-22 11:55:06 +00:00
actorJson = loadJson ( actorFilename )
if actorJson :
2019-09-02 10:51:49 +00:00
if actorJson . get ( ' manuallyApprovesFollowers ' ) :
manuallyApprovesFollowers = actorJson [ ' manuallyApprovesFollowers ' ]
return manuallyApprovesFollowers
2019-11-25 13:34:44 +00:00
def insertQuestion ( baseDir : str , translate : { } , \
2019-11-25 10:29:09 +00:00
nickname : str , domain : str , port : int , \
content : str , \
2019-09-07 08:57:52 +00:00
postJsonObject : { } , pageNumber : int ) - > str :
2019-09-06 15:08:32 +00:00
""" Inserts question selection into a post
"""
if not isQuestion ( postJsonObject ) :
return content
if len ( postJsonObject [ ' object ' ] [ ' oneOf ' ] ) == 0 :
return content
2019-11-25 12:33:05 +00:00
messageId = postJsonObject [ ' id ' ] . replace ( ' /activity ' , ' ' )
if ' # ' in messageId :
messageId = messageId . split ( ' # ' , 1 ) [ 0 ]
2019-09-06 16:37:33 +00:00
pageNumberStr = ' '
if pageNumber :
pageNumberStr = ' ?page= ' + str ( pageNumber )
2019-11-25 13:34:44 +00:00
votesFilename = \
baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /questions.txt '
2019-11-26 20:22:52 +00:00
2019-12-10 19:31:45 +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 ( ) :
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
content + = ' <div class= " question " > '
2020-02-23 15:32:47 +00:00
content + = \
' <form method= " POST " action= " /users/ ' + \
nickname + ' /question ' + pageNumberStr + ' " > '
content + = \
' <input type= " hidden " name= " messageId " value= " ' + messageId + ' " ><br> '
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-02-23 15:32:47 +00:00
content + = \
' <input type= " radio " name= " answer " value= " ' + \
choice [ ' name ' ] + ' " > ' + choice [ ' name ' ] + ' <br><br> '
content + = \
' <input type= " submit " value= " ' + \
translate [ ' Vote ' ] + ' " class= " vote " ><br><br> '
2019-11-25 13:34:44 +00:00
content + = ' </form></div> '
else :
# show the responses to a question
content + = ' <div class= " questionresult " > '
# get the maximum number of votes
maxVotes = 1
for questionOption in postJsonObject [ ' object ' ] [ ' oneOf ' ] :
if not questionOption . get ( ' name ' ) :
continue
if not questionOption . get ( ' replies ' ) :
continue
2019-12-10 18:05:38 +00:00
votes = 0
try :
votes = int ( questionOption [ ' replies ' ] [ ' totalItems ' ] )
except :
pass
2019-11-25 13:34:44 +00:00
if votes > maxVotes :
maxVotes = int ( votes + 1 )
# show the votes as sliders
questionCtr = 1
for questionOption in postJsonObject [ ' object ' ] [ ' oneOf ' ] :
if not questionOption . get ( ' name ' ) :
continue
if not questionOption . get ( ' replies ' ) :
continue
2019-12-10 18:05:38 +00:00
votes = 0
try :
votes = int ( questionOption [ ' replies ' ] [ ' totalItems ' ] )
except :
pass
2019-11-25 13:34:44 +00:00
votesPercent = str ( int ( votes * 100 / maxVotes ) )
2020-02-23 15:32:47 +00:00
content + = \
' <p><input type= " text " title= " ' + str ( votes ) + \
' " name= " skillName ' + str ( questionCtr ) + \
' " value= " ' + questionOption [ ' name ' ] + \
' ( ' + str ( votes ) + ' ) " style= " width:40 % " > '
content + = \
' <input type= " range " min= " 1 " max= " 100 " class= " slider " title= " ' + \
str ( votes ) + ' " name= " skillValue ' + str ( questionCtr ) + \
' " value= " ' + votesPercent + ' " ></p> '
2019-11-25 13:34:44 +00:00
questionCtr + = 1
content + = ' </div> '
2019-09-06 15:08:32 +00:00
return content
2019-09-23 11:44:43 +00:00
def addEmojiToDisplayName ( baseDir : str , httpPrefix : str , \
nickname : str , domain : str , \
2019-09-23 12:19:00 +00:00
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
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 )
# convert the emoji dictionary to a list
emojiTagsList = [ ]
for tagName , tag in emojiTags . items ( ) :
emojiTagsList . append ( tag )
print ( ' TAG: emoji tags list: ' + str ( emojiTagsList ) )
if not inProfileName :
displayName = replaceEmojiFromTags ( displayName , emojiTagsList , ' post header ' )
else :
displayName = replaceEmojiFromTags ( displayName , emojiTagsList , ' profile ' )
print ( ' TAG: displayName after tags 2: ' + displayName )
# remove any stray emoji
while ' : ' in displayName :
if ' :// ' in displayName :
break
emojiStr = displayName . split ( ' : ' ) [ 1 ]
prevDisplayName = displayName
displayName = displayName . replace ( ' : ' + emojiStr + ' : ' , ' ' ) . strip ( )
if prevDisplayName == displayName :
break
print ( ' TAG: displayName after tags 3: ' + displayName )
print ( ' TAG: displayName after tag replacements: ' + displayName )
2019-10-09 17:03:07 +00:00
2019-09-23 11:44:43 +00:00
return displayName
2019-10-18 12:00:14 +00:00
def postContainsPublic ( postJsonObject : { } ) - > bool :
""" Does the given post contain #Public
"""
containsPublic = False
if not postJsonObject [ ' object ' ] . get ( ' to ' ) :
return containsPublic
for toAddress in postJsonObject [ ' object ' ] [ ' to ' ] :
if toAddress . endswith ( ' #Public ' ) :
containsPublic = True
break
if not containsPublic :
if postJsonObject [ ' object ' ] . get ( ' cc ' ) :
for toAddress in postJsonObject [ ' object ' ] [ ' cc ' ] :
if toAddress . endswith ( ' #Public ' ) :
containsPublic = True
break
return containsPublic
2019-10-19 11:53:57 +00:00
def loadIndividualPostAsHtmlFromCache ( baseDir : str , nickname : str , domain : str , \
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-02-23 15:32:47 +00:00
cachedPostFilename = \
getCachedPostFilename ( baseDir , nickname , domain , postJsonObject )
2019-10-19 12:07:12 +00:00
2019-10-19 11:53:57 +00:00
postHtml = ' '
2019-11-29 23:04:37 +00:00
if not cachedPostFilename :
return postHtml
2019-10-19 11:53:57 +00:00
if not os . path . isfile ( cachedPostFilename ) :
return postHtml
tries = 0
while tries < 3 :
try :
with open ( cachedPostFilename , ' r ' ) as file :
postHtml = file . read ( )
break
except Exception as e :
print ( e )
# no sleep
tries + = 1
if postHtml :
return postHtml
2019-10-18 12:00:14 +00:00
2019-10-19 11:53:57 +00:00
def saveIndividualPostAsHtmlToCache ( baseDir : str , nickname : str , domain : str , \
postJsonObject : { } , postHtml : str ) - > bool :
""" Saves the given html for a post to a cache file
This is so that it can be quickly reloaded on subsequent refresh of the timeline
"""
2020-02-23 15:32:47 +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 :
with open ( cachedPostFilename , ' w ' ) as fp :
fp . write ( postHtml )
return True
except Exception as e :
print ( ' ERROR: saving post to cache ' + str ( e ) )
return False
2019-11-24 11:28:58 +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
"""
2019-11-24 11:28:58 +00:00
# if on the bookmarks timeline then remain there
if boxName == ' tlbookmarks ' :
postHtml = postHtml . replace ( ' ?tl=inbox ' , ' ?tl=tlbookmarks ' )
return postHtml . replace ( ' ;-999; ' , ' ; ' + str ( pageNumber ) + ' ; ' ) . replace ( ' ?page=-999 ' , ' ?page= ' + str ( pageNumber ) )
2020-02-23 15:32:47 +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
"""
2019-12-01 16:15:41 +00:00
isMuted = postJsonObject . get ( ' muted ' )
if isMuted == True or isMuted == False :
return isMuted
2019-12-01 13:45:30 +00:00
postDir = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain
2020-02-23 15:32:47 +00:00
muteFilename = \
postDir + ' /inbox/ ' + messageId . replace ( ' / ' , ' # ' ) + ' .json.muted '
2019-12-01 13:45:30 +00:00
if os . path . isfile ( muteFilename ) :
return True
2020-02-23 15:32:47 +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-02-23 15:32:47 +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-02-11 09:38:30 +00:00
2019-11-24 17:40:31 +00:00
def individualPostAsHtml ( recentPostsCache : { } , maxRecentPosts : int , \
iconsDir : str , translate : { } , \
2019-09-07 08:57:52 +00:00
pageNumber : int , baseDir : str , \
2019-07-29 19:46:30 +00:00
session , wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , \
2019-07-30 12:47:42 +00:00
postJsonObject : { } , \
2019-11-19 15:27:43 +00:00
avatarUrl : str , showAvatarOptions : bool ,
2019-08-14 20:12:27 +00:00
allowDeletion : bool , \
2019-11-19 15:27:43 +00:00
httpPrefix : str , projectVersion : str , \
boxName : str , showRepeats = True , \
2019-09-02 10:51:49 +00:00
showIcons = False , \
2019-09-02 11:59:15 +00:00
manuallyApprovesFollowers = False , \
2019-10-19 10:43:34 +00:00
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
"""
2019-11-03 16:11:52 +00:00
postActor = postJsonObject [ ' actor ' ]
2019-11-06 11:39:41 +00:00
# ZZZzzz
if isPersonSnoozed ( baseDir , nickname , domain , postActor ) :
return ' '
2019-11-28 13:43:23 +00:00
avatarPosition = ' '
2019-11-28 13:41:11 +00:00
messageId = ' '
if postJsonObject . get ( ' id ' ) :
messageId = postJsonObject [ ' id ' ] . replace ( ' /activity ' , ' ' )
2019-11-28 13:39:03 +00:00
messageIdStr = ' '
if messageId :
messageIdStr = ' ; ' + messageId
fullDomain = domain
if port :
if port != 80 and port != 443 :
if ' : ' not in domain :
fullDomain = domain + ' : ' + str ( port )
pageNumberParam = ' '
if pageNumber :
pageNumberParam = ' ?page= ' + str ( pageNumber )
2019-10-23 11:22:42 +00:00
if not showPublicOnly and storeToCache and boxName != ' tlmedia ' :
2019-11-03 16:11:52 +00:00
# update avatar if needed
if not avatarUrl :
2020-02-23 15:32:47 +00:00
avatarUrl = \
getPersonAvatarUrl ( baseDir , postActor , personCache )
updateAvatarImageCache ( session , baseDir , httpPrefix , \
postActor , avatarUrl , personCache )
2019-11-03 16:11:52 +00:00
2019-10-19 11:53:57 +00:00
postHtml = \
loadIndividualPostAsHtmlFromCache ( baseDir , nickname , domain , \
postJsonObject )
if postHtml :
2019-11-24 17:44:31 +00:00
postHtml = preparePostFromHtmlCache ( postHtml , boxName , pageNumber )
updateRecentPostsCache ( recentPostsCache , maxRecentPosts , \
postJsonObject , postHtml )
return postHtml
2019-10-19 10:34:06 +00:00
2019-11-28 13:39:03 +00:00
if not avatarUrl :
2020-02-23 15:32:47 +00:00
avatarUrl = \
getPersonAvatarUrl ( baseDir , postActor , personCache )
avatarUrl = \
updateAvatarImageCache ( session , baseDir , httpPrefix , \
postActor , avatarUrl , personCache )
2019-11-28 13:39:03 +00:00
else :
2020-02-23 15:32:47 +00:00
updateAvatarImageCache ( session , baseDir , httpPrefix , \
postActor , avatarUrl , personCache )
2019-11-28 13:39:03 +00:00
if not avatarUrl :
avatarUrl = postActor + ' /avatar.png '
if fullDomain not in postActor :
inboxUrl , pubKeyId , pubKey , fromPersonId , sharedInbox , capabilityAcquisition , avatarUrl2 , displayName = \
getPersonBox ( baseDir , session , wfRequest , personCache , \
projectVersion , httpPrefix , nickname , domain , ' outbox ' )
if avatarUrl2 :
avatarUrl = avatarUrl2
if displayName :
if ' : ' in displayName :
displayName = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
displayName , False )
titleStr = displayName + ' ' + titleStr
avatarLink = ' <a href= " ' + postActor + ' " > '
2020-02-23 15:32:47 +00:00
avatarLink + = \
' <img loading= " lazy " src= " ' + avatarUrl + ' " title= " ' + \
translate [ ' Show profile ' ] + ' " alt= " " ' + avatarPosition + ' /></a> '
2019-11-28 13:39:03 +00:00
if showAvatarOptions and fullDomain + ' /users/ ' + nickname not in postActor :
2020-02-23 15:32:47 +00:00
avatarLink = \
' <a href= " /users/ ' + nickname + ' ?options= ' + postActor + \
' ; ' + str ( pageNumber ) + ' ; ' + avatarUrl + messageIdStr + ' " > '
avatarLink + = \
' <img loading= " lazy " title= " ' + \
translate [ ' Show options for this person ' ] + \
' " src= " ' + avatarUrl + ' " ' + avatarPosition + ' /></a> '
2020-01-02 23:04:43 +00:00
avatarImageInPost = ' <div class= " timeline-avatar " > ' + avatarLink + ' </div> '
2019-11-28 13:39:03 +00:00
2019-11-17 14:14:59 +00:00
# don't create new html within the bookmarks timeline
# it should already have been created for the inbox
if boxName == ' tlbookmarks ' :
return ' '
2020-02-23 15:32:47 +00:00
timelinePostBookmark = \
postJsonObject [ ' id ' ] . replace ( ' /activity ' , ' ' ) . replace ( ' :// ' , ' - ' ) . replace ( ' / ' , ' - ' )
2019-11-19 15:27:43 +00:00
2019-08-25 17:49:07 +00:00
# If this is the inbox timeline then don't show the repeat icon on any DMs
2019-08-25 18:08:25 +00:00
showRepeatIcon = showRepeats
2020-02-14 17:16:01 +00:00
isPublicRepeat = False
2019-08-25 17:53:20 +00:00
showDMicon = False
2019-08-25 17:49:07 +00:00
if showRepeats :
2020-02-11 09:42:11 +00:00
if isDM ( postJsonObject ) :
2019-08-25 17:53:20 +00:00
showDMicon = True
2020-02-14 17:16:01 +00:00
showRepeatIcon = False
2020-02-14 17:30:09 +00:00
else :
2020-02-11 09:42:11 +00:00
if not isPublicPost ( postJsonObject ) :
2020-02-14 17:16:01 +00:00
isPublicRepeat = True
2019-08-25 17:49:07 +00:00
2019-07-31 10:09:02 +00:00
titleStr = ' '
2019-09-28 11:29:42 +00:00
galleryStr = ' '
2019-08-20 13:13:44 +00:00
isAnnounced = False
2019-07-31 10:09:02 +00:00
if postJsonObject [ ' type ' ] == ' Announce ' :
2019-09-30 15:52:37 +00:00
postJsonAnnounce = \
downloadAnnounce ( session , baseDir , httpPrefix , nickname , domain , postJsonObject , projectVersion )
2019-09-28 16:05:01 +00:00
if not postJsonAnnounce :
return ' '
postJsonObject = postJsonAnnounce
isAnnounced = True
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 ' '
2019-09-02 11:59:15 +00:00
2019-08-12 13:47:17 +00:00
isModerationPost = False
if postJsonObject [ ' object ' ] . get ( ' moderationStatus ' ) :
isModerationPost = True
2019-07-21 11:20:49 +00:00
containerClass = ' container '
2019-07-30 12:47:42 +00:00
containerClassIcons = ' containericons '
2019-07-21 11:20:49 +00:00
timeClass = ' time-right '
2019-10-31 09:59:06 +00:00
actorNickname = getNicknameFromActor ( postActor )
2019-10-21 10:07:44 +00:00
if not actorNickname :
# single user instance
2019-10-21 12:06:13 +00:00
actorNickname = ' dev '
2019-10-31 09:59:06 +00:00
actorDomain , actorPort = getDomainFromActor ( postActor )
2019-08-20 13:13:44 +00:00
2019-10-31 09:59:06 +00:00
displayName = getDisplayName ( baseDir , postActor , personCache )
2019-08-22 18:36:07 +00:00
if displayName :
2019-10-18 17:07:45 +00:00
if ' : ' in displayName :
displayName = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
displayName , False )
2020-02-23 15:32:47 +00:00
titleStr + = \
' <a href= " /users/ ' + nickname + ' ?options= ' + postActor + \
' ; ' + str ( pageNumber ) + ' ; ' + avatarUrl + messageIdStr + \
' " > ' + displayName + ' </a> '
2019-08-22 12:41:16 +00:00
else :
2019-10-21 10:04:05 +00:00
if not messageId :
2019-10-29 20:23:49 +00:00
#pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print ( ' ERROR: no messageId ' )
if not actorNickname :
2019-10-29 20:23:49 +00:00
#pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print ( ' ERROR: no actorNickname ' )
if not actorDomain :
2019-10-29 20:23:49 +00:00
#pprint(postJsonObject)
2019-10-21 10:04:05 +00:00
print ( ' ERROR: no actorDomain ' )
2020-02-23 15:32:47 +00:00
titleStr + = \
' <a href= " /users/ ' + nickname + ' ?options= ' + postActor + \
' ; ' + str ( pageNumber ) + ' ; ' + avatarUrl + messageIdStr + \
' " >@ ' + actorNickname + ' @ ' + actorDomain + ' </a> '
2019-08-25 17:22:24 +00:00
2019-08-25 17:53:20 +00:00
# Show a DM icon for DMs in the inbox timeline
if showDMicon :
2020-02-23 15:32:47 +00:00
titleStr = \
titleStr + ' <img loading= " lazy " src= " / ' + \
iconsDir + ' /dm.png " class= " DMicon " /> '
2019-08-25 17:53:20 +00:00
2019-11-28 12:34:02 +00:00
replyStr = ' '
if showIcons :
2019-12-01 19:00:28 +00:00
replyToLink = postJsonObject [ ' object ' ] [ ' id ' ]
2019-11-28 12:34:02 +00:00
if postJsonObject [ ' object ' ] . get ( ' attributedTo ' ) :
replyToLink + = ' ?mention= ' + postJsonObject [ ' object ' ] [ ' attributedTo ' ]
if postJsonObject [ ' object ' ] . get ( ' content ' ) :
2020-02-23 15:32:47 +00:00
mentionedActors = \
getMentionsFromHtml ( postJsonObject [ ' object ' ] [ ' content ' ] )
2019-11-28 12:34:02 +00:00
if mentionedActors :
for actorUrl in mentionedActors :
if ' ?mention= ' + actorUrl not in replyToLink :
replyToLink + = ' ?mention= ' + actorUrl
if len ( replyToLink ) > 500 :
break
replyToLink + = pageNumberParam
replyStr = ' '
2020-02-14 17:16:01 +00:00
if isPublicRepeat :
2020-02-11 10:24:22 +00:00
replyStr + = \
' <a href= " /users/ ' + nickname + ' ?replyto= ' + replyToLink + \
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' " title= " ' + translate [ ' Reply to this post ' ] + ' " > '
else :
if isDM ( postJsonObject ) :
2019-11-28 12:34:02 +00:00
replyStr + = \
2020-02-11 10:24:22 +00:00
' <a href= " /users/ ' + nickname + ' ?replydm= ' + replyToLink + \
2019-11-28 12:34:02 +00:00
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' " title= " ' + translate [ ' Reply to this post ' ] + ' " > '
else :
replyStr + = \
' <a href= " /users/ ' + nickname + ' ?replyfollowers= ' + replyToLink + \
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' " title= " ' + translate [ ' Reply to this post ' ] + ' " > '
2020-02-11 10:24:22 +00:00
2020-02-23 15:32:47 +00:00
replyStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' Reply to this post ' ] + ' | " alt= " ' + \
translate [ ' Reply to this post ' ] + \
' | " src= " / ' + iconsDir + ' /reply.png " /></a> '
2019-11-28 12:34:02 +00:00
2019-11-28 12:00:01 +00:00
announceStr = ' '
if not isModerationPost and showRepeatIcon :
# don't allow announce/repeat of your own posts
announceIcon = ' repeat_inactive.png '
announceLink = ' repeat '
2020-02-14 17:16:01 +00:00
if not isPublicRepeat :
announceLink = ' repeatprivate '
2019-11-28 12:00:01 +00:00
announceTitle = translate [ ' Repeat this post ' ]
if announcedByPerson ( postJsonObject , nickname , fullDomain ) :
announceIcon = ' repeat.png '
2020-02-14 17:16:01 +00:00
if not isPublicRepeat :
announceLink = ' unrepeatprivate '
2019-11-28 12:00:01 +00:00
announceTitle = translate [ ' Undo the repeat ' ]
announceStr = \
2020-02-23 15:32:47 +00:00
' <a href= " /users/ ' + nickname + ' ? ' + announceLink + \
' = ' + postJsonObject [ ' object ' ] [ ' id ' ] + pageNumberParam + \
2019-11-28 12:00:01 +00:00
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' ?bm= ' + timelinePostBookmark + \
' ?tl= ' + boxName + ' " title= " ' + announceTitle + ' " > '
2020-02-23 15:32:47 +00:00
announceStr + = \
' <img loading= " lazy " title= " ' + translate [ ' Repeat this post ' ] + \
' | " alt= " ' + translate [ ' Repeat this post ' ] + \
' | " src= " / ' + iconsDir + ' / ' + announceIcon + ' " /></a> '
2019-11-28 12:00:01 +00:00
likeStr = ' '
if not isModerationPost :
likeIcon = ' like_inactive.png '
likeLink = ' like '
likeTitle = translate [ ' Like this post ' ]
if noOfLikes ( postJsonObject ) > 0 :
likeIcon = ' like.png '
if likedByPerson ( postJsonObject , nickname , fullDomain ) :
likeLink = ' unlike '
likeTitle = translate [ ' Undo the like ' ]
likeStr = \
' <a href= " /users/ ' + nickname + ' ? ' + \
2019-12-01 18:49:54 +00:00
likeLink + ' = ' + postJsonObject [ ' object ' ] [ ' id ' ] + pageNumberParam + \
2019-11-28 12:00:01 +00:00
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' ?bm= ' + timelinePostBookmark + \
' ?tl= ' + boxName + ' " title= " ' + likeTitle + ' " > '
2020-02-23 15:32:47 +00:00
likeStr + = \
' <img loading= " lazy " title= " ' + likeTitle + ' | " alt= " ' + likeTitle + \
' | " src= " / ' + iconsDir + ' / ' + likeIcon + ' " /></a> '
2019-11-28 12:00:01 +00:00
bookmarkStr = ' '
if not isModerationPost :
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 ' ]
bookmarkStr = \
' <a href= " /users/ ' + nickname + ' ? ' + \
2019-12-01 19:00:28 +00:00
bookmarkLink + ' = ' + postJsonObject [ ' object ' ] [ ' id ' ] + pageNumberParam + \
2019-11-28 12:00:01 +00:00
' ?actor= ' + postJsonObject [ ' actor ' ] + \
' ?bm= ' + timelinePostBookmark + \
' ?tl= ' + boxName + ' " title= " ' + bookmarkTitle + ' " > '
2020-02-23 15:32:47 +00:00
bookmarkStr + = \
' <img loading= " lazy " title= " ' + bookmarkTitle + ' | " alt= " ' + \
bookmarkTitle + ' | " src= " / ' + iconsDir + ' / ' + bookmarkIcon + ' " /></a> '
2019-11-28 12:00:01 +00:00
2019-12-01 14:34:10 +00:00
isMuted = postIsMuted ( baseDir , nickname , domain , postJsonObject , messageId )
2019-12-01 13:45:30 +00:00
2019-11-28 12:00:01 +00:00
deleteStr = ' '
2019-12-01 13:45:30 +00:00
muteStr = ' '
2019-11-28 12:00:01 +00:00
if allowDeletion or \
( ' / ' + fullDomain + ' / ' in postActor and \
2019-12-01 17:19:27 +00:00
messageId . startswith ( postActor ) ) :
if ' /users/ ' + nickname + ' / ' in messageId :
2020-02-23 15:32:47 +00:00
deleteStr = \
' <a href= " /users/ ' + nickname + ' ?delete= ' + messageId + pageNumberParam + \
' " title= " ' + translate [ ' Delete this post ' ] + ' " > '
deleteStr + = \
' <img loading= " lazy " alt= " ' + translate [ ' Delete this post ' ] + \
' | " title= " ' + translate [ ' Delete this post ' ] + \
' | " src= " / ' + iconsDir + ' /delete.png " /></a> '
2019-12-01 13:45:30 +00:00
else :
if not isMuted :
2020-02-23 15:32:47 +00:00
muteStr = \
' <a href= " /users/ ' + nickname + \
' ?mute= ' + messageId + pageNumberParam + ' ?tl= ' + boxName + \
' ?bm= ' + timelinePostBookmark + \
' " title= " ' + translate [ ' Mute this post ' ] + ' " > '
muteStr + = \
' <img loading= " lazy " alt= " ' + \
translate [ ' Mute this post ' ] + \
' | " title= " ' + translate [ ' Mute this post ' ] + \
' | " src= " / ' + iconsDir + ' /mute.png " /></a> '
2019-12-01 13:45:30 +00:00
else :
2020-02-23 15:32:47 +00:00
muteStr = \
' <a href= " /users/ ' + nickname + ' ?unmute= ' + messageId + \
pageNumberParam + ' ?tl= ' + boxName + ' ?bm= ' + \
timelinePostBookmark + ' " title= " ' + translate [ ' Undo mute ' ] + ' " > '
muteStr + = \
' <img loading= " lazy " alt= " ' + translate [ ' Undo mute ' ] + \
' | " title= " ' + translate [ ' Undo mute ' ] + \
' | " src= " / ' + iconsDir + ' /unmute.png " /></a> '
2019-12-01 13:45:30 +00:00
2019-09-24 11:05:47 +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 ' ) :
2019-10-31 09:59:06 +00:00
if postJsonObject [ ' object ' ] [ ' attributedTo ' ] . startswith ( postActor ) :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + translate [ ' announces ' ] + \
' " alt= " ' + translate [ ' announces ' ] + \
' " src= " / ' + iconsDir + \
' /repeat_inactive.png " class= " announceOrReply " /> '
2019-08-25 17:22:24 +00:00
else :
2020-02-23 15:32:47 +00:00
announceNickname = \
getNicknameFromActor ( postJsonObject [ ' object ' ] [ ' attributedTo ' ] )
2019-09-25 12:48:12 +00:00
if announceNickname :
2020-02-23 15:32:47 +00:00
announceDomain , announcePort = \
getDomainFromActor ( postJsonObject [ ' object ' ] [ ' attributedTo ' ] )
getPersonFromCache ( baseDir , \
postJsonObject [ ' object ' ] [ ' attributedTo ' ] , \
personCache )
announceDisplayName = \
getDisplayName ( baseDir , \
postJsonObject [ ' object ' ] [ ' attributedTo ' ] , \
personCache )
2019-09-25 12:48:12 +00:00
if announceDisplayName :
2019-10-18 12:55:53 +00:00
if ' : ' in announceDisplayName :
announceDisplayName = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
announceDisplayName , False )
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' announces ' ] + ' " alt= " ' + \
translate [ ' announces ' ] + ' " src= " / ' + \
iconsDir + ' /repeat_inactive.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' id ' ] + ' " > ' + announceDisplayName + ' </a> '
2019-09-25 12:48:12 +00:00
# show avatar of person replied to
announceActor = postJsonObject [ ' object ' ] [ ' attributedTo ' ]
2020-02-23 15:32:47 +00:00
announceAvatarUrl = \
getPersonAvatarUrl ( baseDir , announceActor , personCache )
2019-09-25 12:48:12 +00:00
if announceAvatarUrl :
replyAvatarImageInPost = \
' <div class= " timeline-avatar-reply " > ' \
2020-02-23 15:32:47 +00:00
' <a href= " /users/ ' + nickname + ' ?options= ' + \
announceActor + ' ; ' + str ( pageNumber ) + \
' ; ' + announceAvatarUrl + messageIdStr + ' " > ' \
2019-10-18 19:25:21 +00:00
' <img loading= " lazy " src= " ' + announceAvatarUrl + ' " ' \
2019-10-04 08:42:53 +00:00
' title= " ' + translate [ ' Show options for this person ' ] + \
2019-11-01 22:35:33 +00:00
' " alt= " " ' + avatarPosition + ' /></a></div> '
2019-09-25 12:48:12 +00:00
else :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + translate [ ' announces ' ] + \
' " alt= " ' + translate [ ' announces ' ] + ' " src= " / ' + iconsDir + \
' /repeat_inactive.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' id ' ] + ' " >@ ' + \
announceNickname + ' @ ' + announceDomain + ' </a> '
2019-09-25 12:48:12 +00:00
else :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' announces ' ] + ' " alt= " ' + \
translate [ ' announces ' ] + ' " src= " / ' + iconsDir + \
' /repeat_inactive.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' id ' ] + ' " >@unattributed</a> '
2019-08-22 12:41:16 +00:00
else :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + translate [ ' announces ' ] + \
' " alt= " ' + translate [ ' announces ' ] + ' " src= " / ' + iconsDir + \
' /repeat_inactive.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' id ' ] + ' " >@unattributed</a> '
2019-07-21 13:03:57 +00:00
else :
2019-08-25 17:22:24 +00:00
if postJsonObject [ ' object ' ] . get ( ' inReplyTo ' ) :
containerClassIcons = ' containericons darker '
containerClass = ' container darker '
#avatarPosition=' class="right"'
2019-10-31 09:59:06 +00:00
if postJsonObject [ ' object ' ] [ ' inReplyTo ' ] . startswith ( postActor ) :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' replying to themselves ' ] + \
' " alt= " ' + translate [ ' replying to themselves ' ] + \
' " src= " / ' + iconsDir + ' /reply.png " class= " announceOrReply " /> '
2019-08-25 17:22:24 +00:00
else :
2019-09-24 10:09:35 +00:00
if ' /statuses/ ' in postJsonObject [ ' object ' ] [ ' inReplyTo ' ] :
2020-02-23 15:32:47 +00:00
replyActor = \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] . split ( ' /statuses/ ' ) [ 0 ]
2019-10-09 13:24:14 +00:00
replyNickname = getNicknameFromActor ( replyActor )
2019-09-24 10:09:35 +00:00
if replyNickname :
2019-10-09 13:24:14 +00:00
replyDomain , replyPort = getDomainFromActor ( replyActor )
2019-09-24 10:09:35 +00:00
if replyNickname and replyDomain :
2019-10-09 13:44:01 +00:00
getPersonFromCache ( baseDir , replyActor , personCache )
2019-10-09 13:24:14 +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 :
replyDisplayName = \
addEmojiToDisplayName ( baseDir , httpPrefix , \
nickname , domain , \
replyDisplayName , False )
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' replying to ' ] + ' " alt= " ' + \
translate [ ' replying to ' ] + ' " src= " / ' + \
iconsDir + ' /reply.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] + \
' " > ' + replyDisplayName + ' </a> '
2019-09-24 11:05:47 +00:00
2019-09-24 11:11:45 +00:00
# show avatar of person replied to
2020-02-23 15:32:47 +00:00
replyAvatarUrl = \
getPersonAvatarUrl ( baseDir , replyActor , personCache )
2019-09-24 11:05:47 +00:00
if replyAvatarUrl :
2019-11-12 10:42:27 +00:00
replyAvatarImageInPost = ' <div class= " timeline-avatar-reply " > '
2020-02-23 15:32:47 +00:00
replyAvatarImageInPost + = \
' <a href= " /users/ ' + nickname + ' ?options= ' + replyActor + \
' ; ' + str ( pageNumber ) + ' ; ' + replyAvatarUrl + messageIdStr + ' " > '
replyAvatarImageInPost + = \
' <img loading= " lazy " src= " ' + replyAvatarUrl + ' " '
replyAvatarImageInPost + = \
' title= " ' + translate [ ' Show profile ' ]
replyAvatarImageInPost + = \
' " alt= " " ' + avatarPosition + ' /></a></div> '
2019-09-24 10:09:35 +00:00
else :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' replying to ' ] + ' " alt= " ' + \
translate [ ' replying to ' ] + ' " src= " / ' + \
iconsDir + ' /reply.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] + ' " >@ ' + \
replyNickname + ' @ ' + replyDomain + ' </a> '
2019-09-24 10:09:35 +00:00
else :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + \
translate [ ' replying to ' ] + ' " alt= " ' + \
translate [ ' replying to ' ] + ' " src= " / ' + \
iconsDir + ' /reply.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] + ' " >@unknown</a> '
2019-09-24 10:09:35 +00:00
else :
2020-02-23 15:32:47 +00:00
postDomain = \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] . replace ( ' https:// ' , ' ' ) . replace ( ' http:// ' , ' ' ) . replace ( ' dat:// ' , ' ' ) . replace ( ' i2p:// ' , ' ' )
2019-09-24 10:09:35 +00:00
if ' / ' in postDomain :
postDomain = postDomain . split ( ' / ' , 1 ) [ 0 ]
if postDomain :
2020-02-23 15:32:47 +00:00
titleStr + = \
' <img loading= " lazy " title= " ' + translate [ ' replying to ' ] + \
' " alt= " ' + translate [ ' replying to ' ] + ' " src= " / ' + \
iconsDir + ' /reply.png " class= " announceOrReply " /> <a href= " ' + \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] + ' " > ' + postDomain + ' </a> '
2019-07-21 11:20:49 +00:00
attachmentStr = ' '
2019-08-30 17:39:59 +00:00
if postJsonObject [ ' object ' ] . get ( ' attachment ' ) :
2019-07-21 11:20:49 +00:00
if isinstance ( postJsonObject [ ' object ' ] [ ' attachment ' ] , list ) :
attachmentCtr = 0
2019-08-20 15:38:24 +00:00
attachmentStr + = ' <div class= " media " > '
2019-07-21 11:20:49 +00:00
for attach in postJsonObject [ ' object ' ] [ ' attachment ' ] :
if attach . get ( ' mediaType ' ) and attach . get ( ' url ' ) :
mediaType = attach [ ' mediaType ' ]
imageDescription = ' '
if attach . get ( ' name ' ) :
2019-09-08 21:52:42 +00:00
imageDescription = attach [ ' name ' ] . replace ( ' " ' , " ' " )
2019-07-21 11:20:49 +00:00
if mediaType == ' image/png ' or \
mediaType == ' image/jpeg ' or \
mediaType == ' image/gif ' :
if attach [ ' url ' ] . endswith ( ' .png ' ) or \
attach [ ' url ' ] . endswith ( ' .jpg ' ) or \
attach [ ' url ' ] . endswith ( ' .jpeg ' ) or \
2019-11-14 15:11:20 +00:00
attach [ ' url ' ] . endswith ( ' .webp ' ) or \
2019-07-21 11:20:49 +00:00
attach [ ' url ' ] . endswith ( ' .gif ' ) :
if attachmentCtr > 0 :
attachmentStr + = ' <br> '
2019-09-28 11:29:42 +00:00
if boxName == ' tlmedia ' :
2019-11-30 09:41:58 +00:00
galleryStr + = ' <div class= " gallery " > \n '
2019-12-03 09:40:11 +00:00
if not isMuted :
galleryStr + = ' <a href= " ' + attach [ ' url ' ] + ' " > \n '
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <img loading= " lazy " src= " ' + \
attach [ ' url ' ] + ' " alt= " " title= " " > \n '
2019-12-03 09:40:11 +00:00
galleryStr + = ' </a> \n '
2019-11-28 10:48:51 +00:00
if postJsonObject [ ' object ' ] . get ( ' url ' ) :
imagePostUrl = postJsonObject [ ' object ' ] [ ' url ' ]
else :
2019-12-01 19:00:28 +00:00
imagePostUrl = postJsonObject [ ' object ' ] [ ' id ' ]
2019-12-03 09:40:11 +00:00
if imageDescription and not isMuted :
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <a href= " ' + imagePostUrl + \
' " class= " gallerytext " ><div class= " gallerytext " > ' + \
imageDescription + ' </div></a> \n '
2019-11-28 12:43:55 +00:00
else :
galleryStr + = ' <label class= " transparent " >---</label><br> '
2019-11-28 13:17:32 +00:00
galleryStr + = ' <div class= " mediaicons " > \n '
2020-02-23 14:24:11 +00:00
galleryStr + = \
' ' + replyStr + announceStr + likeStr + \
bookmarkStr + deleteStr + muteStr + ' \n '
2019-11-28 14:01:29 +00:00
galleryStr + = ' </div> \n '
galleryStr + = ' <div class= " mediaavatar " > \n '
galleryStr + = ' ' + avatarLink + ' \n '
2019-11-28 13:17:32 +00:00
galleryStr + = ' </div> \n '
2019-11-28 10:18:23 +00:00
galleryStr + = ' </div> \n '
2019-11-12 10:42:27 +00:00
attachmentStr + = ' <a href= " ' + attach [ ' url ' ] + ' " > '
2020-02-23 14:24:11 +00:00
attachmentStr + = \
' <img loading= " lazy " src= " ' + attach [ ' url ' ] + \
' " alt= " ' + imageDescription + ' " title= " ' + \
imageDescription + ' " class= " attachment " ></a> \n '
2019-08-30 12:11:58 +00:00
attachmentCtr + = 1
2019-08-30 12:17:03 +00:00
elif mediaType == ' video/mp4 ' or \
mediaType == ' video/webm ' or \
mediaType == ' video/ogv ' :
2019-08-30 12:11:58 +00:00
extension = ' .mp4 '
if attach [ ' url ' ] . endswith ( ' .webm ' ) :
extension = ' .webm '
elif attach [ ' url ' ] . endswith ( ' .ogv ' ) :
extension = ' .ogv '
if attach [ ' url ' ] . endswith ( extension ) :
if attachmentCtr > 0 :
attachmentStr + = ' <br> '
2019-10-23 13:39:08 +00:00
if boxName == ' tlmedia ' :
2019-11-30 09:41:58 +00:00
galleryStr + = ' <div class= " gallery " > \n '
2019-12-03 09:40:11 +00:00
if not isMuted :
galleryStr + = ' <a href= " ' + attach [ ' url ' ] + ' " > \n '
galleryStr + = ' <video width= " 600 " height= " 400 " controls> \n '
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <source src= " ' + attach [ ' url ' ] + \
' " alt= " ' + imageDescription + \
' " title= " ' + imageDescription + \
' " class= " attachment " type= " video/ ' + \
extension . replace ( ' . ' , ' ' ) + ' " > '
galleryStr + = \
translate [ ' Your browser does not support the video tag. ' ]
2019-12-03 09:40:11 +00:00
galleryStr + = ' </video> \n '
galleryStr + = ' </a> \n '
2019-11-28 20:00:48 +00:00
if postJsonObject [ ' object ' ] . get ( ' url ' ) :
videoPostUrl = postJsonObject [ ' object ' ] [ ' url ' ]
else :
2019-12-01 19:00:28 +00:00
videoPostUrl = postJsonObject [ ' object ' ] [ ' id ' ]
2019-12-03 09:40:11 +00:00
if imageDescription and not isMuted :
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <a href= " ' + videoPostUrl + \
' " class= " gallerytext " ><div class= " gallerytext " > ' + \
imageDescription + ' </div></a> \n '
2019-11-28 20:00:48 +00:00
else :
galleryStr + = ' <label class= " transparent " >---</label><br> '
galleryStr + = ' <div class= " mediaicons " > \n '
2020-02-23 14:24:11 +00:00
galleryStr + = \
' ' + replyStr + announceStr + likeStr + \
bookmarkStr + deleteStr + muteStr + ' \n '
2019-11-28 20:00:48 +00:00
galleryStr + = ' </div> \n '
galleryStr + = ' <div class= " mediaavatar " > \n '
galleryStr + = ' ' + avatarLink + ' \n '
galleryStr + = ' </div> \n '
galleryStr + = ' </div> \n '
2019-11-12 10:42:27 +00:00
2019-11-28 20:00:48 +00:00
attachmentStr + = ' <center><video width= " 400 " height= " 300 " controls> '
2020-02-23 14:24:11 +00:00
attachmentStr + = \
' <source src= " ' + attach [ ' url ' ] + ' " alt= " ' + \
imageDescription + ' " title= " ' + imageDescription + \
' " class= " attachment " type= " video/ ' + \
extension . replace ( ' . ' , ' ' ) + ' " > '
2019-11-28 20:00:48 +00:00
attachmentStr + = translate [ ' Your browser does not support the video tag. ' ]
attachmentStr + = ' </video></center> '
2019-08-30 12:11:58 +00:00
attachmentCtr + = 1
2019-08-30 18:44:26 +00:00
elif mediaType == ' audio/mpeg ' or \
2019-08-30 12:17:03 +00:00
mediaType == ' audio/ogg ' :
2019-08-30 12:11:58 +00:00
extension = ' .mp3 '
if attach [ ' url ' ] . endswith ( ' .ogg ' ) :
extension = ' .ogg '
if attach [ ' url ' ] . endswith ( extension ) :
if attachmentCtr > 0 :
attachmentStr + = ' <br> '
2019-10-23 13:39:08 +00:00
if boxName == ' tlmedia ' :
2019-11-30 09:41:58 +00:00
galleryStr + = ' <div class= " gallery " > \n '
2019-12-03 09:40:11 +00:00
if not isMuted :
galleryStr + = ' <a href= " ' + attach [ ' url ' ] + ' " > \n '
galleryStr + = ' <audio controls> \n '
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <source src= " ' + attach [ ' url ' ] + \
' " alt= " ' + imageDescription + \
' " title= " ' + imageDescription + \
' " class= " attachment " type= " audio/ ' + \
extension . replace ( ' . ' , ' ' ) + ' " > '
galleryStr + = \
translate [ ' Your browser does not support the audio tag. ' ]
2019-12-03 09:40:11 +00:00
galleryStr + = ' </audio> \n '
galleryStr + = ' </a> \n '
2019-11-28 20:00:48 +00:00
if postJsonObject [ ' object ' ] . get ( ' url ' ) :
audioPostUrl = postJsonObject [ ' object ' ] [ ' url ' ]
else :
2019-12-01 19:00:28 +00:00
audioPostUrl = postJsonObject [ ' object ' ] [ ' id ' ]
2019-12-03 09:40:11 +00:00
if imageDescription and not isMuted :
2020-02-23 14:24:11 +00:00
galleryStr + = \
' <a href= " ' + audioPostUrl + \
' " class= " gallerytext " ><div class= " gallerytext " > ' + \
imageDescription + ' </div></a> \n '
2019-11-28 20:00:48 +00:00
else :
galleryStr + = ' <label class= " transparent " >---</label><br> '
galleryStr + = ' <div class= " mediaicons " > \n '
2020-02-23 15:32:47 +00:00
galleryStr + = \
' ' + replyStr + announceStr + likeStr + bookmarkStr + \
deleteStr + muteStr + ' \n '
2019-11-28 20:00:48 +00:00
galleryStr + = ' </div> \n '
galleryStr + = ' <div class= " mediaavatar " > \n '
galleryStr + = ' ' + avatarLink + ' \n '
galleryStr + = ' </div> \n '
galleryStr + = ' </div> \n '
2019-11-12 10:42:27 +00:00
attachmentStr + = ' <center><audio controls> '
2020-02-23 14:24:11 +00:00
attachmentStr + = \
' <source src= " ' + attach [ ' url ' ] + ' " alt= " ' + \
imageDescription + ' " title= " ' + imageDescription + \
' " class= " attachment " type= " audio/ ' + \
extension . replace ( ' . ' , ' ' ) + ' " > '
2019-11-12 10:42:27 +00:00
attachmentStr + = translate [ ' Your browser does not support the audio tag. ' ]
attachmentStr + = ' </audio></center> '
2019-07-21 11:20:49 +00:00
attachmentCtr + = 1
2020-01-03 10:14:10 +00:00
attachmentStr + = ' </div> '
2019-07-22 14:09:21 +00:00
2019-11-29 22:45:56 +00:00
publishedStr = ' '
if postJsonObject [ ' object ' ] . get ( ' published ' ) :
publishedStr = postJsonObject [ ' object ' ] [ ' published ' ]
if ' . ' not in publishedStr :
if ' + ' not in publishedStr :
2020-02-23 15:32:47 +00:00
datetimeObject = \
datetime . strptime ( publishedStr , " % Y- % m- %d T % H: % M: % SZ " )
2019-11-29 22:45:56 +00:00
else :
2020-02-23 15:32:47 +00:00
datetimeObject = \
datetime . strptime ( publishedStr . split ( ' + ' ) [ 0 ] + ' Z ' , \
" % Y- % m- %d T % H: % M: % SZ " )
2019-08-27 22:50:40 +00:00
else :
2019-11-29 22:45:56 +00:00
publishedStr = publishedStr . replace ( ' T ' , ' ' ) . split ( ' . ' ) [ 0 ]
datetimeObject = parse ( publishedStr )
publishedStr = datetimeObject . strftime ( " %a % b %d , % H: % M " )
2020-01-02 22:42:06 +00:00
footerStr = ' <a href= " ' + messageId + ' " class= " ' + timeClass + ' " > ' + publishedStr + ' </a> \n '
2019-07-31 19:37:29 +00:00
2019-08-25 18:21:24 +00:00
# change the background color for DMs in inbox timeline
if showDMicon :
containerClassIcons = ' containericons dm '
containerClass = ' container dm '
2019-07-30 12:47:42 +00:00
if showIcons :
footerStr = ' <div class= " ' + containerClassIcons + ' " > '
2019-12-01 13:45:30 +00:00
footerStr + = replyStr + announceStr + likeStr + bookmarkStr + deleteStr + muteStr
2020-01-02 22:44:20 +00:00
footerStr + = ' <a href= " ' + messageId + ' " class= " ' + timeClass + ' " > ' + publishedStr + ' </a> \n '
2019-07-30 12:47:42 +00:00
footerStr + = ' </div> '
2020-02-18 10:04:31 +00:00
postIsSensitive = False
if postJsonObject [ ' object ' ] . get ( ' sensitive ' ) :
# sensitive posts should have a summary
if postJsonObject [ ' object ' ] . get ( ' summary ' ) :
postIsSensitive = postJsonObject [ ' object ' ] [ ' sensitive ' ]
2020-02-18 17:09:07 +00:00
else :
# add a generic summary if none is provided
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-01-03 10:23:42 +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 ' ) :
2019-10-17 22:47:40 +00:00
postJsonObject [ ' object ' ] [ ' summary ' ] = ' '
2019-11-04 20:39:14 +00:00
2019-11-29 22:49:17 +00:00
if not postJsonObject [ ' object ' ] . get ( ' content ' ) :
return ' '
2019-11-04 20:39:14 +00:00
objectContent = removeLongWords ( postJsonObject [ ' object ' ] [ ' content ' ] , 40 , [ ] )
2020-02-19 18:51:08 +00:00
objectContent = switchWords ( baseDir , nickname , domain , objectContent )
2020-02-18 10:04:31 +00:00
if not postIsSensitive :
2019-11-04 20:39:14 +00:00
contentStr = objectContent + attachmentStr
2019-09-07 08:57:52 +00:00
contentStr = addEmbeddedElements ( translate , contentStr )
2019-11-25 13:34:44 +00:00
contentStr = insertQuestion ( baseDir , translate , nickname , domain , port , \
2019-11-25 10:29:09 +00:00
contentStr , postJsonObject , pageNumber )
2019-07-31 12:44:08 +00:00
else :
postID = ' post ' + str ( createPassword ( 8 ) )
contentStr = ' '
if postJsonObject [ ' object ' ] . get ( ' summary ' ) :
contentStr + = ' <b> ' + postJsonObject [ ' object ' ] [ ' summary ' ] + ' </b> '
2019-08-12 13:47:17 +00:00
if isModerationPost :
2019-08-11 20:38:10 +00:00
containerClass = ' container report '
2020-02-23 14:24:11 +00:00
contentStr + = \
' <button class= " cwButton " onclick= " showContentWarning( ' + \
" ' " + postID + " ' " + ' ) " > ' + translate [ ' SHOW MORE ' ] + ' </button> '
2019-09-04 18:39:46 +00:00
contentStr + = ' <div class= " cwText " id= " ' + postID + ' " > '
2019-11-04 20:39:14 +00:00
contentStr + = objectContent + attachmentStr
2019-09-07 08:57:52 +00:00
contentStr = addEmbeddedElements ( translate , contentStr )
2019-11-25 13:34:44 +00:00
contentStr = insertQuestion ( baseDir , translate , nickname , domain , port , \
2019-11-25 10:29:09 +00:00
contentStr , postJsonObject , pageNumber )
2019-07-31 12:44:08 +00:00
contentStr + = ' </div> '
2019-08-19 12:59:57 +00:00
if postJsonObject [ ' object ' ] . get ( ' tag ' ) :
2020-02-23 14:24:11 +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 :
contentStr = ' '
else :
contentStr = ' <div class= " message " > ' + contentStr + ' </div> '
2019-08-25 18:21:24 +00:00
2019-10-19 11:53:57 +00:00
postHtml = ' '
2019-09-28 11:29:42 +00:00
if boxName != ' tlmedia ' :
2019-11-19 15:27:43 +00:00
postHtml = ' <div id= " ' + timelinePostBookmark + ' " class= " ' + containerClass + ' " > \n '
2019-11-12 10:42:27 +00:00
postHtml + = avatarImageInPost
postHtml + = ' <p class= " post-title " > ' + titleStr + replyAvatarImageInPost + ' </p> '
postHtml + = contentStr + footerStr
postHtml + = ' </div> \n '
2019-09-28 11:29:42 +00:00
else :
2019-10-19 11:53:57 +00:00
postHtml = galleryStr
2019-10-19 09:13:08 +00:00
2019-11-17 14:14:59 +00:00
if not showPublicOnly and storeToCache and \
boxName != ' tlmedia ' and boxName != ' tlbookmarks ' :
2019-10-19 11:53:57 +00:00
saveIndividualPostAsHtmlToCache ( baseDir , nickname , domain , \
postJsonObject , postHtml )
2019-11-24 17:40:31 +00:00
updateRecentPostsCache ( recentPostsCache , maxRecentPosts , \
postJsonObject , postHtml )
2019-10-19 09:13:08 +00:00
2019-10-19 11:53:57 +00:00
return postHtml
2019-07-21 09:09:28 +00:00
2019-09-06 15:08:32 +00:00
def isQuestion ( postObjectJson : { } ) - > bool :
""" is the given post a question?
"""
2019-11-25 10:10:59 +00:00
if postObjectJson [ ' type ' ] != ' Create ' and \
postObjectJson [ ' type ' ] != ' Update ' :
return False
if not isinstance ( postObjectJson [ ' object ' ] , dict ) :
return False
if not postObjectJson [ ' object ' ] . get ( ' type ' ) :
return False
if postObjectJson [ ' object ' ] [ ' type ' ] != ' Question ' :
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
2019-11-28 16:16:43 +00:00
def htmlTimeline ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , \
2019-09-07 08:57:52 +00:00
itemsPerPage : int , session , baseDir : str , \
2019-07-31 20:37:19 +00:00
wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , timelineJson : { } , \
2019-08-14 20:12:27 +00:00
boxName : str , allowDeletion : bool , \
2019-09-02 10:51:49 +00:00
httpPrefix : str , projectVersion : str , \
manuallyApproveFollowers : bool ) - > str :
2019-07-21 09:09:28 +00:00
""" Show the timeline as html
"""
2019-11-17 14:27:55 +00:00
accountDir = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain
2019-10-12 14:31:07 +00:00
# should the calendar icon be highlighted?
calendarImage = ' calendar.png '
calendarPath = ' /calendar '
calendarFile = accountDir + ' /.newCalendar '
if os . path . isfile ( calendarFile ) :
calendarImage = ' calendar_notify.png '
with open ( calendarFile , ' r ' ) as calfile :
2019-10-12 14:46:32 +00:00
calendarPath = calfile . read ( ) . replace ( ' ##sent## ' , ' ' ) . replace ( ' \n ' , ' ' )
2019-10-12 14:31:07 +00:00
2019-10-03 16:22:34 +00:00
# should the DM button be highlighted?
newDM = False
2019-10-03 16:37:25 +00:00
dmFile = accountDir + ' /.newDM '
2019-10-03 16:22:34 +00:00
if os . path . isfile ( dmFile ) :
newDM = True
if boxName == ' dm ' :
os . remove ( dmFile )
2019-10-03 16:37:25 +00:00
# should the Replies button be highlighted?
newReply = False
replyFile = accountDir + ' /.newReply '
if os . path . isfile ( replyFile ) :
newReply = True
if boxName == ' tlreplies ' :
os . remove ( replyFile )
2019-11-02 11:31:25 +00:00
# should the Shares button be highlighted?
newShare = False
2019-11-02 11:36:56 +00:00
newShareFile = accountDir + ' /.newShare '
2019-11-02 11:31:25 +00:00
if os . path . isfile ( newShareFile ) :
newShare = True
if boxName == ' tlshares ' :
os . remove ( newShareFile )
2019-11-16 15:24:07 +00:00
# should the Moderation button be highlighted?
newReport = False
newReportFile = accountDir + ' /.newReport '
if os . path . isfile ( newReportFile ) :
newReport = True
if boxName == ' moderation ' :
os . remove ( newReportFile )
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
2019-11-14 14:52:35 +00:00
cssFilename = baseDir + ' /epicyon.css '
bannerFile = ' banner.png '
bannerFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' / ' + bannerFile
if not os . path . isfile ( bannerFilename ) :
bannerFile = ' banner.jpg '
bannerFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' / ' + bannerFile
if not os . path . isfile ( bannerFilename ) :
bannerFile = ' banner.gif '
bannerFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' / ' + bannerFile
if not os . path . isfile ( bannerFilename ) :
bannerFile = ' banner.webp '
2019-12-10 14:48:08 +00:00
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-24 12:02:28 +00:00
profileStyle = \
cssFile . read ( ) . replace ( ' banner.png ' , \
2019-11-14 14:52:35 +00:00
' /users/ ' + nickname + ' / ' + bannerFile )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
profileStyle = profileStyle . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-07-24 12:02:28 +00:00
2019-08-12 13:22:17 +00:00
moderator = isModerator ( baseDir , nickname )
2019-07-30 10:50:01 +00:00
inboxButton = ' button '
2020-02-24 14:39:25 +00:00
blogsButton = ' button '
2019-08-25 14:29:47 +00:00
dmButton = ' button '
2019-10-03 16:22:34 +00:00
if newDM :
dmButton = ' buttonhighlighted '
2019-09-23 20:09:11 +00:00
repliesButton = ' button '
2019-10-03 16:37:25 +00:00
if newReply :
repliesButton = ' buttonhighlighted '
2019-09-28 11:29:42 +00:00
mediaButton = ' button '
2019-11-17 14:01:49 +00:00
bookmarksButton = ' button '
2019-07-30 10:50:01 +00:00
sentButton = ' button '
2019-11-02 11:31:25 +00:00
sharesButton = ' button '
2019-11-16 16:08:13 +00:00
if newShare :
sharesButton = ' buttonhighlighted '
2019-08-12 13:22:17 +00:00
moderationButton = ' button '
2019-11-16 16:08:13 +00:00
if newReport :
moderationButton = ' buttonhighlighted '
2019-07-24 12:02:28 +00:00
if boxName == ' inbox ' :
2019-07-30 10:50:01 +00:00
inboxButton = ' buttonselected '
2020-02-24 14:39:25 +00:00
elif boxName == ' tlblogs ' :
blogsButton = ' buttonselected '
2019-08-25 14:29:47 +00:00
elif boxName == ' dm ' :
dmButton = ' buttonselected '
2019-10-03 16:22:34 +00:00
if newDM :
dmButton = ' buttonselectedhighlighted '
2019-09-23 20:43:18 +00:00
elif boxName == ' tlreplies ' :
2019-09-23 20:09:11 +00:00
repliesButton = ' buttonselected '
2019-10-03 16:37:25 +00:00
if newReply :
repliesButton = ' buttonselectedhighlighted '
2019-09-28 11:29:42 +00:00
elif boxName == ' tlmedia ' :
mediaButton = ' buttonselected '
2019-07-24 12:02:28 +00:00
elif boxName == ' outbox ' :
2019-07-30 10:50:01 +00:00
sentButton = ' buttonselected '
2019-08-12 13:22:17 +00:00
elif boxName == ' moderation ' :
moderationButton = ' buttonselected '
2019-11-16 15:24:07 +00:00
if newReport :
moderationButton = ' buttonselectedhighlighted '
2019-11-02 11:31:25 +00:00
elif boxName == ' tlshares ' :
sharesButton = ' buttonselected '
if newShare :
sharesButton = ' buttonselectedhighlighted '
2019-11-17 14:01:49 +00:00
elif boxName == ' tlbookmarks ' :
bookmarksButton = ' buttonselected '
2019-11-02 14:53:30 +00:00
fullDomain = domain
if port != 80 and port != 443 :
if ' : ' not in domain :
fullDomain = domain + ' : ' + str ( port )
actor = httpPrefix + ' :// ' + fullDomain + ' /users/ ' + nickname
2019-07-29 20:56:07 +00:00
2019-07-30 12:47:42 +00:00
showIndividualPostIcons = True
2019-07-29 20:56:07 +00:00
followApprovals = ' '
2019-09-28 11:29:42 +00:00
followRequestsFilename = \
baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /followrequests.txt '
2019-07-29 20:56:07 +00:00
if os . path . isfile ( followRequestsFilename ) :
with open ( followRequestsFilename , ' r ' ) as f :
for line in f :
if len ( line ) > 0 :
2019-07-30 10:21:02 +00:00
# show follow approvals icon
2020-02-23 15:32:47 +00:00
followApprovals = \
' <a href= " ' + actor + ' /followers " ><img loading= " lazy " class= " timelineicon " alt= " ' + \
translate [ ' Approve follow requests ' ] + \
' " title= " ' + translate [ ' Approve follow requests ' ] + \
' " src= " / ' + iconsDir + ' /person.png " /></a> '
2019-07-29 20:56:07 +00:00
break
2019-08-12 13:22:17 +00:00
moderationButtonStr = ' '
if moderator :
2020-02-23 15:32:47 +00:00
moderationButtonStr = \
' <a href= " ' + actor + ' /moderation " ><button class= " ' + \
moderationButton + ' " ><span> ' + \
translate [ ' Mod ' ] + ' </span></button></a> '
2019-08-22 12:14:24 +00:00
2020-02-23 15:32:47 +00:00
sharesButtonStr = \
' <a href= " ' + actor + ' /tlshares " ><button class= " ' + \
sharesButton + ' " ><span> ' + translate [ ' Shares ' ] + \
' </span></button></a> '
2019-11-02 11:31:25 +00:00
2020-02-23 15:32:47 +00:00
bookmarksButtonStr = \
' <a href= " ' + actor + ' /tlbookmarks " ><button class= " ' + \
bookmarksButton + ' " ><span> ' + translate [ ' Bookmarks ' ] + \
' </span></button></a> '
2019-11-17 14:01:49 +00:00
2019-09-11 09:58:43 +00:00
tlStr = htmlHeader ( cssFilename , profileStyle )
2019-09-05 08:47:36 +00:00
#if (boxName=='inbox' or boxName=='dm') and pageNumber==1:
2019-08-25 18:27:19 +00:00
# refresh if on the first page of the inbox and dm timeline
2019-09-11 09:58:43 +00:00
#tlStr=htmlHeader(cssFilename,profileStyle,240)
2019-08-12 14:48:19 +00:00
2019-08-28 20:05:10 +00:00
if boxName != ' dm ' :
2020-02-24 14:54:42 +00:00
if boxName != ' tlblogs ' :
if not manuallyApproveFollowers :
newPostButtonStr = \
' <a href= " ' + actor + ' /newpost " ><img loading= " lazy " src= " / ' + \
iconsDir + ' /newpost.png " title= " ' + \
translate [ ' Create a new post ' ] + ' " alt= " ' + \
translate [ ' Create a new post ' ] + ' " class= " timelineicon " /></a> '
else :
newPostButtonStr = \
' <a href= " ' + actor + ' /newfollowers " ><img loading= " lazy " src= " / ' + \
iconsDir + ' /newpost.png " title= " ' + translate [ ' Create a new post ' ] + \
' " alt= " ' + translate [ ' Create a new post ' ] + ' " class= " timelineicon " /></a> '
else :
2020-02-23 15:32:47 +00:00
newPostButtonStr = \
2020-02-24 14:54:42 +00:00
' <a href= " ' + actor + ' /newblog " ><img loading= " lazy " src= " / ' + \
2020-02-23 15:32:47 +00:00
iconsDir + ' /newpost.png " title= " ' + \
translate [ ' Create a new post ' ] + ' " alt= " ' + \
translate [ ' Create a new post ' ] + ' " class= " timelineicon " /></a> '
2019-08-28 20:05:10 +00:00
else :
2020-02-23 15:32:47 +00:00
newPostButtonStr = \
' <a href= " ' + actor + ' /newdm " ><img loading= " lazy " src= " / ' + \
iconsDir + ' /newpost.png " title= " ' + translate [ ' Create a new DM ' ] + \
' " alt= " ' + translate [ ' Create a new DM ' ] + ' " class= " timelineicon " /></a> '
2019-08-28 20:05:10 +00:00
2019-11-27 20:56:41 +00:00
# This creates a link to the profile page when viewed in lynx, but should be invisible in a graphical web browser
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " /users/ ' + nickname + ' " ><label class= " transparent " > ' + \
translate [ ' Switch to profile view ' ] + ' </label></a> '
2019-11-27 20:56:41 +00:00
2019-08-12 14:48:19 +00:00
# banner and row of buttons
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " /users/ ' + nickname + ' " title= " ' + \
translate [ ' Switch to profile view ' ] + ' " alt= " ' + \
translate [ ' Switch to profile view ' ] + ' " > '
2019-11-27 20:46:44 +00:00
tlStr + = ' <div class= " timeline-banner " > '
2019-11-12 10:42:27 +00:00
tlStr + = ' </div></a> '
tlStr + = ' <div class= " container " > \n '
2020-02-24 14:39:25 +00:00
# first button
if defaultTimeline == ' tlmedia ' :
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " ' + actor + ' /tlmedia " ><button class= " ' + \
mediaButton + ' " ><span> ' + translate [ ' Media ' ] + \
' </span></button></a> '
2020-02-24 14:39:25 +00:00
elif defaultTimeline == ' tlblogs ' :
tlStr + = \
' <a href= " ' + actor + ' /tlblogs " ><button class= " ' + \
blogsButton + ' " ><span> ' + translate [ ' Blogs ' ] + \
' </span></button></a> '
else :
tlStr + = \
' <a href= " ' + actor + ' /inbox " ><button class= " ' + \
inboxButton + ' " ><span> ' + \
translate [ ' Inbox ' ] + ' </span></button></a> '
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " ' + actor + ' /dm " ><button class= " ' + dmButton + \
' " ><span> ' + translate [ ' DM ' ] + ' </span></button></a> '
tlStr + = \
' <a href= " ' + actor + ' /tlreplies " ><button class= " ' + \
repliesButton + ' " ><span> ' + translate [ ' Replies ' ] + \
' </span></button></a> '
2020-02-24 14:39:25 +00:00
# typically the media button
2019-11-28 16:16:43 +00:00
if defaultTimeline != ' tlmedia ' :
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " ' + actor + ' /tlmedia " ><button class= " ' + \
mediaButton + ' " ><span> ' + translate [ ' Media ' ] + \
' </span></button></a> '
2019-11-28 16:16:43 +00:00
else :
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " ' + actor + ' /inbox " ><button class= " ' + \
inboxButton + ' " ><span> ' + translate [ ' Inbox ' ] + \
' </span></button></a> '
2020-02-24 14:39:25 +00:00
# typically the blogs button
if defaultTimeline != ' tlblogs ' :
tlStr + = \
' <a href= " ' + actor + ' /tlblogs " ><button class= " ' + \
blogsButton + ' " ><span> ' + translate [ ' Blogs ' ] + \
' </span></button></a> '
else :
tlStr + = \
' <a href= " ' + actor + ' /inbox " ><button class= " ' + \
inboxButton + ' " ><span> ' + translate [ ' Inbox ' ] + \
' </span></button></a> '
2020-02-23 15:32:47 +00:00
tlStr + = \
' <a href= " ' + actor + ' /outbox " ><button class= " ' + \
sentButton + ' " ><span> ' + translate [ ' Outbox ' ] + \
' </span></button></a> '
tlStr + = \
sharesButtonStr + bookmarksButtonStr + moderationButtonStr + newPostButtonStr
tlStr + = \
' <a href= " ' + actor + ' /search " ><img loading= " lazy " src= " / ' + \
iconsDir + ' /search.png " title= " ' + \
translate [ ' Search and follow ' ] + ' " alt= " ' + \
translate [ ' Search and follow ' ] + ' " class= " timelineicon " /></a> '
tlStr + = \
' <a href= " ' + actor + calendarPath + \
' " ><img loading= " lazy " src= " / ' + iconsDir + ' / ' + \
calendarImage + ' " title= " ' + translate [ ' Calendar ' ] + \
' " alt= " ' + translate [ ' Calendar ' ] + ' " class= " timelineicon " /></a> '
tlStr + = \
' <a href= " ' + actor + ' / ' + boxName + \
' " ><img loading= " lazy " src= " / ' + iconsDir + \
' /refresh.png " title= " ' + translate [ ' Refresh ' ] + \
' " alt= " ' + translate [ ' Refresh ' ] + ' " class= " timelineicon " /></a> '
2019-11-12 10:42:27 +00:00
tlStr + = followApprovals
tlStr + = ' </div> '
2019-08-12 14:48:19 +00:00
# second row of buttons for moderator actions
if moderator and boxName == ' moderation ' :
2020-02-23 15:32:47 +00:00
tlStr + = \
' <form method= " POST " action= " /users/ ' + \
nickname + ' /moderationaction " > '
2019-11-12 10:42:27 +00:00
tlStr + = ' <div class= " container " > \n '
2020-02-23 15:32:47 +00:00
tlStr + = \
' <b> ' + \
translate [ ' Nickname or URL. Block using *@domain or nickname@domain ' ] + ' </b><br> \n '
2019-11-27 12:04:06 +00:00
tlStr + = ' <input type= " text " name= " moderationAction " value= " " autofocus><br> \n '
2020-02-23 15:32:47 +00:00
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Remove the above item ' ] + \
' " name= " submitRemove " value= " ' + \
translate [ ' Remove ' ] + ' " > '
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Suspend the above account nickname ' ] + \
' " name= " submitSuspend " value= " ' + translate [ ' Suspend ' ] + ' " > '
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Remove a suspension for an account nickname ' ] + \
' " name= " submitUnsuspend " value= " ' + translate [ ' Unsuspend ' ] + ' " > '
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Block an account on another instance ' ] + \
' " name= " submitBlock " value= " ' + translate [ ' Block ' ] + ' " > '
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Unblock an account on another instance ' ] + \
' " name= " submitUnblock " value= " ' + translate [ ' Unblock ' ] + ' " > '
tlStr + = \
' <input type= " submit " title= " ' + \
translate [ ' Information about current blocks/suspensions ' ] + \
' " name= " submitInfo " value= " ' + translate [ ' Info ' ] + ' " > '
2019-11-12 10:42:27 +00:00
tlStr + = ' </div></form> '
2019-08-12 14:48:19 +00:00
2019-11-02 14:19:51 +00:00
if boxName == ' tlshares ' :
maxSharesPerAccount = itemsPerPage
2019-11-02 14:39:13 +00:00
return tlStr + \
htmlSharesTimeline ( translate , pageNumber , itemsPerPage , \
baseDir , actor , nickname , domain , port , \
maxSharesPerAccount , httpPrefix ) + \
htmlFooter ( )
2019-11-02 14:19:51 +00:00
2019-08-12 14:48:19 +00:00
# add the javascript for content warnings
2019-09-22 09:14:39 +00:00
tlStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-08-12 14:48:19 +00:00
2020-02-22 22:39:13 +00:00
# show todays events buttons on the first inbox page
if boxName == ' inbox ' and pageNumber == 1 :
2020-02-22 17:56:34 +00:00
if todaysEventsCheck ( baseDir , nickname , domain ) :
2020-02-22 18:01:16 +00:00
now = datetime . now ( )
tlStr + = \
' <center><a href= " ' + actor + ' /calendar?year= ' + \
str ( now . year ) + ' ?month= ' + str ( now . month ) + \
2020-02-22 18:09:24 +00:00
' ?day= ' + str ( now . day ) + ' " ><button class= " buttonevent " > ' + \
2020-02-22 18:35:50 +00:00
translate [ ' Happening Today ' ] + ' </button></a> '
if thisWeeksEventsCheck ( baseDir , nickname , domain ) :
tlStr + = \
' <a href= " ' + actor + ' /calendar " ><button class= " buttonevent " > ' + \
translate [ ' Happening This Week ' ] + ' </button></a> '
tlStr + = ' </center> '
else :
if thisWeeksEventsCheck ( baseDir , nickname , domain ) :
tlStr + = \
' <center><a href= " ' + actor + ' /calendar " ><button class= " buttonevent " > ' + \
translate [ ' Happening This Week ' ] + ' </button></a></center> '
2020-02-22 17:56:34 +00:00
2019-08-12 14:48:19 +00:00
# page up arrow
2019-07-31 20:37:19 +00:00
if pageNumber > 1 :
2020-02-23 14:24:11 +00:00
tlStr + = \
' <center><a href= " ' + actor + ' / ' + boxName + \
' ?page= ' + str ( pageNumber - 1 ) + \
' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pageup.png " title= " ' + \
translate [ ' Page up ' ] + ' " alt= " ' + \
translate [ ' Page up ' ] + ' " ></a></center> '
2019-08-12 14:48:19 +00:00
# show the posts
2019-07-31 20:37:19 +00:00
itemCtr = 0
2019-09-23 21:05:31 +00:00
if timelineJson :
2019-09-28 11:49:16 +00:00
if boxName == ' tlmedia ' :
2019-09-28 11:58:41 +00:00
if pageNumber > 1 :
tlStr + = ' <br> '
2019-09-28 11:49:16 +00:00
tlStr + = ' <div class= " galleryContainer " > \n '
2019-09-23 21:05:31 +00:00
for item in timelineJson [ ' orderedItems ' ] :
2019-11-25 10:10:59 +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?
if isPersonSnoozed ( baseDir , nickname , domain , item [ ' actor ' ] ) :
continue
# is the post in the memory cache of recent ones?
2019-11-24 11:28:58 +00:00
currTlStr = None
2019-11-24 15:57:46 +00:00
if boxName != ' tlmedia ' and recentPostsCache . get ( ' index ' ) :
2019-11-24 11:28:58 +00:00
postId = item [ ' id ' ] . replace ( ' /activity ' , ' ' ) . replace ( ' / ' , ' # ' )
if postId in recentPostsCache [ ' index ' ] :
2019-12-01 14:34:10 +00:00
if not item . get ( ' muted ' ) :
if recentPostsCache [ ' html ' ] . get ( postId ) :
currTlStr = recentPostsCache [ ' html ' ] [ postId ]
currTlStr = \
preparePostFromHtmlCache ( currTlStr , boxName , pageNumber )
2019-11-24 11:28:58 +00:00
if not currTlStr :
2019-11-25 09:37:01 +00:00
# read the post from disk
2019-11-24 11:28:58 +00:00
currTlStr = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , pageNumber , \
2019-11-24 11:28:58 +00:00
baseDir , session , wfRequest , personCache , \
nickname , domain , port , item , None , True , \
allowDeletion , \
httpPrefix , projectVersion , boxName , \
boxName != ' dm ' , \
showIndividualPostIcons , \
manuallyApproveFollowers , False , True )
2019-09-30 16:26:59 +00:00
if currTlStr :
2019-11-17 15:38:46 +00:00
itemCtr + = 1
2019-09-30 15:52:37 +00:00
tlStr + = currTlStr
2019-09-28 11:49:16 +00:00
if boxName == ' tlmedia ' :
2019-09-28 14:27:18 +00:00
tlStr + = ' </div> \n '
2019-08-12 14:48:19 +00:00
# page down arrow
2019-12-09 09:45:16 +00:00
if itemCtr > 2 :
2020-02-23 14:24:11 +00:00
tlStr + = \
' <center><a href= " ' + actor + ' / ' + boxName + ' ?page= ' + \
str ( pageNumber + 1 ) + ' " ><img loading= " lazy " class= " pageicon " src= " / ' + \
iconsDir + ' /pagedown.png " title= " ' + \
translate [ ' Page down ' ] + ' " alt= " ' + \
translate [ ' Page down ' ] + ' " ></a></center> '
2019-07-21 09:09:28 +00:00
tlStr + = htmlFooter ( )
return tlStr
2019-11-28 16:16:43 +00:00
def htmlShares ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-11-02 14:31:39 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-11-02 14:34:12 +00:00
nickname : str , domain : str , port : int , \
2019-11-02 14:31:39 +00:00
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the shares timeline as html
"""
manuallyApproveFollowers = \
followerApprovalActive ( baseDir , nickname , domain )
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-11-02 14:31:39 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-11-02 14:34:12 +00:00
nickname , domain , port , None , ' tlshares ' , allowDeletion , \
2019-11-02 14:31:39 +00:00
httpPrefix , projectVersion , manuallyApproveFollowers )
2019-11-28 16:16:43 +00:00
def htmlInbox ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-07-31 20:37:19 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-08-09 22:49:44 +00:00
nickname : str , domain : str , port : int , inboxJson : { } , \
2019-08-14 20:12:27 +00:00
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the inbox as html
"""
2019-09-02 10:51:49 +00:00
manuallyApproveFollowers = \
followerApprovalActive ( baseDir , nickname , domain )
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-07 09:21:10 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
nickname , domain , port , inboxJson , ' inbox ' , allowDeletion , \
2019-09-02 10:51:49 +00:00
httpPrefix , projectVersion , manuallyApproveFollowers )
2019-08-25 17:34:09 +00:00
2019-11-28 16:16:43 +00:00
def htmlBookmarks ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-11-17 14:01:49 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-11-17 14:09:57 +00:00
nickname : str , domain : str , port : int , bookmarksJson : { } , \
2019-11-17 14:01:49 +00:00
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the bookmarks as html
"""
manuallyApproveFollowers = \
followerApprovalActive ( baseDir , nickname , domain )
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-11-17 14:01:49 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-11-17 14:09:57 +00:00
nickname , domain , port , bookmarksJson , ' tlbookmarks ' , allowDeletion , \
2019-11-17 14:01:49 +00:00
httpPrefix , projectVersion , manuallyApproveFollowers )
2019-11-28 16:16:43 +00:00
def htmlInboxDMs ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-08-25 17:34:09 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
nickname : str , domain : str , port : int , inboxJson : { } , \
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the DM timeline as html
"""
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-07 09:21:10 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-08-25 17:34:09 +00:00
nickname , domain , port , inboxJson , ' dm ' , allowDeletion , \
2019-09-02 10:51:49 +00:00
httpPrefix , projectVersion , False )
2019-07-20 21:13:36 +00:00
2019-11-28 16:16:43 +00:00
def htmlInboxReplies ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-09-23 20:09:11 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
nickname : str , domain : str , port : int , inboxJson : { } , \
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the replies timeline as html
"""
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-23 20:09:11 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-09-23 20:43:18 +00:00
nickname , domain , port , inboxJson , ' tlreplies ' , allowDeletion , \
2019-09-23 20:09:11 +00:00
httpPrefix , projectVersion , False )
2019-11-28 16:16:43 +00:00
def htmlInboxMedia ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-09-28 11:29:42 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
nickname : str , domain : str , port : int , inboxJson : { } , \
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the media timeline as html
"""
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-28 11:29:42 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
nickname , domain , port , inboxJson , ' tlmedia ' , allowDeletion , \
httpPrefix , projectVersion , False )
2020-02-24 14:39:25 +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 , \
httpPrefix : str , projectVersion : str ) - > str :
""" Show the blogs timeline as html
"""
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
translate , pageNumber , \
itemsPerPage , session , baseDir , wfRequest , personCache , \
nickname , domain , port , inboxJson , ' tlblogs ' , allowDeletion , \
httpPrefix , projectVersion , False )
2019-11-28 16:16:43 +00:00
def htmlModeration ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-08-12 13:22:17 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
nickname : str , domain : str , port : int , inboxJson : { } , \
2019-08-14 20:12:27 +00:00
allowDeletion : bool , \
httpPrefix : str , projectVersion : str ) - > str :
2019-08-12 13:22:17 +00:00
""" Show the moderation feed as html
"""
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-07 09:21:10 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
nickname , domain , port , inboxJson , ' moderation ' , allowDeletion , \
2019-09-02 10:51:49 +00:00
httpPrefix , projectVersion , True )
2019-08-12 13:22:17 +00:00
2019-11-28 16:16:43 +00:00
def htmlOutbox ( defaultTimeline : str , \
recentPostsCache : { } , maxRecentPosts : int , \
2019-11-24 11:28:58 +00:00
translate : { } , pageNumber : int , itemsPerPage : int , \
2019-07-31 20:37:19 +00:00
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-08-09 22:49:44 +00:00
nickname : str , domain : str , port : int , outboxJson : { } , \
2019-08-14 20:12:27 +00:00
allowDeletion : bool ,
httpPrefix : str , projectVersion : str ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the Outbox as html
"""
2019-09-02 10:51:49 +00:00
manuallyApproveFollowers = \
followerApprovalActive ( baseDir , nickname , domain )
2019-11-28 16:16:43 +00:00
return htmlTimeline ( defaultTimeline , recentPostsCache , maxRecentPosts , \
2019-11-24 11:28:58 +00:00
translate , pageNumber , \
2019-09-07 09:21:10 +00:00
itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
nickname , domain , port , outboxJson , ' outbox ' , allowDeletion , \
2019-09-02 10:51:49 +00:00
httpPrefix , projectVersion , manuallyApproveFollowers )
2019-07-20 21:13:36 +00:00
2019-11-24 17:40:31 +00:00
def htmlIndividualPost ( recentPostsCache : { } , maxRecentPosts : int , \
translate : { } , \
2019-09-07 08:57:52 +00:00
baseDir : str , session , wfRequest : { } , personCache : { } , \
2019-08-02 18:57:06 +00:00
nickname : str , domain : str , port : int , authorized : bool , \
2019-08-14 20:12:27 +00:00
postJsonObject : { } , httpPrefix : str , projectVersion : str ) - > str :
2019-07-20 21:13:36 +00:00
""" Show an individual post as html
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-09-06 09:18:52 +00:00
postStr = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-08-02 20:21:25 +00:00
postStr + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
2019-09-11 18:10:32 +00:00
baseDir , session , wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
nickname , domain , port , postJsonObject , None , True , False , \
2019-09-23 21:40:31 +00:00
httpPrefix , projectVersion , ' inbox ' , \
2019-10-19 11:42:41 +00:00
False , authorized , False , False , False )
2019-08-02 19:47:30 +00:00
messageId = postJsonObject [ ' id ' ] . replace ( ' /activity ' , ' ' )
# show the previous posts
2020-02-05 18:50:53 +00:00
if isinstance ( postJsonObject [ ' object ' ] , dict ) :
while postJsonObject [ ' object ' ] . get ( ' inReplyTo ' ) :
2020-02-23 14:24:11 +00:00
postFilename = \
locatePost ( baseDir , nickname , domain , \
postJsonObject [ ' object ' ] [ ' inReplyTo ' ] )
2020-02-05 18:50:53 +00:00
if not postFilename :
break
postJsonObject = loadJson ( postFilename )
if postJsonObject :
postStr = \
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
baseDir , session , wfRequest , personCache , \
nickname , domain , port , postJsonObject , \
None , True , False , \
httpPrefix , projectVersion , ' inbox ' , \
False , authorized , False , False , False ) + postStr
2019-08-02 19:47:30 +00:00
# show the following posts
postFilename = locatePost ( baseDir , nickname , domain , messageId )
2019-08-02 18:57:06 +00:00
if postFilename :
# is there a replies file for this post?
repliesFilename = postFilename . replace ( ' .json ' , ' .replies ' )
if os . path . isfile ( repliesFilename ) :
# get items from the replies file
repliesJson = { ' orderedItems ' : [ ] }
2020-02-23 14:24:11 +00:00
populateRepliesJson ( baseDir , nickname , domain , \
repliesFilename , authorized , repliesJson )
2019-08-02 18:57:06 +00:00
# add items to the html output
for item in repliesJson [ ' orderedItems ' ] :
postStr + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
2019-09-04 11:29:44 +00:00
baseDir , session , wfRequest , personCache , \
2019-08-14 20:12:27 +00:00
nickname , domain , port , item , None , True , False , \
2019-09-23 21:40:31 +00:00
httpPrefix , projectVersion , ' inbox ' , \
2019-10-19 11:42:41 +00:00
False , authorized , False , False , False )
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
2019-09-18 09:53:13 +00:00
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 :
postsCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
postsCSS = postsCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-18 10:01:31 +00:00
return htmlHeader ( cssFilename , postsCSS ) + postStr + htmlFooter ( )
2019-08-02 16:49:42 +00:00
2019-11-24 17:40:31 +00:00
def htmlPostReplies ( recentPostsCache : { } , maxRecentPosts : int , \
translate : { } , baseDir : str , \
2019-09-07 08:57:52 +00:00
session , wfRequest : { } , personCache : { } , \
2019-08-14 20:12:27 +00:00
nickname : str , domain : str , port : int , repliesJson : { } , \
httpPrefix : str , projectVersion : str ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the replies to an individual post as html
"""
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-08-02 16:49:42 +00:00
repliesStr = ' '
if repliesJson . get ( ' orderedItems ' ) :
for item in repliesJson [ ' orderedItems ' ] :
2019-09-07 08:57:52 +00:00
repliesStr + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , \
2019-09-07 08:57:52 +00:00
baseDir , session , wfRequest , personCache , \
nickname , domain , port , item , None , True , False , \
2019-09-23 21:40:31 +00:00
httpPrefix , projectVersion , ' inbox ' , \
2019-10-19 11:42:41 +00:00
False , False , False , False , False )
2019-08-02 16:49:42 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
2019-09-18 09:53:13 +00:00
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 :
postsCSS = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
postsCSS = postsCSS . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-18 10:01:31 +00:00
return htmlHeader ( cssFilename , postsCSS ) + repliesStr + htmlFooter ( )
2019-07-29 09:49:46 +00:00
2019-09-07 08:57:52 +00:00
def htmlRemoveSharedItem ( translate : { } , baseDir : str , actor : str , shareName : str ) - > str :
2019-08-26 09:13:26 +00:00
""" Shows a screen asking to confirm the removal of a shared item
"""
2019-11-03 09:48:01 +00:00
itemID = getValidSharedItemID ( shareName )
2019-08-26 09:13:26 +00:00
nickname = getNicknameFromActor ( actor )
domain , port = getDomainFromActor ( actor )
sharesFile = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /shares.json '
if not os . path . isfile ( sharesFile ) :
2019-11-03 09:36:41 +00:00
print ( ' ERROR: no shares file ' + sharesFile )
2019-08-26 09:13:26 +00:00
return None
2019-10-22 11:55:06 +00:00
sharesJson = loadJson ( sharesFile )
2019-08-26 09:13:26 +00:00
if not sharesJson :
2019-11-03 09:36:04 +00:00
print ( ' ERROR: unable to load shares.json ' )
2019-08-26 09:13:26 +00:00
return None
2019-11-03 09:48:01 +00:00
if not sharesJson . get ( itemID ) :
print ( ' ERROR: share named " ' + itemID + ' " is not in ' + sharesFile )
2019-08-26 09:13:26 +00:00
return None
2019-11-03 09:48:01 +00:00
sharedItemDisplayName = sharesJson [ itemID ] [ ' displayName ' ]
2019-08-26 09:13:26 +00:00
sharedItemImageUrl = None
2019-11-03 09:48:01 +00:00
if sharesJson [ itemID ] . get ( ' imageUrl ' ) :
sharedItemImageUrl = sharesJson [ itemID ] [ ' imageUrl ' ]
2019-08-26 09:13:26 +00:00
if os . path . isfile ( baseDir + ' /img/shares-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/shares-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/shares-background.png ' , \
baseDir + ' /accounts/shares-background.png ' )
2019-08-26 09:13:26 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-26 09:13:26 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
sharesStr = htmlHeader ( cssFilename , profileStyle )
2019-08-26 09:13:26 +00:00
sharesStr + = ' <div class= " follow " > '
sharesStr + = ' <div class= " followAvatar " > '
sharesStr + = ' <center> '
if sharedItemImageUrl :
2019-10-18 19:25:21 +00:00
sharesStr + = ' <img loading= " lazy " src= " ' + sharedItemImageUrl + ' " /> '
2020-02-23 14:24:11 +00:00
sharesStr + = \
' <p class= " followText " > ' + translate [ ' Remove ' ] + \
' ' + sharedItemDisplayName + ' ?</p> '
2019-11-12 10:42:27 +00:00
sharesStr + = ' <form method= " POST " action= " ' + actor + ' /rmshare " > '
sharesStr + = ' <input type= " hidden " name= " actor " value= " ' + actor + ' " > '
sharesStr + = ' <input type= " hidden " name= " shareName " value= " ' + shareName + ' " > '
2020-02-23 14:24:11 +00:00
sharesStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
sharesStr + = \
' <a href= " ' + actor + ' /inbox ' + ' " ><button class= " button " > ' + \
translate [ ' No ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
sharesStr + = ' </form> '
2019-08-26 09:13:26 +00:00
sharesStr + = ' </center> '
sharesStr + = ' </div> '
sharesStr + = ' </div> '
sharesStr + = htmlFooter ( )
return sharesStr
2019-08-27 12:47:11 +00:00
2019-11-24 17:40:31 +00:00
def htmlDeletePost ( recentPostsCache : { } , maxRecentPosts : int , \
translate , pageNumber : int , \
2019-09-04 11:29:44 +00:00
session , baseDir : str , messageId : str , \
2019-08-27 12:47:11 +00:00
httpPrefix : str , projectVersion : str , \
wfRequest : { } , personCache : { } ) - > str :
""" Shows a screen asking to confirm the deletion of a post
"""
if ' /statuses/ ' not in messageId :
return None
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-08-27 12:47:11 +00:00
actor = messageId . split ( ' /statuses/ ' ) [ 0 ]
nickname = getNicknameFromActor ( actor )
domain , port = getDomainFromActor ( actor )
postFilename = locatePost ( baseDir , nickname , domain , messageId )
if not postFilename :
return None
2019-09-17 12:14:36 +00:00
2019-10-22 11:55:06 +00:00
postJsonObject = loadJson ( postFilename )
2019-09-17 12:14:36 +00:00
if not postJsonObject :
return None
2019-08-27 12:47:11 +00:00
if os . path . isfile ( baseDir + ' /img/delete-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/delete-background.png ' ) :
2019-09-04 11:29:44 +00:00
copyfile ( baseDir + ' /img/delete-background.png ' , \
baseDir + ' /accounts/delete-background.png ' )
2019-08-27 12:47:11 +00:00
2019-08-27 13:19:42 +00:00
deletePostStr = None
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-27 12:47:11 +00:00
profileStyle = cssFile . read ( )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
profileStyle = profileStyle . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-09-11 09:58:43 +00:00
deletePostStr = htmlHeader ( cssFilename , profileStyle )
2019-08-27 13:19:42 +00:00
deletePostStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
deletePostStr + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , pageNumber , \
2019-09-07 08:57:52 +00:00
baseDir , session , wfRequest , personCache , \
nickname , domain , port , postJsonObject , \
None , True , False , \
2019-09-23 21:40:31 +00:00
httpPrefix , projectVersion , ' outbox ' , \
2019-10-19 11:42:41 +00:00
False , False , False , False , False )
2019-08-27 13:19:42 +00:00
deletePostStr + = ' <center> '
2020-02-23 14:24:11 +00:00
deletePostStr + = \
' <p class= " followText " > ' + \
translate [ ' Delete this post? ' ] + ' </p> '
2019-11-12 10:42:27 +00:00
deletePostStr + = ' <form method= " POST " action= " ' + actor + ' /rmpost " > '
2020-02-23 14:24:11 +00:00
deletePostStr + = \
' <input type= " hidden " name= " pageNumber " value= " ' + \
str ( pageNumber ) + ' " > '
deletePostStr + = \
' <input type= " hidden " name= " messageId " value= " ' + messageId + ' " > '
deletePostStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
deletePostStr + = \
' <a href= " ' + actor + ' /inbox ' + ' " ><button class= " button " > ' + \
translate [ ' No ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
deletePostStr + = ' </form> '
2019-08-27 13:19:42 +00:00
deletePostStr + = ' </center> '
deletePostStr + = htmlFooter ( )
2019-08-27 12:47:11 +00:00
return deletePostStr
2020-02-23 12:20:54 +00:00
def htmlCalendarDeleteConfirm ( translate : { } , baseDir : str , \
path : str , httpPrefix : str , \
2020-02-23 13:07:30 +00:00
domainFull : str , postId : str , postTime : str , \
year : int , monthNumber : int , dayNumber : int ) - > str :
2020-02-23 12:20:54 +00:00
""" Shows a screen asking to confirm the deletion of a calendar event
"""
iconsDir = getIconsDir ( baseDir )
nickname = getNicknameFromActor ( path )
actor = httpPrefix + ' :// ' + domainFull + ' /users/ ' + nickname
domain , port = getDomainFromActor ( actor )
2020-02-23 12:25:00 +00:00
messageId = actor + ' /statuses/ ' + postId
2020-02-23 12:20:54 +00:00
postFilename = locatePost ( baseDir , nickname , domain , messageId )
if not postFilename :
return None
postJsonObject = loadJson ( postFilename )
if not postJsonObject :
return None
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 ' )
deletePostStr = None
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
profileStyle = cssFile . read ( )
if httpPrefix != ' https ' :
profileStyle = profileStyle . replace ( ' https:// ' , httpPrefix + ' :// ' )
deletePostStr = htmlHeader ( cssFilename , profileStyle )
2020-02-23 14:24:11 +00:00
deletePostStr + = \
' <center><h1> ' + postTime + ' ' + str ( year ) + ' / ' + str ( monthNumber ) + \
' / ' + str ( dayNumber ) + ' </h1></center> '
2020-02-23 12:20:54 +00:00
deletePostStr + = ' <center> '
deletePostStr + = ' <p class= " followText " > ' + translate [ ' Delete this event ' ] + ' </p> '
deletePostStr + = ' <form method= " POST " action= " ' + actor + ' /rmpost " > '
2020-02-23 13:28:27 +00:00
deletePostStr + = ' <input type= " hidden " name= " year " value= " ' + str ( year ) + ' " > '
deletePostStr + = ' <input type= " hidden " name= " month " value= " ' + str ( monthNumber ) + ' " > '
deletePostStr + = ' <input type= " hidden " name= " day " value= " ' + str ( dayNumber ) + ' " > '
2020-02-23 12:20:54 +00:00
deletePostStr + = ' <input type= " hidden " name= " pageNumber " value= " 1 " > '
deletePostStr + = ' <input type= " hidden " name= " messageId " value= " ' + messageId + ' " > '
2020-02-23 14:24:11 +00:00
deletePostStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
deletePostStr + = \
' <a href= " ' + actor + ' /calendar?year= ' + str ( year ) + ' ?month= ' + \
str ( monthNumber ) + ' " ><button class= " button " > ' + translate [ ' No ' ] + \
' </button></a> '
2020-02-23 12:20:54 +00:00
deletePostStr + = ' </form> '
deletePostStr + = ' </center> '
deletePostStr + = htmlFooter ( )
return deletePostStr
2019-09-07 08:57:52 +00:00
def htmlFollowConfirm ( translate : { } , baseDir : str , \
originPathStr : str , \
followActor : str , \
followProfileUrl : str ) - > str :
2019-07-29 09:49:46 +00:00
""" Asks to confirm a follow
"""
followDomain , port = getDomainFromActor ( followActor )
if os . path . isfile ( baseDir + ' /img/follow-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/follow-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/follow-background.png ' , \
baseDir + ' /accounts/follow-background.png ' )
2019-07-29 09:49:46 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-29 09:49:46 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
followStr = htmlHeader ( cssFilename , profileStyle )
2019-07-29 09:49:46 +00:00
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
followStr + = ' <a href= " ' + followActor + ' " > '
2019-10-18 19:25:21 +00:00
followStr + = ' <img loading= " lazy " src= " ' + followProfileUrl + ' " /></a> '
2020-02-23 14:24:11 +00:00
followStr + = \
' <p class= " followText " > ' + translate [ ' Follow ' ] + ' ' + \
getNicknameFromActor ( followActor ) + ' @ ' + followDomain + ' ?</p> '
2019-11-12 10:42:27 +00:00
followStr + = ' <form method= " POST " action= " ' + originPathStr + ' /followconfirm " > '
followStr + = ' <input type= " hidden " name= " actor " value= " ' + followActor + ' " > '
2020-02-23 14:24:11 +00:00
followStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
followStr + = \
' <a href= " ' + originPathStr + ' " ><button class= " button " > ' + \
translate [ ' No ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
followStr + = ' </form> '
2019-07-29 09:49:46 +00:00
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
2019-07-29 20:36:26 +00:00
2019-09-07 08:57:52 +00:00
def htmlUnfollowConfirm ( translate : { } , baseDir : str , \
originPathStr : str , \
followActor : str , \
followProfileUrl : str ) - > str :
2019-07-29 20:36:26 +00:00
""" Asks to confirm unfollowing an actor
"""
followDomain , port = getDomainFromActor ( followActor )
if os . path . isfile ( baseDir + ' /img/follow-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/follow-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/follow-background.png ' , \
baseDir + ' /accounts/follow-background.png ' )
2019-07-29 20:36:26 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-29 20:36:26 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
followStr = htmlHeader ( cssFilename , profileStyle )
2019-07-29 20:36:26 +00:00
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
followStr + = ' <a href= " ' + followActor + ' " > '
2019-10-18 19:25:21 +00:00
followStr + = ' <img loading= " lazy " src= " ' + followProfileUrl + ' " /></a> '
2020-02-23 14:24:11 +00:00
followStr + = \
' <p class= " followText " > ' + translate [ ' Stop following ' ] + \
' ' + getNicknameFromActor ( followActor ) + ' @ ' + followDomain + ' ?</p> '
2019-11-12 10:42:27 +00:00
followStr + = ' <form method= " POST " action= " ' + originPathStr + ' /unfollowconfirm " > '
followStr + = ' <input type= " hidden " name= " actor " value= " ' + followActor + ' " > '
2020-02-23 14:24:11 +00:00
followStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
followStr + = \
' <a href= " ' + originPathStr + ' " ><button class= " button " > ' + \
translate [ ' No ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
followStr + = ' </form> '
2019-07-29 20:36:26 +00:00
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
2019-07-30 22:34:04 +00:00
2019-09-07 08:57:52 +00:00
def htmlPersonOptions ( translate : { } , baseDir : str , \
domain : str , originPathStr : str , \
optionsActor : str , \
optionsProfileUrl : str , \
optionsLink : str , \
2019-12-17 14:57:16 +00:00
pageNumber : int , \
donateUrl : str , \
2019-12-17 15:25:34 +00:00
xmppAddress : str , \
2019-12-17 20:44:18 +00:00
matrixAddress : str , \
PGPpubKey : str , \
emailAddress ) - > str :
2019-08-24 21:10:20 +00:00
""" Show options for a person: view/follow/block/report
"""
2019-08-24 23:00:03 +00:00
optionsDomain , optionsPort = getDomainFromActor ( optionsActor )
2019-08-24 21:10:20 +00:00
if os . path . isfile ( baseDir + ' /img/options-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/options-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/options-background.png ' , \
baseDir + ' /accounts/options-background.png ' )
2019-08-24 21:10:20 +00:00
2019-08-24 23:00:03 +00:00
followStr = ' Follow '
blockStr = ' Block '
2019-11-06 11:39:41 +00:00
nickname = None
2019-08-24 23:00:03 +00:00
if originPathStr . startswith ( ' /users/ ' ) :
nickname = originPathStr . split ( ' /users/ ' ) [ 1 ]
if ' / ' in nickname :
nickname = nickname . split ( ' / ' ) [ 0 ]
if ' ? ' in nickname :
nickname = nickname . split ( ' ? ' ) [ 0 ]
followerDomain , followerPort = getDomainFromActor ( optionsActor )
if isFollowingActor ( baseDir , nickname , domain , optionsActor ) :
followStr = ' Unfollow '
optionsNickname = getNicknameFromActor ( optionsActor )
optionsDomainFull = optionsDomain
if optionsPort :
if optionsPort != 80 and optionsPort != 443 :
optionsDomainFull = optionsDomain + ' : ' + str ( optionsPort )
if isBlocked ( baseDir , nickname , domain , optionsNickname , optionsDomainFull ) :
blockStr = ' Block '
optionsLinkStr = ' '
if optionsLink :
optionsLinkStr = ' <input type= " hidden " name= " postUrl " value= " ' + optionsLink + ' " > '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-24 21:10:20 +00:00
profileStyle = cssFile . read ( )
2019-11-06 11:39:41 +00:00
# To snooze, or not to snooze? That is the question
snoozeButtonStr = ' Snooze '
if nickname :
if isPersonSnoozed ( baseDir , nickname , domain , optionsActor ) :
snoozeButtonStr = ' Unsnooze '
2019-11-06 23:20:00 +00:00
donateStr = ' '
if donateUrl :
donateStr = \
2020-02-23 14:24:11 +00:00
' <a href= " ' + donateUrl + ' " ><button class= " button " name= " submitDonate " > ' + \
translate [ ' Donate ' ] + ' </button></a> '
2019-11-06 23:20:00 +00:00
2019-09-11 09:58:43 +00:00
optionsStr = htmlHeader ( cssFilename , profileStyle )
2019-08-24 21:10:20 +00:00
optionsStr + = ' <div class= " options " > '
optionsStr + = ' <div class= " optionsAvatar " > '
optionsStr + = ' <center> '
optionsStr + = ' <a href= " ' + optionsActor + ' " > '
2019-10-18 19:25:21 +00:00
optionsStr + = ' <img loading= " lazy " src= " ' + optionsProfileUrl + ' " /></a> '
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <p class= " optionsText " > ' + translate [ ' Options for ' ] + \
' @ ' + getNicknameFromActor ( optionsActor ) + ' @ ' + optionsDomain + ' </p> '
2019-12-17 20:44:18 +00:00
if emailAddress :
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <p class= " imText " > ' + translate [ ' Email ' ] + ' : <a href= " mailto: ' + \
emailAddress + ' " > ' + emailAddress + ' </a></p> '
2019-12-17 14:57:16 +00:00
if xmppAddress :
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <p class= " imText " > ' + translate [ ' XMPP ' ] + \
' : <a href= " xmpp: ' + xmppAddress + ' " > ' + xmppAddress + ' </a></p> '
2019-12-17 15:25:34 +00:00
if matrixAddress :
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <p class= " imText " > ' + translate [ ' Matrix ' ] + ' : ' + matrixAddress + ' </p> '
2019-12-17 20:44:18 +00:00
if PGPpubKey :
2019-12-17 23:02:02 +00:00
optionsStr + = ' <p class= " pgp " > ' + PGPpubKey . replace ( ' \n ' , ' <br> ' ) + ' </p> '
2019-11-12 10:42:27 +00:00
optionsStr + = ' <form method= " POST " action= " ' + originPathStr + ' /personoptions " > '
optionsStr + = ' <input type= " hidden " name= " pageNumber " value= " ' + str ( pageNumber ) + ' " > '
optionsStr + = ' <input type= " hidden " name= " actor " value= " ' + optionsActor + ' " > '
optionsStr + = ' <input type= " hidden " name= " avatarUrl " value= " ' + optionsProfileUrl + ' " > '
optionsStr + = optionsLinkStr
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <button type= " submit " class= " button " name= " submitView " > ' + \
translate [ ' View ' ] + ' </button> '
2019-11-12 10:42:27 +00:00
optionsStr + = donateStr
2020-02-23 14:24:11 +00:00
optionsStr + = \
' <button type= " submit " class= " button " name= " submit ' + \
followStr + ' " > ' + translate [ followStr ] + ' </button> '
optionsStr + = \
' <button type= " submit " class= " button " name= " submit ' + \
blockStr + ' " > ' + translate [ blockStr ] + ' </button> '
optionsStr + = \
' <button type= " submit " class= " button " name= " submitDM " > ' + \
translate [ ' DM ' ] + ' </button> '
optionsStr + = \
' <button type= " submit " class= " button " name= " submit ' + \
snoozeButtonStr + ' " > ' + translate [ snoozeButtonStr ] + ' </button> '
optionsStr + = \
' <button type= " submit " class= " button " name= " submitReport " > ' + \
translate [ ' Report ' ] + ' </button> '
2019-11-12 10:42:27 +00:00
optionsStr + = ' </form> '
2019-08-24 21:10:20 +00:00
optionsStr + = ' </center> '
optionsStr + = ' </div> '
optionsStr + = ' </div> '
optionsStr + = htmlFooter ( )
return optionsStr
2019-09-07 08:57:52 +00:00
#def htmlBlockConfirm(translate: {},baseDir: str, \
# originPathStr: str, \
# blockActor: str, \
# blockProfileUrl: str) -> str:
# """Asks to confirm a block
# """
# blockDomain,port=getDomainFromActor(blockActor)
#
# 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')
#
# with open(baseDir+'/epicyon-follow.css', 'r') as cssFile:
# profileStyle = cssFile.read()
2019-09-11 09:58:43 +00:00
# blockStr=htmlHeader(cssFilename,profileStyle)
2019-09-07 08:57:52 +00:00
# blockStr+='<div class="block">'
# blockStr+=' <div class="blockAvatar">'
# blockStr+=' <center>'
# blockStr+=' <a href="'+blockActor+'">'
2019-10-18 19:25:21 +00:00
# blockStr+=' <img loading="lazy" src="'+blockProfileUrl+'"/></a>'
2019-09-07 08:57:52 +00:00
# blockStr+=' <p class="blockText">'+translate['Block']+' '+getNicknameFromActor(blockActor)+'@'+blockDomain+' ?</p>'
2019-11-12 10:42:27 +00:00
# blockStr+=' <form method="POST" action="'+originPathStr+'/blockconfirm">'
# blockStr+=' <input type="hidden" name="actor" value="'+blockActor+'">'
# blockStr+=' <button type="submit" class="button" name="submitYes">'+translate['Yes']+'</button>'
# blockStr+=' <a href="'+originPathStr+'"><button class="button">'+translate['No']+'</button></a>'
# blockStr+=' </form>'
2019-09-07 08:57:52 +00:00
# blockStr+='</center>'
# blockStr+='</div>'
# blockStr+='</div>'
# blockStr+=htmlFooter()
# return blockStr
def htmlUnblockConfirm ( translate : { } , baseDir : str , \
originPathStr : str , \
blockActor : str , \
blockProfileUrl : str ) - > str :
2019-08-02 14:36:03 +00:00
""" Asks to confirm unblocking an actor
"""
blockDomain , port = getDomainFromActor ( blockActor )
if os . path . isfile ( baseDir + ' /img/block-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/block-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/block-background.png ' , \
baseDir + ' /accounts/block-background.png ' )
2019-08-02 14:36:03 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-02 14:36:03 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
blockStr = htmlHeader ( cssFilename , profileStyle )
2019-08-02 14:36:03 +00:00
blockStr + = ' <div class= " block " > '
blockStr + = ' <div class= " blockAvatar " > '
blockStr + = ' <center> '
blockStr + = ' <a href= " ' + blockActor + ' " > '
2019-10-18 19:25:21 +00:00
blockStr + = ' <img loading= " lazy " src= " ' + blockProfileUrl + ' " /></a> '
2020-02-23 14:24:11 +00:00
blockStr + = \
' <p class= " blockText " > ' + translate [ ' Stop blocking ' ] + ' ' + \
getNicknameFromActor ( blockActor ) + ' @ ' + blockDomain + ' ?</p> '
2019-11-12 10:42:27 +00:00
blockStr + = ' <form method= " POST " action= " ' + originPathStr + ' /unblockconfirm " > '
blockStr + = ' <input type= " hidden " name= " actor " value= " ' + blockActor + ' " > '
2020-02-23 14:24:11 +00:00
blockStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + \
translate [ ' Yes ' ] + ' </button> '
blockStr + = \
' <a href= " ' + originPathStr + ' " ><button class= " button " > ' + \
translate [ ' No ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
blockStr + = ' </form> '
2019-08-02 14:36:03 +00:00
blockStr + = ' </center> '
blockStr + = ' </div> '
blockStr + = ' </div> '
blockStr + = htmlFooter ( )
return blockStr
2019-09-07 08:57:52 +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
# will be retained even if default_emoji.json is subsequently updated
2019-08-19 20:18:45 +00:00
if not os . path . isfile ( baseDir + ' /emoji/emoji.json ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /emoji/default_emoji.json ' , \
baseDir + ' /emoji/emoji.json ' )
2019-08-19 20:18:45 +00:00
2019-08-19 20:01:29 +00:00
actor = path . replace ( ' /search ' , ' ' )
nickname = getNicknameFromActor ( actor )
domain , port = getDomainFromActor ( actor )
if os . path . isfile ( baseDir + ' /img/search-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/search-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/search-background.png ' , \
baseDir + ' /accounts/search-background.png ' )
2019-08-19 20:01:29 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
cssFilename = baseDir + ' /follow.css '
with open ( cssFilename , ' r ' ) as cssFile :
2019-08-19 20:01:29 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
emojiStr = htmlHeader ( cssFilename , profileStyle )
2019-08-19 20:01:29 +00:00
emojiStr + = ' <div class= " follow " > '
emojiStr + = ' <div class= " followAvatar " > '
emojiStr + = ' <center> '
2020-02-23 14:24:11 +00:00
emojiStr + = \
' <p class= " followText " > ' + \
translate [ ' Enter an emoji name to search for ' ] + ' </p> '
2019-11-12 10:42:27 +00:00
emojiStr + = ' <form method= " POST " action= " ' + actor + ' /searchhandleemoji " > '
emojiStr + = ' <input type= " hidden " name= " actor " value= " ' + actor + ' " > '
emojiStr + = ' <input type= " text " name= " searchtext " autofocus><br> '
2020-02-23 14:24:11 +00:00
emojiStr + = \
' <button type= " submit " class= " button " name= " submitSearch " > ' + \
translate [ ' Submit ' ] + ' </button> '
2019-11-12 10:42:27 +00:00
emojiStr + = ' </form> '
2019-08-19 20:01:29 +00:00
emojiStr + = ' </center> '
emojiStr + = ' </div> '
emojiStr + = ' </div> '
emojiStr + = htmlFooter ( )
return emojiStr
2019-10-10 18:47:58 +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
"""
2019-10-10 18:55:31 +00:00
firstDayOfMonth = datetime ( year , monthNumber , 1 , 0 , 0 )
2019-10-10 18:40:01 +00:00
return int ( firstDayOfMonth . strftime ( " % w " ) ) + 1
2019-10-10 18:25:42 +00:00
2020-02-22 16:00:27 +00:00
2019-10-11 16:00:54 +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
"""
2019-10-12 14:31:07 +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
2019-10-11 16:00:54 +00:00
cssFilename = baseDir + ' /epicyon-calendar.css '
if os . path . isfile ( baseDir + ' /calendar.css ' ) :
cssFilename = baseDir + ' /calendar.css '
with open ( cssFilename , ' r ' ) as cssFile :
calendarStyle = cssFile . read ( )
2020-02-23 10:23:12 +00:00
2019-10-11 16:00:54 +00:00
calendarStr = htmlHeader ( cssFilename , calendarStyle )
calendarStr + = ' <main><table class= " calendar " > \n '
calendarStr + = ' <caption class= " calendar__banner--month " > \n '
2020-02-23 14:24:11 +00:00
calendarStr + = \
' <a href= " ' + actor + ' /calendar?year= ' + str ( year ) + \
' ?month= ' + str ( monthNumber ) + ' " > '
calendarStr + = \
' <h1> ' + str ( dayNumber ) + ' ' + monthName + \
' </h1></a><br><span class= " year " > ' + str ( year ) + ' </span> \n '
2019-10-11 16:00:54 +00:00
calendarStr + = ' </caption> \n '
calendarStr + = ' <tbody> \n '
2019-10-11 20:45:46 +00:00
2020-02-23 11:20:02 +00:00
iconsDir = getIconsDir ( baseDir )
2020-02-23 10:23:12 +00:00
if dayEvents :
for eventPost in dayEvents :
eventTime = None
eventDescription = None
eventPlace = None
2020-02-23 11:20:02 +00:00
postId = None
# get the time place and description
2020-02-23 10:23:12 +00:00
for ev in eventPost :
if ev [ ' type ' ] == ' Event ' :
2020-02-23 11:20:02 +00:00
if ev . get ( ' postId ' ) :
postId = ev [ ' postId ' ]
2020-02-23 10:23:12 +00:00
if ev . get ( ' startTime ' ) :
2020-02-23 14:24:11 +00:00
eventDate = \
datetime . strptime ( ev [ ' startTime ' ] , \
" % Y- % m- %d T % H: % M: % S % z " )
2020-02-23 10:23:12 +00:00
eventTime = eventDate . strftime ( " % H: % M " ) . strip ( )
if ev . get ( ' name ' ) :
eventDescription = ev [ ' name ' ] . strip ( )
elif ev [ ' type ' ] == ' Place ' :
if ev . get ( ' name ' ) :
eventPlace = ev [ ' name ' ]
2020-02-23 11:20:02 +00:00
deleteButtonStr = ' '
if postId :
2020-02-23 14:24:11 +00:00
deleteButtonStr = \
' <td class= " calendar__day__icons " ><a href= " ' + actor + \
' /eventdelete?id= ' + postId + ' ?year= ' + str ( year ) + \
' ?month= ' + str ( monthNumber ) + ' ?day= ' + str ( dayNumber ) + \
' ?time= ' + eventTime + \
' " ><img class= " calendardayicon " loading= " lazy " alt= " ' + \
translate [ ' Delete this event ' ] + ' | " title= " ' + \
translate [ ' Delete this event ' ] + ' | " src= " / ' + \
iconsDir + ' /delete.png " /></a></td> '
2020-02-23 11:20:02 +00:00
2020-02-23 10:23:12 +00:00
if eventTime and eventDescription and eventPlace :
2020-02-23 14:24:11 +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-02-23 14:24:11 +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-02-23 14:24:11 +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-02-23 14:24:11 +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-02-23 14:24:11 +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
2019-10-11 16:00:54 +00:00
calendarStr + = ' </tbody> \n '
calendarStr + = ' </table></main> \n '
calendarStr + = htmlFooter ( )
return calendarStr
2019-10-10 14:43:21 +00:00
def htmlCalendar ( translate : { } , \
2019-10-11 19:25:36 +00:00
baseDir : str , path : str , \
httpPrefix : str , domainFull : str ) - > str :
2019-10-10 14:43:21 +00:00
""" Show the calendar for a person
"""
2019-10-11 22:27:00 +00:00
iconsDir = getIconsDir ( baseDir )
2019-10-11 19:25:36 +00:00
domain = domainFull
if ' : ' in domainFull :
domain = domainFull . split ( ' : ' ) [ 0 ]
2019-10-10 18:25:42 +00:00
monthNumber = 0
2019-10-11 16:00:54 +00:00
dayNumber = None
2019-10-10 18:25:42 +00:00
year = 1970
2019-10-11 19:25:36 +00:00
actor = httpPrefix + ' :// ' + domainFull + path . replace ( ' /calendar ' , ' ' )
2019-10-10 18:25:42 +00:00
if ' ? ' in actor :
first = True
for p in actor . split ( ' ? ' ) :
if not first :
if ' = ' in p :
if p . split ( ' = ' ) [ 0 ] == ' year ' :
numStr = p . split ( ' = ' ) [ 1 ]
if numStr . isdigit ( ) :
year = int ( numStr )
elif p . split ( ' = ' ) [ 0 ] == ' month ' :
numStr = p . split ( ' = ' ) [ 1 ]
if numStr . isdigit ( ) :
monthNumber = int ( numStr )
2019-10-11 16:00:54 +00:00
elif p . split ( ' = ' ) [ 0 ] == ' day ' :
numStr = p . split ( ' = ' ) [ 1 ]
if numStr . isdigit ( ) :
dayNumber = int ( numStr )
2019-10-10 18:25:42 +00:00
first = False
actor = actor . split ( ' ? ' ) [ 0 ]
2019-10-10 20:51:36 +00:00
currDate = datetime . now ( )
2019-10-10 18:34:50 +00:00
if year == 1970 and monthNumber == 0 :
year = currDate . year
monthNumber = currDate . month
2019-10-10 18:25:42 +00:00
2019-10-11 16:00:54 +00:00
nickname = getNicknameFromActor ( actor )
if os . path . isfile ( baseDir + ' /img/calendar-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/calendar-background.png ' ) :
2020-02-23 14:24:11 +00:00
copyfile ( baseDir + ' /img/calendar-background.png ' , \
baseDir + ' /accounts/calendar-background.png ' )
2019-10-11 16:00:54 +00:00
2020-02-23 14:24:11 +00:00
months = ( ' January ' , ' February ' , ' March ' , ' April ' , \
' May ' , ' June ' , ' July ' , ' August ' , ' September ' , \
' October ' , ' November ' , ' December ' )
2020-02-23 10:20:10 +00:00
monthName = translate [ months [ monthNumber - 1 ] ]
2019-10-11 16:00:54 +00:00
if dayNumber :
dayEvents = None
2020-02-23 14:24:11 +00:00
events = \
getTodaysEvents ( baseDir , nickname , domain , \
year , monthNumber , dayNumber )
2020-02-23 10:23:12 +00:00
if events :
if events . get ( str ( dayNumber ) ) :
dayEvents = events [ str ( dayNumber ) ]
2019-10-11 16:00:54 +00:00
return htmlCalendarDay ( translate , baseDir , path , \
year , monthNumber , dayNumber , \
nickname , domain , dayEvents , \
2019-10-12 16:18:24 +00:00
monthName , actor )
2019-10-11 16:00:54 +00:00
2020-02-23 14:24:11 +00:00
events = \
getCalendarEvents ( baseDir , nickname , domain , year , monthNumber )
2020-02-23 10:20:10 +00:00
2019-10-11 14:23:23 +00:00
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
2020-02-23 14:24:11 +00:00
print ( ' Calendar year= ' + str ( year ) + ' month= ' + str ( monthNumber ) + \
' ' + str ( weekDayOfMonthStart ( monthNumber , year ) ) )
2019-10-10 18:25:42 +00:00
if monthNumber < 12 :
daysInMonth = ( date ( year , monthNumber + 1 , 1 ) - date ( year , monthNumber , 1 ) ) . days
else :
daysInMonth = ( date ( year + 1 , 1 , 1 ) - date ( year , monthNumber , 1 ) ) . days
2019-10-10 14:43:21 +00:00
cssFilename = baseDir + ' /epicyon-calendar.css '
if os . path . isfile ( baseDir + ' /calendar.css ' ) :
cssFilename = baseDir + ' /calendar.css '
with open ( cssFilename , ' r ' ) as cssFile :
calendarStyle = cssFile . read ( )
2019-10-10 18:25:42 +00:00
2019-10-10 14:43:21 +00:00
calendarStr = htmlHeader ( cssFilename , calendarStyle )
2019-10-10 20:00:22 +00:00
calendarStr + = ' <main><table class= " calendar " > \n '
2019-10-10 18:25:42 +00:00
calendarStr + = ' <caption class= " calendar__banner--month " > \n '
2020-02-23 14:24:11 +00:00
calendarStr + = \
' <a href= " ' + actor + ' /calendar?year= ' + str ( prevYear ) + \
' ?month= ' + str ( prevMonthNumber ) + ' " > '
calendarStr + = \
' <img loading= " lazy " alt= " ' + translate [ ' Previous month ' ] + \
' " title= " ' + translate [ ' Previous month ' ] + ' " src= " / ' + iconsDir + \
' /prev.png " class= " buttonprev " /></a> \n '
2019-10-12 16:18:24 +00:00
calendarStr + = ' <a href= " ' + actor + ' /inbox " > '
calendarStr + = ' <h1> ' + monthName + ' </h1></a> \n '
2020-02-23 14:24:11 +00:00
calendarStr + = \
' <a href= " ' + actor + ' /calendar?year= ' + str ( nextYear ) + \
' ?month= ' + str ( nextMonthNumber ) + ' " > '
calendarStr + = \
' <img loading= " lazy " alt= " ' + translate [ ' Next month ' ] + \
' " title= " ' + translate [ ' Next month ' ] + ' " src= " / ' + iconsDir + \
' /prev.png " class= " buttonnext " /></a> \n '
2019-10-10 18:25:42 +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
2019-10-10 19:00:57 +00:00
dow = weekDayOfMonthStart ( monthNumber , year )
2019-10-10 19:06:32 +00:00
for weekOfMonth in range ( 1 , 6 ) :
2019-10-10 18:25:42 +00:00
calendarStr + = ' <tr> \n '
2019-10-10 19:06:32 +00:00
for dayNumber in range ( 1 , 8 ) :
2019-10-10 19:21:32 +00:00
if ( weekOfMonth > 1 and dayOfMonth < daysInMonth ) or \
2019-10-10 19:00:57 +00:00
( weekOfMonth == 1 and dayNumber > = dow ) :
2019-10-10 18:25:42 +00:00
dayOfMonth + = 1
2019-10-10 20:51:36 +00:00
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-02-23 14:24:11 +00:00
url = actor + ' /calendar?year= ' + str ( year ) + ' ?month= ' + \
str ( monthNumber ) + ' ?day= ' + str ( dayOfMonth )
2019-10-11 20:12:24 +00:00
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-02-23 15:32:47 +00:00
calendarStr + = \
' <td class= " calendar__day__cell " data-event= " " > ' + dayLink + ' </td> \n '
2019-10-11 10:28:28 +00:00
else :
2020-02-23 15:32:47 +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-02-23 15:32:47 +00:00
calendarStr + = \
' <td class= " calendar__day__cell " > ' + str ( dayOfMonth ) + ' </td> \n '
2019-10-11 10:28:28 +00:00
else :
2020-02-23 15:32:47 +00:00
calendarStr + = \
' <td class= " calendar__day__cell " data-today= " " > ' + str ( dayOfMonth ) + ' </td> \n '
2019-10-10 18:25:42 +00:00
else :
calendarStr + = ' <td class= " calendar__day__cell " ></td> \n '
calendarStr + = ' </tr> \n '
calendarStr + = ' </tbody> \n '
2019-10-10 20:00:22 +00:00
calendarStr + = ' </table></main> \n '
2019-10-10 14:43:21 +00:00
calendarStr + = htmlFooter ( )
return calendarStr
2019-12-13 17:57:15 +00:00
def htmlHashTagSwarm ( baseDir : str , actor : str ) - > str :
""" Returns a tag swarm of today ' s hashtags
2019-12-12 20:39:49 +00:00
"""
2019-12-12 20:45:55 +00:00
daysSinceEpoch = ( datetime . utcnow ( ) - datetime ( 1970 , 1 , 1 ) ) . days
daysSinceEpochStr = str ( daysSinceEpoch ) + ' '
2019-12-13 10:33:33 +00:00
nickname = getNicknameFromActor ( actor )
2019-12-13 17:57:15 +00:00
tagSwarm = [ ]
2019-12-12 20:39:49 +00:00
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
2019-12-13 09:46:46 +00:00
hashTagName = f . split ( ' . ' ) [ 0 ]
if isBlockedHashtag ( baseDir , hashTagName ) :
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 :
lines = tagsFile . readlines ( )
for l in lines :
if ' ' not in l :
continue
postDaysSinceEpochStr = l . split ( ' ' ) [ 0 ]
if not postDaysSinceEpochStr . isdigit ( ) :
continue
postDaysSinceEpoch = int ( postDaysSinceEpochStr )
if postDaysSinceEpoch < daysSinceEpoch :
break
if postDaysSinceEpoch == daysSinceEpoch :
2019-12-13 17:57:15 +00:00
tagSwarm . append ( hashTagName )
2019-12-12 20:39:49 +00:00
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 ( )
tagSwarmStr = ' '
for tagName in tagSwarm :
2020-02-23 15:32:47 +00:00
tagSwarmStr + = \
' <a href= " ' + actor + ' /tags/ ' + tagName + \
' " class= " hashtagswarm " > ' + tagName + ' </a> '
2019-12-13 17:57:15 +00:00
tagSwarmHtml = tagSwarmStr . strip ( ) + ' \n '
return tagSwarmHtml
2019-12-12 20:39:49 +00:00
2019-09-07 08:57:52 +00:00
def htmlSearch ( translate : { } , \
2019-12-17 13:09:08 +00:00
baseDir : str , path : str ) - > str :
2019-07-30 22:34:04 +00:00
""" Search called from the timeline icon
"""
actor = path . replace ( ' /search ' , ' ' )
nickname = getNicknameFromActor ( actor )
domain , port = getDomainFromActor ( actor )
2019-12-17 12:58:10 +00:00
2019-07-30 22:34:04 +00:00
if os . path . isfile ( baseDir + ' /img/search-background.png ' ) :
if not os . path . isfile ( baseDir + ' /accounts/search-background.png ' ) :
2020-02-23 15:32:47 +00:00
copyfile ( baseDir + ' /img/search-background.png ' , \
baseDir + ' /accounts/search-background.png ' )
2019-07-30 22:34:04 +00:00
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-follow.css '
if os . path . isfile ( baseDir + ' /follow.css ' ) :
2019-12-17 13:09:08 +00:00
cssFilename = baseDir + ' /follow.css '
2019-09-11 09:58:43 +00:00
with open ( cssFilename , ' r ' ) as cssFile :
2019-07-30 22:34:04 +00:00
profileStyle = cssFile . read ( )
2019-09-11 09:58:43 +00:00
followStr = htmlHeader ( cssFilename , profileStyle )
2019-07-30 22:34:04 +00:00
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
2020-02-23 15:32:47 +00:00
followStr + = \
' <p class= " followText " > ' + \
translate [ ' Enter an address, shared item, #hashtag, *skill or :emoji: to search for ' ] + ' </p> '
2019-11-12 10:42:27 +00:00
followStr + = ' <form method= " POST " accept-charset= " UTF-8 " action= " ' + actor + ' /searchhandle " > '
followStr + = ' <input type= " hidden " name= " actor " value= " ' + actor + ' " > '
followStr + = ' <input type= " text " name= " searchtext " autofocus><br> '
followStr + = ' <button type= " submit " class= " button " name= " submitSearch " > ' + translate [ ' Submit ' ] + ' </button> '
2019-12-17 13:17:00 +00:00
followStr + = ' <button type= " submit " class= " button " name= " submitBack " > ' + translate [ ' Go Back ' ] + ' </button> '
2019-11-12 10:42:27 +00:00
followStr + = ' </form> '
2019-12-13 17:57:15 +00:00
followStr + = ' <p class= " hashtagswarm " > ' + htmlHashTagSwarm ( baseDir , actor ) + ' </p> '
2019-07-30 22:34:04 +00:00
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
2019-11-24 17:40:31 +00:00
def htmlProfileAfterSearch ( recentPostsCache : { } , maxRecentPosts : int , \
translate : { } , \
2019-09-07 08:57:52 +00:00
baseDir : str , path : str , httpPrefix : str , \
2019-07-30 22:34:04 +00:00
nickname : str , domain : str , port : int , \
profileHandle : str , \
session , wfRequest : { } , personCache : { } ,
2019-08-14 20:12:27 +00:00
debug : bool , projectVersion : 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 \
' /channel/ ' in profileHandle or \
' /profile/ ' in profileHandle or \
' /@ ' in profileHandle :
2019-07-30 22:34:04 +00:00
searchNickname = getNicknameFromActor ( profileHandle )
searchDomain , searchPort = getDomainFromActor ( profileHandle )
else :
if ' @ ' not in profileHandle :
if debug :
print ( ' DEBUG: no @ in ' + profileHandle )
return None
if profileHandle . startswith ( ' @ ' ) :
profileHandle = profileHandle [ 1 : ]
if ' @ ' not in profileHandle :
if debug :
print ( ' DEBUG: no @ in ' + profileHandle )
return None
searchNickname = profileHandle . split ( ' @ ' ) [ 0 ]
searchDomain = profileHandle . split ( ' @ ' ) [ 1 ]
searchPort = None
if ' : ' in searchDomain :
searchPort = int ( searchDomain . split ( ' : ' ) [ 1 ] )
searchDomain = searchDomain . split ( ' : ' ) [ 0 ]
if not searchNickname :
if debug :
print ( ' DEBUG: No nickname found in ' + profileHandle )
return None
if not searchDomain :
if debug :
print ( ' DEBUG: No domain found in ' + profileHandle )
return None
searchDomainFull = searchDomain
if searchPort :
if searchPort != 80 and searchPort != 443 :
2019-08-16 20:35:11 +00:00
if ' : ' not in searchDomain :
searchDomainFull = searchDomain + ' : ' + str ( searchPort )
2019-07-30 22:34:04 +00:00
profileStr = ' '
2019-09-11 09:58:43 +00:00
cssFilename = baseDir + ' /epicyon-profile.css '
if os . path . isfile ( baseDir + ' /epicyon.css ' ) :
cssFilename = baseDir + ' /epicyon.css '
with open ( cssFilename , ' r ' ) as cssFile :
2020-02-23 15:32:47 +00:00
wf = \
webfingerHandle ( session , \
searchNickname + ' @ ' + searchDomainFull , \
httpPrefix , wfRequest , \
domain , projectVersion )
2019-07-30 22:34:04 +00:00
if not wf :
if debug :
2019-08-04 20:06:25 +00:00
print ( ' DEBUG: Unable to webfinger ' + searchNickname + ' @ ' + searchDomainFull )
2019-07-30 22:34:04 +00:00
return None
2019-10-17 15:18:43 +00:00
personUrl = None
if wf . get ( ' errors ' ) :
personUrl = httpPrefix + ' :// ' + searchDomainFull + ' /users/ ' + searchNickname
2020-02-23 15:32:47 +00:00
asHeader = {
' Accept ' : ' application/activity+json; profile= " https://www.w3.org/ns/activitystreams " '
}
2019-10-17 15:18:43 +00:00
if not personUrl :
personUrl = getUserUrl ( wf )
2019-09-03 18:38:52 +00:00
if not personUrl :
2019-10-21 09:57:42 +00:00
# try single user instance
asHeader = { ' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' }
personUrl = httpPrefix + ' :// ' + searchDomainFull
2019-09-01 12:09:29 +00:00
profileJson = getJson ( session , personUrl , asHeader , None , projectVersion , httpPrefix , domain )
2019-10-17 22:35:56 +00:00
if not profileJson :
2020-02-23 15:32:47 +00:00
asHeader = {
' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " '
}
profileJson = \
getJson ( session , personUrl , asHeader , None , projectVersion , httpPrefix , domain )
2019-07-30 22:34:04 +00:00
if not profileJson :
if debug :
print ( ' DEBUG: No actor returned from ' + personUrl )
return None
avatarUrl = ' '
if profileJson . get ( ' icon ' ) :
if profileJson [ ' icon ' ] . get ( ' url ' ) :
avatarUrl = profileJson [ ' icon ' ] [ ' url ' ]
2019-08-18 13:30:40 +00:00
if not avatarUrl :
2019-08-20 09:37:09 +00:00
avatarUrl = getPersonAvatarUrl ( baseDir , personUrl , personCache )
2019-08-22 18:36:07 +00:00
displayName = searchNickname
2019-08-22 18:37:22 +00:00
if profileJson . get ( ' name ' ) :
2019-08-22 18:36:07 +00:00
displayName = profileJson [ ' name ' ]
2019-07-30 22:34:04 +00:00
profileDescription = ' '
2019-07-31 12:44:08 +00:00
if profileJson . get ( ' summary ' ) :
profileDescription = profileJson [ ' summary ' ]
2019-07-30 22:34:04 +00:00
outboxUrl = None
if not profileJson . get ( ' outbox ' ) :
if debug :
pprint ( profileJson )
print ( ' DEBUG: No outbox found ' )
return None
outboxUrl = profileJson [ ' outbox ' ]
profileBackgroundImage = ' '
if profileJson . get ( ' image ' ) :
if profileJson [ ' image ' ] . get ( ' url ' ) :
profileBackgroundImage = profileJson [ ' image ' ] [ ' url ' ]
profileStyle = cssFile . read ( ) . replace ( ' image.png ' , profileBackgroundImage )
2019-12-10 14:48:08 +00:00
if httpPrefix != ' https ' :
profileStyle = profileStyle . replace ( ' https:// ' , httpPrefix + ' :// ' )
2019-07-31 09:00:44 +00:00
# url to return to
backUrl = path
if not backUrl . endswith ( ' /inbox ' ) :
backUrl + = ' /inbox '
2019-10-23 14:27:43 +00:00
profileDescriptionShort = profileDescription
if ' \n ' in profileDescription :
if len ( profileDescription . split ( ' \n ' ) ) > 2 :
profileDescriptionShort = ' '
else :
if ' <br> ' in profileDescription :
if len ( profileDescription . split ( ' <br> ' ) ) > 2 :
profileDescriptionShort = ' '
2019-10-23 15:09:20 +00:00
# keep the profile description short
if len ( profileDescriptionShort ) > 256 :
profileDescriptionShort = ' '
# remove formatting from profile description used on title
2019-10-23 15:06:22 +00:00
avatarDescription = ' '
if profileJson . get ( ' summary ' ) :
2020-02-23 15:32:47 +00:00
avatarDescription = \
profileJson [ ' summary ' ] . replace ( ' <br> ' , ' \n ' ) . replace ( ' <p> ' , ' ' ) . replace ( ' </p> ' , ' ' )
2019-11-12 10:42:27 +00:00
profileStr = ' <div class= " hero-image " > '
profileStr + = ' <div class= " hero-text " > '
2020-02-23 15:32:47 +00:00
profileStr + = \
' <img loading= " lazy " src= " ' + avatarUrl + \
' " alt= " ' + avatarDescription + ' " title= " ' + \
avatarDescription + ' " > '
2019-11-12 10:42:27 +00:00
profileStr + = ' <h1> ' + displayName + ' </h1> '
profileStr + = ' <p><b>@ ' + searchNickname + ' @ ' + searchDomainFull + ' </b></p> '
profileStr + = ' <p> ' + profileDescriptionShort + ' </p> '
profileStr + = ' </div> '
profileStr + = ' </div> '
profileStr + = ' <div class= " container " > \n '
profileStr + = ' <form method= " POST " action= " ' + backUrl + ' /followconfirm " > '
profileStr + = ' <center> '
2020-02-23 15:32:47 +00:00
profileStr + = \
' <input type= " hidden " name= " actor " value= " ' + personUrl + ' " > '
profileStr + = \
' <button type= " submit " class= " button " name= " submitYes " > ' + translate [ ' Follow ' ] + ' </button> '
profileStr + = \
' <button type= " submit " class= " button " name= " submitView " > ' + translate [ ' View ' ] + ' </button> '
profileStr + = \
' <a href= " ' + backUrl + ' " ><button class= " button " > ' + translate [ ' Go Back ' ] + ' </button></a> '
2019-11-12 10:42:27 +00:00
profileStr + = ' </center> '
profileStr + = ' </form> '
profileStr + = ' </div> '
2019-07-30 22:34:04 +00:00
2019-07-31 12:44:08 +00:00
profileStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-09-11 18:10:32 +00:00
iconsDir = getIconsDir ( baseDir )
2019-07-30 22:34:04 +00:00
result = [ ]
i = 0
2019-08-14 20:39:26 +00:00
for item in parseUserFeed ( session , outboxUrl , asHeader , \
projectVersion , httpPrefix , domain ) :
2019-07-30 22:34:04 +00:00
if not item . get ( ' type ' ) :
continue
2019-07-31 10:09:02 +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
profileStr + = \
2019-11-24 17:40:31 +00:00
individualPostAsHtml ( recentPostsCache , maxRecentPosts , \
iconsDir , translate , None , baseDir , \
2019-07-30 22:34:04 +00:00
session , wfRequest , personCache , \
nickname , domain , port , \
2019-08-14 20:12:27 +00:00
item , avatarUrl , False , False , \
2019-09-23 21:40:31 +00:00
httpPrefix , projectVersion , ' inbox ' , \
2019-10-19 11:42:41 +00:00
False , False , False , False , False )
2019-07-30 22:34:04 +00:00
i + = 1
if i > = 20 :
break
2019-09-11 09:58:43 +00:00
return htmlHeader ( cssFilename , profileStyle ) + profileStr + htmlFooter ( )
2020-02-24 18:26:07 +00:00
def htmlBlogPost ( baseDir : str , httpPrefix : str , translate : { } , \
nickname : str , domain : str , postJsonObject : { } ) - > str :
""" Returns a html blog post
"""
blogStr = ' '
cssFilename = baseDir + ' /epicyon-blog.css '
if os . path . isfile ( baseDir + ' /blog.css ' ) :
2020-02-24 19:21:33 +00:00
cssFilename = baseDir + ' /blog.css '
2020-02-24 18:26:07 +00:00
with open ( cssFilename , ' r ' ) as cssFile :
blogCSS = cssFile . read ( )
blogStr = htmlHeader ( cssFilename , blogCSS )
if postJsonObject [ ' object ' ] . get ( ' summary ' ) :
2020-02-24 19:58:01 +00:00
blogStr + = ' <h1> ' + postJsonObject [ ' object ' ] [ ' summary ' ] + ' </h1> \n '
2020-02-24 18:26:07 +00:00
if postJsonObject [ ' object ' ] . get ( ' published ' ) :
if ' T ' in postJsonObject [ ' object ' ] [ ' published ' ] :
2020-02-24 19:58:01 +00:00
blogStr + = ' <h3> ' + postJsonObject [ ' object ' ] [ ' published ' ] . split ( ' T ' ) [ 0 ] + ' </h3> \n '
2020-02-24 18:26:07 +00:00
if postJsonObject [ ' object ' ] . get ( ' content ' ) :
contentStr = addEmbeddedElements ( translate , postJsonObject [ ' object ' ] [ ' content ' ] )
if postJsonObject [ ' object ' ] . get ( ' tag ' ) :
contentStr = \
replaceEmojiFromTags ( contentStr , \
postJsonObject [ ' object ' ] [ ' tag ' ] , ' content ' )
2020-02-24 20:38:48 +00:00
blogStr + = ' <br> ' + contentStr + ' \n '
2020-02-24 18:26:07 +00:00
2020-02-24 20:40:03 +00:00
blogStr + = ' <br><hr> \n '
2020-02-24 18:26:07 +00:00
blogStr + = \
' <p class= " about " ><a href= " ' + httpPrefix + ' :// ' + domain + \
2020-02-24 19:58:01 +00:00
' /users/ ' + nickname + ' " > ' + translate [ ' About the author ' ] + ' </a></p> \n '
2020-02-24 18:26:07 +00:00
return blogStr + htmlFooter ( )
return None