Search for address

master
Bob Mottram 2019-07-30 23:34:04 +01:00
parent ce0bd875f5
commit bcd6278e82
4 changed files with 232 additions and 19 deletions

View File

@ -66,7 +66,9 @@ from webinterface import htmlLogin
from webinterface import htmlGetLoginCredentials
from webinterface import htmlNewPost
from webinterface import htmlFollowConfirm
from webinterface import htmlSearch
from webinterface import htmlUnfollowConfirm
from webinterface import htmlProfileAfterSearch
from shares import getSharesFeedForPerson
from shares import outboxShareUpload
from shares import outboxUndoShareUpload
@ -611,7 +613,16 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStr,cookie)
self.server.GETbusy=False
return
# search for a fediverse address from the web interface by selecting search icon
if '/users/' in self.path:
if self.path.endswith('/search'):
# show the search screen
self._set_headers('text/html',cookie)
self.wfile.write(htmlSearch(self.server.baseDir,self.path).encode())
self.server.GETbusy=False
return
# Unfollow a person from the web interface by selecting Unfollow on the dropdown
if '/users/' in self.path:
if '?unfollow=' in self.path:
@ -1459,6 +1470,42 @@ class PubServer(BaseHTTPRequestHandler):
self._redirect_headers(originPathStr,cookie)
self.server.POSTbusy=False
# decision to follow in the web interface is confirmed
if authorized and self.path.endswith('/searchhandle'):
actorStr=self.path.replace('/searchhandle','')
length = int(self.headers['Content-length'])
searchParams=self.rfile.read(length).decode('utf-8')
if 'searchtext=' in searchParams:
searchStr=searchParams.split('searchtext=')[1]
if '&' in searchStr:
searchStr=searchStr.split('&')[0]
searchStr=searchStr.replace('+',' ').replace('%40','@')
if '@' in searchStr:
print('Search: '+searchStr)
nickname=getNicknameFromActor(self.path)
if not self.server.session:
self.server.session= \
createSession(self.server.domain,self.server.port,self.server.useTor)
profileStr= \
htmlProfileAfterSearch(self.server.baseDir, \
self.path.replace('/searchhandle',''), \
self.server.httpPrefix, \
nickname, \
self.server.domain,self.server.port, \
searchStr, \
self.server.session, \
self.server.cachedWebfingers, \
self.server.personCache, \
self.server.debug)
if profileStr:
self._login_headers('text/html')
self.wfile.write(profileStr.encode('utf-8'))
self.server.POSTbusy=False
return
self._redirect_headers(actorStr,cookie)
self.server.POSTbusy=False
return
# decision to follow in the web interface is confirmed
if authorized and self.path.endswith('/followconfirm'):
originPathStr=self.path.split('/followconfirm')[0]

View File

@ -49,3 +49,12 @@ body, html {
background-color: #555;
color: white;
}
input[type=text] {
width: 70%;
clear: both;
font-size: 24px;
text-align: center;
color: var(--text-entry-foreground);
background-color: var(--text-entry-background);
}

View File

