2019-07-20 21:13:36 +00:00
__filename__ = " webinterface.py "
__author__ = " Bob Mottram "
__license__ = " AGPL3+ "
__version__ = " 0.0.1 "
__maintainer__ = " Bob Mottram "
__email__ = " bob@freedombone.net "
__status__ = " Production "
import json
2019-07-24 22:38:42 +00:00
import time
import os
2019-08-02 09:52:12 +00:00
import commentjson
2019-07-31 13:11:09 +00:00
from datetime import datetime
2019-07-24 22:38:42 +00:00
from shutil import copyfile
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-07-21 12:41:31 +00:00
from utils import getNicknameFromActor
from utils import getDomainFromActor
2019-07-22 14:09:21 +00:00
from posts import getPersonBox
2019-07-29 19:46:30 +00:00
from follow import isFollowingActor
2019-07-30 22:34:04 +00:00
from webfinger import webfingerHandle
from posts import getUserUrl
from posts import parseUserFeed
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-01 12:18:22 +00:00
from announce import announcedByPerson
2019-07-20 21:13:36 +00:00
2019-08-02 09:52:12 +00:00
def htmlEditProfile ( baseDir : str , path : str , domain : str , port : int ) - > str :
""" Shows the edit profile screen
"""
pathOriginal = path
path = path . replace ( ' /inbox ' , ' ' ) . replace ( ' /outbox ' , ' ' ) . replace ( ' /shares ' , ' ' )
nickname = getNicknameFromActor ( path )
domainFull = domain
if port :
if port != 80 and port != 443 :
domainFull = domain + ' : ' + str ( port )
actorFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' .json '
if not os . path . isfile ( actorFilename ) :
return ' '
preferredNickname = nickname
bioStr = ' '
manuallyApprovesFollowers = ' checked '
with open ( actorFilename , ' r ' ) as fp :
actorJson = commentjson . load ( fp )
if actorJson . get ( ' preferredUsername ' ) :
preferredNickname = actorJson [ ' preferredUsername ' ]
if actorJson . get ( ' summary ' ) :
bioStr = actorJson [ ' summary ' ]
if actorJson . get ( ' manuallyApprovesFollowers ' ) :
if actorJson [ ' manuallyApprovesFollowers ' ] :
manuallyApprovesFollowers = ' checked '
else :
manuallyApprovesFollowers = ' '
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 ( )
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 09:52:12 +00:00
with open ( baseDir + ' /epicyon-profile.css ' , ' r ' ) as cssFile :
newPostCSS = cssFile . read ( )
editProfileForm = htmlHeader ( newPostCSS )
editProfileForm + = \
' <form enctype= " multipart/form-data " method= " POST " action= " ' + path + ' /profiledata " > ' \
' <div class= " vertical-center " > ' \
' <p class= " new-post-text " >Edit profile for ' + nickname + ' @ ' + domainFull + ' </p> ' \
' <div class= " container " > ' \
' <input type= " text " placeholder= " Preferred name " name= " preferredNickname " value= " ' + preferredNickname + ' " > ' \
' <textarea id= " message " name= " bio " placeholder= " Your bio... " style= " height:200px " > ' + bioStr + ' </textarea> ' \
' </div> ' \
' <div class= " container " > ' \
' Avatar image ' \
' <input type= " file " id= " avatar " name= " avatar " ' \
' accept= " .png " > ' \
' <br>Background image ' \
' <input type= " file " id= " image " name= " image " ' \
' accept= " .png " > ' \
' <br>Timeline banner image ' \
' <input type= " file " id= " banner " name= " banner " ' \
' accept= " .png " > ' \
' </div> ' \
' <div class= " container " > ' \
2019-08-02 11:43:14 +00:00
' <input type= " checkbox " class=profilecheckbox " name= " approveFollowers " ' + manuallyApprovesFollowers + ' >Approve follower requests<br> ' \
' <br>Filtered words ' \
' <textarea id= " message " name= " filteredWords " placeholder= " Filtered words or phrases (one per line) " style= " height:200px " > ' + filterStr + ' </textarea> ' \
' <br>Blocked accounts ' \
2019-08-02 11:46:42 +00:00
' <textarea id= " message " name= " blocked " placeholder= " Blocked accounts, one per line, in the form nickname@domain or *@blockeddomain " style= " height:200px " > ' + blockedStr + ' </textarea> ' \
2019-08-02 09:52:12 +00:00
' </div> ' \
' <div class= " container " > ' \
' <input type= " submit " name= " submitProfile " value= " Submit " > ' \
' <a href= " ' + pathOriginal + ' " ><button class= " cancelbtn " >Cancel</button></a> ' \
' </div> ' + \
' </div> ' \
' </form> '
editProfileForm + = htmlFooter ( )
return editProfileForm
2019-07-25 10:56:24 +00:00
def htmlGetLoginCredentials ( loginParams : str , lastLoginTime : int ) - > ( str , str ) :
""" 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-07-24 22:38:42 +00:00
return None , None
# minimum time between login attempts
currTime = int ( time . time ( ) )
if currTime < lastLoginTime + 5 :
return None , None
if ' & ' not in loginParams :
return None , None
loginArgs = loginParams . split ( ' & ' )
nickname = None
password = None
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 ]
return nickname , password
def htmlLogin ( baseDir : str ) - > str :
if not os . path . isfile ( baseDir + ' /accounts/login.png ' ) :
copyfile ( baseDir + ' /img/login.png ' , baseDir + ' /accounts/login.png ' )
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 ' ) :
copyfile ( baseDir + ' /img/login-background.png ' , baseDir + ' /accounts/login-background.png ' )
2019-07-25 19:22:19 +00:00
2019-07-25 19:56:25 +00:00
loginText = ' <p class= " login-text " >Welcome. Please enter your login details below.</p> '
2019-07-25 19:22:19 +00:00
if os . path . isfile ( baseDir + ' /accounts/login.txt ' ) :
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> '
with open ( baseDir + ' /epicyon-login.css ' , ' r ' ) as cssFile :
loginCSS = cssFile . read ( )
2019-07-24 22:38:42 +00:00
loginForm = htmlHeader ( loginCSS )
loginForm + = \
2019-07-25 21:39:09 +00:00
' <form method= " POST " action= " /login " > ' \
2019-07-24 22:38:42 +00:00
' <div class= " imgcontainer " > ' \
2019-07-25 19:22:19 +00:00
' <img src= " login.png " alt= " login image " class= " loginimage " > ' + \
loginText + \
2019-07-24 22:38:42 +00:00
' </div> ' \
' ' \
' <div class= " container " > ' \
' <label for= " nickname " ><b>Nickname</b></label> ' \
2019-07-25 10:56:24 +00:00
' <input type= " text " placeholder= " Enter Nickname " name= " username " required> ' \
2019-07-24 22:38:42 +00:00
' ' \
' <label for= " password " ><b>Password</b></label> ' \
' <input type= " password " placeholder= " Enter Password " name= " password " required> ' \
' ' \
2019-07-25 10:56:24 +00:00
' <button type= " submit " name= " submit " >Login</button> ' \
2019-07-24 22:38:42 +00:00
' </div> ' \
' </form> '
loginForm + = htmlFooter ( )
return loginForm
2019-07-31 13:51:10 +00:00
def htmlNewPost ( baseDir : str , path : str , inReplyTo : str ) - > str :
replyStr = ' '
2019-07-28 11:35:57 +00:00
if not path . endswith ( ' /newshare ' ) :
2019-07-31 13:51:10 +00:00
if not inReplyTo :
newPostText = ' <p class= " new-post-text " >Enter your post text below.</p> '
else :
newPostText = ' <p class= " new-post-text " >Enter your reply to <a href= " ' + inReplyTo + ' " >this post</a> below.</p> '
replyStr = ' <input type= " hidden " name= " replyTo " value= " ' + inReplyTo + ' " > '
2019-07-28 11:35:57 +00:00
else :
newPostText = ' <p class= " new-post-text " >Enter the details for your shared item below.</p> '
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> '
with open ( baseDir + ' /epicyon-profile.css ' , ' r ' ) as cssFile :
newPostCSS = cssFile . read ( )
2019-07-26 12:26:41 +00:00
pathBase = path . replace ( ' /newpost ' , ' ' ) . replace ( ' /newshare ' , ' ' ) . replace ( ' /newunlisted ' , ' ' ) . replace ( ' /newfollowers ' , ' ' ) . replace ( ' /newdm ' , ' ' )
scopeIcon = ' scope_public.png '
scopeDescription = ' Public '
placeholderSubject = ' Subject or Content Warning (optional)... '
placeholderMessage = ' Write something... '
2019-07-26 12:59:30 +00:00
extraFields = ' '
2019-07-27 20:30:58 +00:00
endpoint = ' newpost '
2019-07-26 12:26:41 +00:00
if path . endswith ( ' /newunlisted ' ) :
scopeIcon = ' scope_unlisted.png '
scopeDescription = ' Unlisted '
2019-07-27 20:30:58 +00:00
endpoint = ' newunlisted '
2019-07-26 12:26:41 +00:00
if path . endswith ( ' /newfollowers ' ) :
scopeIcon = ' scope_followers.png '
scopeDescription = ' Followers Only '
2019-07-27 20:30:58 +00:00
endpoint = ' newfollowers '
2019-07-26 12:26:41 +00:00
if path . endswith ( ' /newdm ' ) :
scopeIcon = ' scope_dm.png '
scopeDescription = ' Direct Message '
2019-07-27 20:30:58 +00:00
endpoint = ' newdm '
2019-07-26 12:26:41 +00:00
if path . endswith ( ' /newshare ' ) :
scopeIcon = ' scope_share.png '
scopeDescription = ' Shared Item '
placeholderSubject = ' Name of the shared item... '
placeholderMessage = ' Description of the item being shared... '
2019-07-27 20:30:58 +00:00
endpoint = ' newshare '
2019-07-26 12:59:30 +00:00
extraFields = \
2019-07-26 14:19:37 +00:00
' <div class= " container " > ' \
2019-07-28 11:35:57 +00:00
' <input type= " text " class= " itemType " placeholder= " Type of shared item. eg. hat " name= " itemType " > ' \
' <input type= " text " class= " category " placeholder= " Category of shared item. eg. clothing " name= " category " > ' \
' <label class= " labels " >Duration of listing in days:</label> <input type= " number " name= " duration " min= " 1 " max= " 365 " step= " 1 " value= " 14 " > ' \
2019-07-26 14:19:37 +00:00
' </div> ' \
2019-07-26 12:59:30 +00:00
' <input type= " text " placeholder= " City or location of the shared item " name= " location " > '
2019-07-25 21:39:09 +00:00
newPostForm = htmlHeader ( newPostCSS )
2019-07-31 15:12:30 +00:00
# only show the share option if this is not a reply
shareOptionOnDropdown = ' '
if not replyStr :
shareOptionOnDropdown = ' <a href= " ' + pathBase + ' /newshare " ><img src= " /icons/scope_share.png " /><b>Share</b><br>Describe a shared item</a> '
2019-07-25 21:39:09 +00:00
newPostForm + = \
2019-07-27 20:30:58 +00:00
' <form enctype= " multipart/form-data " method= " POST " action= " ' + path + ' ? ' + endpoint + ' " > ' \
2019-07-25 21:39:09 +00:00
' <div class= " vertical-center " > ' \
' <label for= " nickname " ><b> ' + newPostText + ' </b></label> ' \
2019-07-26 10:30:13 +00:00
' <div class= " container " > ' \
2019-07-26 14:19:37 +00:00
' <div class= " dropdown " > ' \
' <img src= " /icons/ ' + scopeIcon + ' " /><b class= " scope-desc " > ' + scopeDescription + ' </b> ' \
' <div class= " dropdown-content " > ' \
' <a href= " ' + pathBase + ' /newpost " ><img src= " /icons/scope_public.png " /><b>Public</b><br>Visible to anyone</a> ' \
' <a href= " ' + pathBase + ' /newunlisted " ><img src= " /icons/scope_unlisted.png " /><b>Unlisted</b><br>Not on public timeline</a> ' \
' <a href= " ' + pathBase + ' /newfollowers " ><img src= " /icons/scope_followers.png " /><b>Followers Only</b><br>Only to followers</a> ' \
2019-07-31 15:12:30 +00:00
' <a href= " ' + pathBase + ' /newdm " ><img src= " /icons/scope_dm.png " /><b>Direct Message</b><br>Only to mentioned people</a> ' + \
shareOptionOnDropdown + \
2019-07-26 14:19:37 +00:00
' </div> ' \
' </div> ' \
2019-07-28 15:16:14 +00:00
' <input type= " submit " name= " submitPost " value= " Submit " > ' \
' <a href= " ' + pathBase + ' /outbox " ><button class= " cancelbtn " >Cancel</button></a> ' \
2019-07-31 13:51:10 +00:00
' </div> ' + \
replyStr + \
2019-07-28 12:04:32 +00:00
' <input type= " text " placeholder= " ' + placeholderSubject + ' " name= " subject " > ' \
' ' \
' <textarea id= " message " name= " message " placeholder= " ' + placeholderMessage + ' " style= " height:200px " ></textarea> ' \
' ' + extraFields + \
' <div class= " container " > ' \
' <input type= " text " placeholder= " Image description " name= " imageDescription " > ' \
' <input type= " file " id= " attachpic " name= " attachpic " ' \
' accept= " .png, .jpg, .jpeg, .gif " > ' \
2019-07-26 10:30:13 +00:00
' </div> ' \
2019-07-25 21:39:09 +00:00
' </div> ' \
' </form> '
newPostForm + = htmlFooter ( )
return newPostForm
2019-07-21 18:18:58 +00:00
def htmlHeader ( css = None , lang = ' en ' ) - > str :
if not css :
htmlStr = \
' <!DOCTYPE html> \n ' \
' <html lang= " ' + lang + ' " > \n ' \
' <meta charset= " utf-8 " > \n ' \
' <style> \n ' \
2019-07-24 11:03:56 +00:00
' @import url( " epicyon-profile.css " ); \n ' + \
2019-07-21 18:18:58 +00:00
' </style> \n ' \
' <body> \n '
else :
htmlStr = \
' <!DOCTYPE html> \n ' \
' <html lang= " ' + lang + ' " > \n ' \
' <meta charset= " utf-8 " > \n ' \
' <style> \n ' + css + ' </style> \n ' \
' <body> \n '
2019-07-20 21:13:36 +00:00
return htmlStr
def htmlFooter ( ) - > str :
htmlStr = \
' </body> \n ' \
' </html> \n '
return htmlStr
2019-07-22 14:09:21 +00:00
def htmlProfilePosts ( baseDir : str , httpPrefix : str , \
authorized : bool , ocapAlways : bool , \
nickname : str , domain : str , port : int , \
session , wfRequest : { } , personCache : { } ) - > str :
2019-07-22 09:38:02 +00:00
""" Shows posts on the profile screen
"""
profileStr = ' '
2019-07-22 14:09:21 +00:00
outboxFeed = \
personBoxJson ( baseDir , domain , \
port , ' /users/ ' + nickname + ' /outbox?page=1 ' , \
httpPrefix , \
4 , ' outbox ' , \
authorized , \
ocapAlways )
2019-07-31 12:44:08 +00:00
profileStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-07-22 09:38:02 +00:00
for item in outboxFeed [ ' orderedItems ' ] :
2019-07-31 10:09:02 +00:00
if item [ ' type ' ] == ' Create ' or item [ ' type ' ] == ' Announce ' :
2019-07-22 14:09:21 +00:00
profileStr + = \
2019-07-29 19:46:30 +00:00
individualPostAsHtml ( baseDir , session , wfRequest , personCache , \
2019-07-30 22:34:04 +00:00
nickname , domain , port , item , None , True , False )
2019-07-22 09:38:02 +00:00
return profileStr
2019-07-22 14:09:21 +00:00
def htmlProfileFollowing ( baseDir : str , httpPrefix : str , \
authorized : bool , ocapAlways : bool , \
nickname : str , domain : str , port : int , \
session , wfRequest : { } , personCache : { } , \
followingJson : { } ) - > str :
2019-07-22 11:44:31 +00:00
""" Shows following on the profile screen
"""
profileStr = ' '
for item in followingJson [ ' orderedItems ' ] :
2019-07-22 14:09:21 +00:00
profileStr + = individualFollowAsHtml ( session , wfRequest , personCache , domain , item )
2019-07-22 11:44:31 +00:00
return profileStr
2019-07-22 17:21:45 +00:00
def htmlProfileRoles ( nickname : str , domain : str , rolesJson : { } ) - > str :
""" Shows roles on the profile screen
"""
profileStr = ' '
for project , rolesList in rolesJson . items ( ) :
profileStr + = ' <div class= " roles " ><h2> ' + project + ' </h2><div class= " roles-inner " > '
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
2019-07-22 20:01:46 +00:00
def htmlProfileSkills ( nickname : str , domain : str , skillsJson : { } ) - > str :
""" Shows skills on the profile screen
"""
profileStr = ' '
for skill , level in skillsJson . items ( ) :
profileStr + = ' <div> ' + skill + ' <br><div id= " myProgress " ><div id= " myBar " style= " width: ' + str ( level ) + ' % " ></div></div></div><br> '
if len ( profileStr ) == 0 :
profileStr + = ' <p>@ ' + nickname + ' @ ' + domain + ' has no skills assigned</p> '
else :
profileStr = ' <center><div class= " skill-title " > ' + profileStr + ' </div></center> '
return profileStr
2019-07-23 12:33:09 +00:00
def htmlProfileShares ( nickname : str , domain : str , sharesJson : { } ) - > str :
""" Shows shares on the profile screen
"""
profileStr = ' '
for item in sharesJson [ ' orderedItems ' ] :
2019-07-24 09:02:38 +00:00
profileStr + = ' <div class= " container " > '
2019-07-24 09:53:07 +00:00
profileStr + = ' <p class= " share-title " > ' + item [ ' displayName ' ] + ' </p> '
2019-07-24 10:02:23 +00:00
profileStr + = ' <a href= " ' + item [ ' imageUrl ' ] + ' " > '
2019-07-24 09:53:07 +00:00
profileStr + = ' <img src= " ' + item [ ' imageUrl ' ] + ' " alt= " Item image " ></a> '
2019-07-24 09:02:38 +00:00
profileStr + = ' <p> ' + item [ ' summary ' ] + ' </p> '
2019-07-24 09:53:07 +00:00
profileStr + = ' <p><b>Type:</b> ' + item [ ' itemType ' ] + ' '
profileStr + = ' <b>Category:</b> ' + item [ ' category ' ] + ' '
profileStr + = ' <b>Location:</b> ' + item [ ' location ' ] + ' </p> '
2019-07-24 09:02:38 +00:00
profileStr + = ' </div> '
2019-07-23 12:33:09 +00:00
if len ( profileStr ) == 0 :
profileStr + = ' <p>@ ' + nickname + ' @ ' + domain + ' is not sharing any items</p> '
else :
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-07-22 14:09:21 +00:00
def htmlProfile ( baseDir : str , httpPrefix : str , authorized : bool , \
ocapAlways : bool , profileJson : { } , selected : str , \
session , wfRequest : { } , personCache : { } , \
extraJson = None ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the profile page as html
"""
2019-07-21 18:18:58 +00:00
nickname = profileJson [ ' name ' ]
if not nickname :
return " "
preferredName = profileJson [ ' preferredUsername ' ]
domain , port = getDomainFromActor ( profileJson [ ' id ' ] )
if not domain :
return " "
domainFull = domain
if port :
domainFull = domain + ' : ' + str ( port )
2019-07-31 12:44:08 +00:00
profileDescription = profileJson [ ' summary ' ]
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 = ' '
actor = profileJson [ ' id ' ]
2019-07-29 18:48:23 +00:00
2019-07-28 15:52:59 +00:00
if not authorized :
loginButton = ' <br><a href= " /login " ><button class= " loginButton " >Login</button></a> '
2019-07-29 18:48:23 +00:00
else :
2019-08-02 10:18:21 +00:00
editProfileStr = ' <a href= " ' + actor + ' /editprofile " ><button class= " button " ><span>Profile </span></button></a> '
2019-07-31 09:05:37 +00:00
linkToTimelineStart = ' <a href= " /users/ ' + nickname + ' /inbox " title= " Switch to timeline view " alt= " 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?
followRequestsFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /followrequests.txt '
if os . path . isfile ( followRequestsFilename ) :
with open ( followRequestsFilename , ' r ' ) as f :
for line in f :
if len ( line ) > 0 :
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 :
followerActor = httpPrefix + ' :// ' + followerHandle . split ( ' @ ' ) [ 1 ] + ' /users/ ' + followerHandle . split ( ' @ ' ) [ 0 ]
basePath = httpPrefix + ' :// ' + domainFull + ' /users/ ' + nickname
followApprovalsSection + = ' <div class= " container " > '
followApprovalsSection + = ' <a href= " ' + followerActor + ' " > '
followApprovalsSection + = ' <span class= " followRequestHandle " > ' + followerHandle + ' </span></a> '
followApprovalsSection + = ' <a href= " ' + basePath + ' /followapprove= ' + followerHandle + ' " > '
followApprovalsSection + = ' <button class= " followApprove " >Approve</button></a> '
followApprovalsSection + = ' <a href= " ' + basePath + ' /followdeny= ' + followerHandle + ' " > '
followApprovalsSection + = ' <button class= " followDeny " >Deny</button></a> '
followApprovalsSection + = ' </div> '
2019-07-21 18:18:58 +00:00
profileStr = \
2019-07-30 12:03:40 +00:00
linkToTimelineStart + \
2019-07-21 18:18:58 +00:00
' <div class= " hero-image " > ' \
2019-08-02 09:52:12 +00:00
' <div class= " hero-text " > ' + \
2019-07-21 20:36:58 +00:00
' <img src= " ' + profileJson [ ' icon ' ] [ ' url ' ] + ' " alt= " ' + nickname + ' @ ' + domainFull + ' " > ' \
2019-07-21 18:18:58 +00:00
' <h1> ' + preferredName + ' </h1> ' \
' <p><b>@ ' + nickname + ' @ ' + domainFull + ' </b></p> ' \
2019-07-28 15:52:59 +00:00
' <p> ' + profileDescription + ' </p> ' + \
loginButton + \
2019-07-21 18:18:58 +00:00
' </div> ' \
2019-07-30 12:03:40 +00:00
' </div> ' + \
linkToTimelineEnd + \
2019-07-21 19:37:48 +00:00
' <div class= " container " > \n ' \
' <center> ' \
2019-07-22 10:01:10 +00:00
' <a href= " ' + actor + ' " ><button class= " ' + postsButton + ' " ><span>Posts </span></button></a> ' \
' <a href= " ' + actor + ' /following " ><button class= " ' + followingButton + ' " ><span>Following </span></button></a> ' \
2019-08-02 10:37:03 +00:00
' <a href= " ' + actor + ' /followers " ><button class= " ' + followersButton + ' " ><span>Followers </span></button></a> ' \
2019-07-22 10:01:10 +00:00
' <a href= " ' + actor + ' /roles " ><button class= " ' + rolesButton + ' " ><span>Roles </span></button></a> ' \
' <a href= " ' + actor + ' /skills " ><button class= " ' + skillsButton + ' " ><span>Skills </span></button></a> ' \
2019-08-02 09:52:12 +00:00
' <a href= " ' + actor + ' /shares " ><button class= " ' + sharesButton + ' " ><span>Shares </span></button></a> ' + \
editProfileStr + \
2019-07-21 19:37:48 +00:00
' </center> ' \
2019-07-21 18:18:58 +00:00
' </div> '
2019-07-29 18:48:23 +00:00
profileStr + = followApprovalsSection
2019-07-22 17:42:39 +00:00
with open ( baseDir + ' /epicyon-profile.css ' , ' r ' ) as cssFile :
profileStyle = cssFile . read ( ) . replace ( ' image.png ' , actor + ' /image.png ' )
2019-07-21 22:38:44 +00:00
2019-07-22 17:42:39 +00:00
if selected == ' posts ' :
profileStr + = \
htmlProfilePosts ( baseDir , httpPrefix , authorized , \
ocapAlways , nickname , domain , port , \
session , wfRequest , personCache )
if selected == ' following ' or selected == ' followers ' :
profileStr + = \
htmlProfileFollowing ( baseDir , httpPrefix , \
authorized , ocapAlways , nickname , \
domain , port , session , \
wfRequest , personCache , extraJson )
if selected == ' roles ' :
profileStr + = \
htmlProfileRoles ( nickname , domainFull , extraJson )
2019-07-22 20:01:46 +00:00
if selected == ' skills ' :
profileStr + = \
htmlProfileSkills ( nickname , domainFull , extraJson )
2019-07-23 12:33:09 +00:00
if selected == ' shares ' :
profileStr + = \
htmlProfileShares ( nickname , domainFull , extraJson )
2019-07-22 17:42:39 +00:00
profileStr = htmlHeader ( profileStyle ) + profileStr + htmlFooter ( )
2019-07-21 18:18:58 +00:00
return profileStr
2019-07-20 21:13:36 +00:00
2019-07-22 14:09:21 +00:00
def individualFollowAsHtml ( session , wfRequest : { } , \
personCache : { } , domain : str , \
followUrl : str ) - > str :
2019-07-22 11:44:31 +00:00
nickname = getNicknameFromActor ( followUrl )
domain , port = getDomainFromActor ( followUrl )
titleStr = ' @ ' + nickname + ' @ ' + domain
2019-07-22 14:09:21 +00:00
avatarUrl = followUrl + ' /avatar.png '
if domain not in followUrl :
2019-07-22 14:21:49 +00:00
inboxUrl , pubKeyId , pubKey , fromPersonId , sharedInbox , capabilityAcquisition , avatarUrl2 , preferredName = \
2019-07-22 14:09:21 +00:00
getPersonBox ( session , wfRequest , personCache , ' outbox ' )
if avatarUrl2 :
avatarUrl = avatarUrl2
2019-07-22 14:21:49 +00:00
if preferredName :
titleStr = preferredName + ' ' + titleStr
2019-07-22 11:44:31 +00:00
return \
' <div class= " container " > \n ' \
' <a href= " ' + followUrl + ' " > ' \
2019-07-22 14:09:21 +00:00
' <img src= " ' + avatarUrl + ' " alt= " Avatar " > \n ' + \
2019-07-22 11:44:31 +00:00
' <p> ' + titleStr + ' </p></a> ' + \
' </div> \n '
2019-07-31 12:44:08 +00:00
def contentWarningScript ( ) - > str :
""" Returns a script used for content warnings
"""
script = \
' function showContentWarning(postID) { ' \
' var x = document.getElementById(postID); ' \
' if (x.style.display === " none " ) { ' \
' x.style.display = " block " ; ' \
' } else { ' \
' x.style.display = " none " ; ' \
' } ' \
' } '
return script
2019-07-29 19:46:30 +00:00
def individualPostAsHtml ( baseDir : str , \
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-07-30 22:34:04 +00:00
avatarUrl : str , showAvatarDropdown : bool ,
2019-07-30 12:47:42 +00:00
showIcons = False ) - > str :
2019-07-31 10:09:02 +00:00
""" Shows a single post as html
"""
titleStr = ' '
if postJsonObject [ ' type ' ] == ' Announce ' :
if postJsonObject . get ( ' object ' ) :
if isinstance ( postJsonObject [ ' object ' ] , str ) :
# get the announced post
asHeader = { ' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' }
announcedJson = getJson ( session , postJsonObject [ ' object ' ] , asHeader , None )
if announcedJson :
2019-07-31 18:33:57 +00:00
if not announcedJson . get ( ' type ' ) :
2019-07-31 10:09:02 +00:00
return ' '
2019-07-31 18:33:57 +00:00
if announcedJson [ ' type ' ] != ' Create ' :
2019-07-31 10:09:02 +00:00
return ' '
actorNickname = getNicknameFromActor ( postJsonObject [ ' actor ' ] )
actorDomain , actorPort = getDomainFromActor ( postJsonObject [ ' actor ' ] )
2019-07-31 10:10:31 +00:00
titleStr + = ' @ ' + actorNickname + ' @ ' + actorDomain + ' announced:<br> '
2019-07-31 10:09:02 +00:00
postJsonObject = announcedJson
else :
return ' '
else :
return ' '
if not isinstance ( postJsonObject [ ' object ' ] , dict ) :
return ' '
2019-07-21 11:20:49 +00:00
avatarPosition = ' '
containerClass = ' container '
2019-07-30 12:47:42 +00:00
containerClassIcons = ' containericons '
2019-07-21 11:20:49 +00:00
timeClass = ' time-right '
2019-07-28 19:54:05 +00:00
actorNickname = getNicknameFromActor ( postJsonObject [ ' actor ' ] )
actorDomain , actorPort = getDomainFromActor ( postJsonObject [ ' actor ' ] )
2019-07-31 10:09:02 +00:00
titleStr + = ' @ ' + actorNickname + ' @ ' + actorDomain
2019-07-21 11:20:49 +00:00
if postJsonObject [ ' object ' ] [ ' inReplyTo ' ] :
2019-07-30 12:47:42 +00:00
containerClassIcons = ' containericons darker '
2019-07-21 11:20:49 +00:00
containerClass = ' container darker '
avatarPosition = ' class= " right " '
timeClass = ' time-left '
2019-07-21 13:03:57 +00:00
if ' /statuses/ ' in postJsonObject [ ' object ' ] [ ' inReplyTo ' ] :
replyNickname = getNicknameFromActor ( postJsonObject [ ' object ' ] [ ' inReplyTo ' ] )
replyDomain , replyPort = getDomainFromActor ( postJsonObject [ ' object ' ] [ ' inReplyTo ' ] )
2019-07-21 13:05:07 +00:00
if replyNickname and replyDomain :
titleStr + = ' <i>replying to</i> <a href= " ' + postJsonObject [ ' object ' ] [ ' inReplyTo ' ] + ' " >@ ' + replyNickname + ' @ ' + replyDomain + ' </a> '
2019-07-21 13:03:57 +00:00
else :
titleStr + = ' <i>replying to</i> ' + postJsonObject [ ' object ' ] [ ' inReplyTo ' ]
2019-07-21 11:20:49 +00:00
attachmentStr = ' '
if postJsonObject [ ' object ' ] [ ' attachment ' ] :
if isinstance ( postJsonObject [ ' object ' ] [ ' attachment ' ] , list ) :
attachmentCtr = 0
for attach in postJsonObject [ ' object ' ] [ ' attachment ' ] :
if attach . get ( ' mediaType ' ) and attach . get ( ' url ' ) :
mediaType = attach [ ' mediaType ' ]
imageDescription = ' '
if attach . get ( ' name ' ) :
imageDescription = attach [ ' name ' ]
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 \
attach [ ' url ' ] . endswith ( ' .gif ' ) :
if attachmentCtr > 0 :
attachmentStr + = ' <br> '
attachmentStr + = \
2019-07-21 12:24:38 +00:00
' <a href= " ' + attach [ ' url ' ] + ' " > ' \
' <img src= " ' + attach [ ' url ' ] + ' " alt= " ' + imageDescription + ' " title= " ' + imageDescription + ' " class= " attachment " ></a> \n '
2019-07-21 11:20:49 +00:00
attachmentCtr + = 1
2019-07-22 14:09:21 +00:00
2019-07-30 22:34:04 +00:00
if not avatarUrl :
avatarUrl = postJsonObject [ ' actor ' ] + ' /avatar.png '
2019-07-28 19:54:05 +00:00
fullDomain = domain
2019-07-31 19:37:29 +00:00
if port :
if port != 80 and port != 443 :
fullDomain = domain + ' : ' + str ( port )
2019-07-28 20:14:45 +00:00
2019-07-28 19:54:05 +00:00
if fullDomain not in postJsonObject [ ' actor ' ] :
2019-07-22 14:21:49 +00:00
inboxUrl , pubKeyId , pubKey , fromPersonId , sharedInbox , capabilityAcquisition , avatarUrl2 , preferredName = \
2019-07-22 14:09:21 +00:00
getPersonBox ( session , wfRequest , personCache , ' outbox ' )
if avatarUrl2 :
avatarUrl = avatarUrl2
2019-07-22 14:21:49 +00:00
if preferredName :
titleStr = preferredName + ' ' + titleStr
2019-07-28 20:14:45 +00:00
avatarDropdown = \
' <a href= " ' + postJsonObject [ ' actor ' ] + ' " > ' \
2019-07-29 19:46:30 +00:00
' <img src= " ' + avatarUrl + ' " title= " Show profile " alt= " Avatar " ' + avatarPosition + ' /></a> '
2019-07-30 22:36:26 +00:00
if showAvatarDropdown and fullDomain + ' /users/ ' + nickname not in postJsonObject [ ' actor ' ] :
2019-07-29 19:46:30 +00:00
# if not following then show "Follow" in the dropdown
followUnfollowStr = ' <a href= " /users/ ' + nickname + ' ?follow= ' + postJsonObject [ ' actor ' ] + ' ; ' + avatarUrl + ' " >Follow</a> '
# if following then show "Unfollow" in the dropdown
if isFollowingActor ( baseDir , nickname , domain , postJsonObject [ ' actor ' ] ) :
followUnfollowStr = ' <a href= " /users/ ' + nickname + ' ?unfollow= ' + postJsonObject [ ' actor ' ] + ' ; ' + avatarUrl + ' " >Unfollow</a> '
2019-07-29 16:13:48 +00:00
avatarDropdown = \
2019-07-28 20:14:45 +00:00
' <div class= " dropdown-timeline " > ' \
2019-07-30 22:34:04 +00:00
' <img src= " ' + avatarUrl + ' " ' + avatarPosition + ' /> ' \
2019-07-28 20:14:45 +00:00
' <div class= " dropdown-timeline-content " > ' \
' <a href= " ' + postJsonObject [ ' actor ' ] + ' " >Visit</a> ' + \
2019-07-29 19:46:30 +00:00
followUnfollowStr + \
2019-07-28 20:20:58 +00:00
' <a href= " /users/ ' + nickname + ' ?block= ' + postJsonObject [ ' actor ' ] + ' ; ' + avatarUrl + ' " >Block</a> ' \
' <a href= " /users/ ' + nickname + ' ?report= ' + postJsonObject [ ' actor ' ] + ' ; ' + avatarUrl + ' " >Report</a> ' \
2019-07-28 20:14:45 +00:00
' </div> ' \
' </div> '
2019-07-31 13:11:09 +00:00
publishedStr = postJsonObject [ ' object ' ] [ ' published ' ]
datetimeObject = datetime . strptime ( publishedStr , " % Y- % m- %d T % H: % M: % SZ " )
publishedStr = datetimeObject . strftime ( " %a % b %d , % H: % M " )
footerStr = ' <span class= " ' + timeClass + ' " > ' + publishedStr + ' </span> \n '
2019-07-31 19:37:29 +00:00
# don't allow announce/repeat of your own posts
2019-08-01 12:18:22 +00:00
announceIcon = ' repeat_inactive.png '
announceLink = ' repeat '
announceTitle = ' Repeat this post '
if announcedByPerson ( postJsonObject , nickname , fullDomain ) :
announceIcon = ' repeat.png '
announceLink = ' unrepeat '
announceTitle = ' Undo the repeat this post '
announceStr = \
' <a href= " /users/ ' + nickname + ' ? ' + announceLink + ' = ' + postJsonObject [ ' object ' ] [ ' id ' ] + ' " title= " ' + announceTitle + ' " > ' \
' <img src= " /icons/ ' + announceIcon + ' " /></a> '
likeIcon = ' like_inactive.png '
likeLink = ' like '
likeTitle = ' Like this post '
if likedByPerson ( postJsonObject , nickname , fullDomain ) :
likeIcon = ' like.png '
likeLink = ' unlike '
likeTitle = ' Undo the like of this post '
likeStr = \
' <a href= " /users/ ' + nickname + ' ? ' + likeLink + ' = ' + postJsonObject [ ' object ' ] [ ' id ' ] + ' " title= " ' + likeTitle + ' " > ' \
' <img src= " /icons/ ' + likeIcon + ' " /></a> '
2019-07-31 19:37:29 +00:00
2019-07-30 12:47:42 +00:00
if showIcons :
footerStr = ' <div class= " ' + containerClassIcons + ' " > '
2019-07-31 13:51:10 +00:00
footerStr + = ' <a href= " /users/ ' + nickname + ' ?replyto= ' + postJsonObject [ ' object ' ] [ ' id ' ] + ' " title= " Reply to this post " > '
2019-07-30 22:34:04 +00:00
footerStr + = ' <img src= " /icons/reply.png " /></a> '
2019-07-31 19:37:29 +00:00
footerStr + = announceStr + likeStr
2019-07-31 13:11:09 +00:00
footerStr + = ' <span class= " ' + timeClass + ' " > ' + publishedStr + ' </span> '
2019-07-30 12:47:42 +00:00
footerStr + = ' </div> '
2019-07-31 12:44:08 +00:00
if not postJsonObject [ ' object ' ] [ ' sensitive ' ] :
contentStr = postJsonObject [ ' object ' ] [ ' content ' ] + attachmentStr
else :
postID = ' post ' + str ( createPassword ( 8 ) )
contentStr = ' '
if postJsonObject [ ' object ' ] . get ( ' summary ' ) :
contentStr + = ' <b> ' + postJsonObject [ ' object ' ] [ ' summary ' ] + ' </b> '
else :
contentStr + = ' <b>Sensitive</b> '
contentStr + = ' <button class= " cwButton " onclick= " showContentWarning( ' + " ' " + postID + " ' " + ' ) " >SHOW MORE</button> '
contentStr + = ' <div class= " cwText " id= " ' + postID + ' " > '
contentStr + = postJsonObject [ ' object ' ] [ ' content ' ] + attachmentStr
contentStr + = ' </div> '
2019-07-21 09:09:28 +00:00
return \
2019-07-28 20:14:45 +00:00
' <div class= " ' + containerClass + ' " > \n ' + \
avatarDropdown + \
2019-07-21 13:03:57 +00:00
' <p class= " post-title " > ' + titleStr + ' </p> ' + \
2019-07-31 12:44:08 +00:00
contentStr + footerStr + \
2019-07-21 11:52:13 +00:00
' </div> \n '
2019-07-21 09:09:28 +00:00
2019-07-31 20:37:19 +00:00
def htmlTimeline ( pageNumber : int , itemsPerPage : int , session , baseDir : str , \
wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , timelineJson : { } , \
2019-07-28 15:56:11 +00:00
boxName : str ) - > str :
2019-07-21 09:09:28 +00:00
""" Show the timeline as html
"""
2019-07-24 12:02:28 +00:00
with open ( baseDir + ' /epicyon-profile.css ' , ' r ' ) as cssFile :
profileStyle = \
cssFile . read ( ) . replace ( ' banner.png ' , \
' /users/ ' + nickname + ' /banner.png ' )
2019-07-30 10:50:01 +00:00
inboxButton = ' button '
sentButton = ' button '
2019-07-24 12:02:28 +00:00
if boxName == ' inbox ' :
2019-07-30 10:50:01 +00:00
inboxButton = ' buttonselected '
2019-07-24 12:02:28 +00:00
elif boxName == ' outbox ' :
2019-07-30 10:50:01 +00:00
sentButton = ' buttonselected '
2019-07-24 12:02:28 +00:00
actor = ' /users/ ' + nickname
2019-07-29 20:56:07 +00:00
2019-07-30 12:47:42 +00:00
showIndividualPostIcons = True
if boxName == ' inbox ' :
showIndividualPostIcons = True
2019-07-29 20:56:07 +00:00
followApprovals = ' '
followRequestsFilename = baseDir + ' /accounts/ ' + nickname + ' @ ' + domain + ' /followrequests.txt '
if os . path . isfile ( followRequestsFilename ) :
with open ( followRequestsFilename , ' r ' ) as f :
for line in f :
if len ( line ) > 0 :
2019-07-30 10:21:02 +00:00
# show follow approvals icon
2019-07-30 10:50:01 +00:00
followApprovals = ' <a href= " ' + actor + ' /followers " ><img class= " right " alt= " Approve follow requests " title= " Approve follow requests " src= " /icons/person.png " /></a> '
2019-07-29 20:56:07 +00:00
break
2019-07-24 12:02:28 +00:00
tlStr = htmlHeader ( profileStyle )
tlStr + = \
2019-07-31 09:05:37 +00:00
' <a href= " /users/ ' + nickname + ' " title= " Switch to profile view " alt= " Switch to profile view " > ' \
2019-07-24 12:02:28 +00:00
' <div class= " timeline-banner " > ' \
2019-07-30 12:03:40 +00:00
' </div></a> ' \
2019-07-30 10:21:02 +00:00
' <div class= " container " > \n ' + \
2019-07-30 10:50:01 +00:00
' <a href= " ' + actor + ' /inbox " ><button class= " ' + inboxButton + ' " ><span>Inbox </span></button></a> ' \
' <a href= " ' + actor + ' /outbox " ><button class= " ' + sentButton + ' " ><span>Sent </span></button></a> ' \
' <a href= " ' + actor + ' /newpost " ><img src= " /icons/newpost.png " title= " Create a new post " alt= " Create a new post " class= " right " /></a> ' + \
2019-07-30 12:03:40 +00:00
' <a href= " ' + actor + ' /search " ><img src= " /icons/search.png " title= " Search and follow " alt= " Search and follow " class= " right " /></a> ' + \
2019-07-29 20:56:07 +00:00
followApprovals + \
2019-07-24 12:02:28 +00:00
' </div> '
2019-07-31 12:44:08 +00:00
tlStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-07-31 20:37:19 +00:00
if pageNumber > 1 :
tlStr + = ' <center><a href= " ' + actor + ' / ' + boxName + ' ?page= ' + str ( pageNumber - 1 ) + ' " ><img class= " pageicon " src= " /icons/pageup.png " title= " Page up " alt= " Page up " ></a></center> '
itemCtr = 0
2019-07-21 09:09:28 +00:00
for item in timelineJson [ ' orderedItems ' ] :
2019-07-31 10:09:02 +00:00
if item [ ' type ' ] == ' Create ' or item [ ' type ' ] == ' Announce ' :
2019-07-31 20:37:19 +00:00
itemCtr + = 1
2019-07-29 19:46:30 +00:00
tlStr + = individualPostAsHtml ( baseDir , session , wfRequest , personCache , \
2019-07-30 22:34:04 +00:00
nickname , domain , port , item , None , True , showIndividualPostIcons )
2019-07-31 20:37:19 +00:00
if itemCtr > = itemsPerPage :
tlStr + = ' <center><a href= " ' + actor + ' / ' + boxName + ' ?page= ' + str ( pageNumber + 1 ) + ' " ><img class= " pageicon " src= " /icons/pagedown.png " title= " Page down " alt= " Page down " ></a></center> '
2019-07-21 09:09:28 +00:00
tlStr + = htmlFooter ( )
return tlStr
2019-07-31 20:37:19 +00:00
def htmlInbox ( pageNumber : int , itemsPerPage : int , \
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , inboxJson : { } ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the inbox as html
"""
2019-07-31 20:37:19 +00:00
return htmlTimeline ( pageNumber , itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-07-28 19:54:05 +00:00
nickname , domain , port , inboxJson , ' inbox ' )
2019-07-20 21:13:36 +00:00
2019-07-31 20:37:19 +00:00
def htmlOutbox ( pageNumber : int , itemsPerPage : int , \
session , baseDir : str , wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , outboxJson : { } ) - > str :
2019-07-20 21:13:36 +00:00
""" Show the Outbox as html
"""
2019-07-31 20:37:19 +00:00
return htmlTimeline ( pageNumber , itemsPerPage , session , baseDir , wfRequest , personCache , \
2019-07-28 19:54:05 +00:00
nickname , domain , port , outboxJson , ' outbox ' )
2019-07-20 21:13:36 +00:00
2019-07-29 19:46:30 +00:00
def htmlIndividualPost ( baseDir : str , session , wfRequest : { } , personCache : { } , \
2019-07-28 19:54:05 +00:00
nickname : str , domain : str , port : int , postJsonObject : { } ) - > str :
2019-07-20 21:13:36 +00:00
""" Show an individual post as html
"""
2019-07-21 09:09:28 +00:00
return htmlHeader ( ) + \
2019-07-29 19:46:30 +00:00
individualPostAsHtml ( baseDir , session , wfRequest , personCache , \
2019-07-30 22:34:04 +00:00
nickname , domain , port , postJsonObject , None , True , False ) + \
2019-07-21 09:09:28 +00:00
htmlFooter ( )
2019-07-20 21:13:36 +00:00
def htmlPostReplies ( postJsonObject : { } ) - > str :
""" Show the replies to an individual post as html
"""
return htmlHeader ( ) + " <h1>Replies</h1> " + htmlFooter ( )
2019-07-29 09:49:46 +00:00
def htmlFollowConfirm ( baseDir : str , originPathStr : str , followActor : str , followProfileUrl : str ) - > str :
""" 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 ' ) :
copyfile ( baseDir + ' /img/follow-background.png ' , baseDir + ' /accounts/follow-background.png ' )
with open ( baseDir + ' /epicyon-follow.css ' , ' r ' ) as cssFile :
profileStyle = cssFile . read ( )
followStr = htmlHeader ( profileStyle )
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
followStr + = ' <a href= " ' + followActor + ' " > '
followStr + = ' <img src= " ' + followProfileUrl + ' " /></a> '
followStr + = ' <p class= " followText " >Follow ' + getNicknameFromActor ( followActor ) + ' @ ' + followDomain + ' ?</p> '
followStr + = \
2019-07-29 16:13:48 +00:00
' <form method= " POST " action= " ' + originPathStr + ' /followconfirm " > ' \
2019-07-29 09:49:46 +00:00
' <input type= " hidden " name= " actor " value= " ' + followActor + ' " > ' \
' <button type= " submit " class= " button " name= " submitYes " >Yes</button> ' \
' <a href= " ' + originPathStr + ' " ><button class= " button " >No</button></a> ' \
' </form> '
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
2019-07-29 20:36:26 +00:00
def htmlUnfollowConfirm ( baseDir : str , originPathStr : str , followActor : str , followProfileUrl : str ) - > str :
""" 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 ' ) :
copyfile ( baseDir + ' /img/follow-background.png ' , baseDir + ' /accounts/follow-background.png ' )
with open ( baseDir + ' /epicyon-follow.css ' , ' r ' ) as cssFile :
profileStyle = cssFile . read ( )
followStr = htmlHeader ( profileStyle )
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
followStr + = ' <a href= " ' + followActor + ' " > '
followStr + = ' <img src= " ' + followProfileUrl + ' " /></a> '
followStr + = ' <p class= " followText " >Stop following ' + getNicknameFromActor ( followActor ) + ' @ ' + followDomain + ' ?</p> '
followStr + = \
' <form method= " POST " action= " ' + originPathStr + ' /unfollowconfirm " > ' \
' <input type= " hidden " name= " actor " value= " ' + followActor + ' " > ' \
' <button type= " submit " class= " button " name= " submitYes " >Yes</button> ' \
' <a href= " ' + originPathStr + ' " ><button class= " button " >No</button></a> ' \
' </form> '
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
2019-07-30 22:34:04 +00:00
def htmlSearch ( baseDir : str , path : str ) - > str :
""" Search called from the timeline icon
"""
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 ' ) :
copyfile ( baseDir + ' /img/search-background.png ' , baseDir + ' /accounts/search-background.png ' )
with open ( baseDir + ' /epicyon-follow.css ' , ' r ' ) as cssFile :
profileStyle = cssFile . read ( )
followStr = htmlHeader ( profileStyle )
followStr + = ' <div class= " follow " > '
followStr + = ' <div class= " followAvatar " > '
followStr + = ' <center> '
followStr + = ' <p class= " followText " >Enter an address to search for</p> '
followStr + = \
' <form method= " POST " action= " ' + actor + ' /searchhandle " > ' \
' <input type= " hidden " name= " actor " value= " ' + actor + ' " > ' \
' <input type= " text " name= " searchtext " autofocus><br> ' \
' <button type= " submit " class= " button " name= " submitSearch " >Submit</button> ' \
' <a href= " ' + actor + ' " ><button class= " button " >Go Back</button></a> ' \
' </form> '
followStr + = ' </center> '
followStr + = ' </div> '
followStr + = ' </div> '
followStr + = htmlFooter ( )
return followStr
def htmlProfileAfterSearch ( baseDir : str , path : str , httpPrefix : str , \
nickname : str , domain : str , port : int , \
profileHandle : str , \
session , wfRequest : { } , personCache : { } ,
debug : bool ) - > str :
""" Show a profile page after a search for a fediverse address
"""
if ' /users/ ' in profileHandle :
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 :
searchDomainFull = searchDomain + ' : ' + str ( searchPort )
profileStr = ' '
with open ( baseDir + ' /epicyon-profile.css ' , ' r ' ) as cssFile :
wf = webfingerHandle ( session , searchNickname + ' @ ' + searchDomain , httpPrefix , wfRequest )
if not wf :
if debug :
print ( ' DEBUG: Unable to webfinger ' + searchNickname + ' @ ' + searchDomain )
return None
asHeader = { ' Accept ' : ' application/ld+json; profile= " https://www.w3.org/ns/activitystreams " ' }
personUrl = getUserUrl ( wf )
profileJson = getJson ( session , personUrl , asHeader , None )
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 ' ]
preferredName = searchNickname
if profileJson . get ( ' preferredUsername ' ) :
preferredName = profileJson [ ' preferredUsername ' ]
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-07-31 09:00:44 +00:00
# url to return to
backUrl = path
if not backUrl . endswith ( ' /inbox ' ) :
backUrl + = ' /inbox '
2019-07-30 22:34:04 +00:00
profileStr = \
' <div class= " hero-image " > ' \
' <div class= " hero-text " > ' \
' <img src= " ' + avatarUrl + ' " alt= " ' + searchNickname + ' @ ' + searchDomainFull + ' " > ' \
' <h1> ' + preferredName + ' </h1> ' \
' <p><b>@ ' + searchNickname + ' @ ' + searchDomainFull + ' </b></p> ' \
' <p> ' + profileDescription + ' </p> ' + \
' </div> ' \
' </div> ' + \
' <div class= " container " > \n ' \
2019-07-31 09:00:44 +00:00
' <form method= " POST " action= " ' + backUrl + ' /followconfirm " > ' \
' <center> ' \
' <input type= " hidden " name= " actor " value= " ' + personUrl + ' " > ' \
' <button type= " submit " class= " button " name= " submitYes " >Follow</button> ' \
' <a href= " ' + backUrl + ' " ><button class= " button " >Go Back</button></a> ' \
' </center> ' \
' </form> ' \
2019-07-30 22:34:04 +00:00
' </div> '
2019-07-31 12:44:08 +00:00
profileStr + = ' <script> ' + contentWarningScript ( ) + ' </script> '
2019-07-30 22:34:04 +00:00
result = [ ]
i = 0
for item in parseUserFeed ( session , outboxUrl , asHeader ) :
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 + = \
individualPostAsHtml ( baseDir , \
session , wfRequest , personCache , \
nickname , domain , port , \
item , avatarUrl , False , False )
i + = 1
if i > = 20 :
break
return htmlHeader ( profileStyle ) + profileStr + htmlFooter ( )