@ -209,6 +209,7 @@ body, html {
float: left;
max-width: 60px;
width: 10%;
padding: 0px 7px;
margin-right: 20px;
border-radius: 10%;
}
@ -216,22 +217,23 @@ body, html {
.containericons {
border: 0px solid #dedede;
background-color: #f1f1f1;
border-radius: 5px;
padding: 10px;
margin: 10px 0;
}
.darker {
border-color: #ccc;
background-color: #ddd;
border-radius: 0px;
padding: 0px;
margin: 0px 0;
}
.containericons img {
float: right;
max-width: 35px;
width: 5%;
margin-right: 50px;
border-radius: 10%;
margin-right: 10px;
padding-left: 0px 0px;
border-radius: 0%;
}
.darker {
border-color: #ccc;
background-color: #ddd;
}
.container img.attachment {

View File

@ -16,6 +16,10 @@ from utils import getNicknameFromActor
from utils import getDomainFromActor
from posts import getPersonBox
from follow import isFollowingActor
from webfinger import webfingerHandle
from posts import getUserUrl
from posts import parseUserFeed
from session import getJson
def htmlGetLoginCredentials(loginParams: str,lastLoginTime: int) -> (str,str):
"""Receives login credentials via HTTPServer POST
@ -198,7 +202,7 @@ def htmlProfilePosts(baseDir: str,httpPrefix: str, \
if item['type']=='Create':
profileStr+= \
individualPostAsHtml(baseDir,session,wfRequest,personCache, \
nickname,domain,port,item,False)
nickname,domain,port,item,None,True,False)
return profileStr
def htmlProfileFollowing(baseDir: str,httpPrefix: str, \
@ -413,6 +417,7 @@ def individualPostAsHtml(baseDir: str, \
session,wfRequest: {},personCache: {}, \
nickname: str,domain: str,port: int, \
postJsonObject: {}, \
avatarUrl: str, showAvatarDropdown: bool,
showIcons=False) -> str:
avatarPosition=''
containerClass='container'
@ -457,7 +462,8 @@ def individualPostAsHtml(baseDir: str, \
'<img src="'+attach['url']+'" alt="'+imageDescription+'" title="'+imageDescription+'" class="attachment"></a>\n'
attachmentCtr+=1
avatarUrl=postJsonObject['actor']+'/avatar.png'
if not avatarUrl:
avatarUrl=postJsonObject['actor']+'/avatar.png'
fullDomain=domain
if port!=80 and port!=443:
@ -484,7 +490,7 @@ def individualPostAsHtml(baseDir: str, \
avatarDropdown= \
' <div class="dropdown-timeline">' \
' <img src="'+avatarUrl+'" alt="Avatar"'+avatarPosition+'/>' \
' <img src="'+avatarUrl+'" '+avatarPosition+'/>' \
' <div class="dropdown-timeline-content">' \
' <a href="'+postJsonObject['actor']+'">Visit</a>'+ \
followUnfollowStr+ \
@ -496,9 +502,12 @@ def individualPostAsHtml(baseDir: str, \
footerStr='<span class="'+timeClass+'">'+postJsonObject['object']['published']+'</span>\n'
if showIcons:
footerStr='<div class="'+containerClassIcons+'">'
footerStr+='<img src="/icons/reply.png"/>'
footerStr+='<img src="/icons/repeat_inactive.png"/>'
footerStr+='<img src="/icons/like_inactive.png"/>'
footerStr+='<a href="/users/'+nickname+'?replyto='+postJsonObject['object']['id']+'">'
footerStr+='<img src="/icons/reply.png"/></a>'
footerStr+='<a href="/users/'+nickname+'?repeat='+postJsonObject['object']['id']+'">'
footerStr+='<img src="/icons/repeat_inactive.png"/></a>'
footerStr+='<a href="/users/'+nickname+'?like='+postJsonObject['object']['id']+'">'
footerStr+='<img src="/icons/like_inactive.png"/></a>'
footerStr+='<span class="'+timeClass+'">'+postJsonObject['object']['published']+'</span>'
footerStr+='</div>'
@ -557,7 +566,7 @@ def htmlTimeline(session,baseDir: str,wfRequest: {},personCache: {}, \
for item in timelineJson['orderedItems']:
if item['type']=='Create':
tlStr+=individualPostAsHtml(baseDir,session,wfRequest,personCache, \
nickname,domain,port,item,showIndividualPostIcons)
nickname,domain,port,item,None,True,showIndividualPostIcons)
tlStr+=htmlFooter()
return tlStr
@ -581,7 +590,7 @@ def htmlIndividualPost(baseDir: str,session,wfRequest: {},personCache: {}, \
"""
return htmlHeader()+ \
individualPostAsHtml(baseDir,session,wfRequest,personCache, \
nickname,domain,port,postJsonObject,False)+ \
nickname,domain,port,postJsonObject,None,True,False)+ \
htmlFooter()
def htmlPostReplies(postJsonObject: {}) -> str:
@ -648,3 +657,149 @@ def htmlUnfollowConfirm(baseDir: str,originPathStr: str,followActor: str,followP
followStr+='</div>'
followStr+=htmlFooter()
return followStr
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=''
if profileJson.get('publicKey'):
if profileJson['publicKey'].get('summary'):
profileDescription=profileJson['publicKey']['summary']
outboxUrl=None
if not profileJson.get('outbox'):
if debug:
pprint(profileJson)
print('DEBUG: No outbox found')
return None
outboxUrl=profileJson['outbox']
profileBackgroundImage=''
if profileJson.get('image'):
if profileJson['image'].get('url'):
profileBackgroundImage=profileJson['image']['url']
profileStyle = cssFile.read().replace('image.png',profileBackgroundImage)
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' \
' <center>' \
' <a href="'+path+'"><button class="button"><span>Follow </span></button></a>' \
' <a href="'+path+'"><button class="button"><span>Go Back </span></button></a>' \
' </center>' \
'</div>'
result = []
i = 0
for item in parseUserFeed(session,outboxUrl,asHeader):
if not item.get('type'):
continue
if item['type']!='Create':
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()