diff --git a/README.md b/README.md index 77158c39..8692b3f0 100644 --- a/README.md +++ b/README.md @@ -14,29 +14,30 @@ This project is currently *pre alpha* and not recommended for any real world use ## Goals - * A minimal ActivityPub server, comparable to an email MTA. + * A minimal ActivityPub server, comparable to an email MTA * AGPLv3+ - * Server-to-server and client-to-server protocols supported. + * Server-to-server and client-to-server protocols supported * Implemented in a common language (Python 3) * Keyword filtering. * Being able to build crowdsouced organizations with roles and skills * Sharings collection, similar to the gnusocial sharings plugin * Quotas for received posts per day, per domain and per account * Hellthread detection and removal - * Support content warnings, reporting and blocking. - * http signatures and basic auth. - * Compatible with http (onion addresses), https and dat. + * Support content warnings, reporting and blocking + * http signatures and basic auth + * Compatible with http (onion addresses), https and dat * Minimal dependencies. * Capabilities based security * Support image blurhashes - * Data minimization principle. Configurable post expiry time. + * Data minimization principle. Configurable post expiry time * Likes and repeats only visible to authorized viewers * ReplyGuy mitigation - maxmimum replies per post or posts per day * Ability to delete or hide specific conversation threads - * Commandline interface. If there's a GUI it should be a separate project. - * Designed for intermittent connectivity. Assume network disruptions. + * Commandline interface + * Simple web interface + * Designed for intermittent connectivity. Assume network disruptions * Limited visibility of follows/followers - * Suitable for single board computers. + * Suitable for single board computers ## Install diff --git a/daemon.py b/daemon.py index d5b9af84..8a0a28a7 100644 --- a/daemon.py +++ b/daemon.py @@ -44,6 +44,13 @@ from config import setConfigParam from roles import outboxDelegate from skills import outboxSkills from availability import outboxAvailability +from webinterface import htmlIndividualPost +from webinterface import htmlProfile +from webinterface import htmlFollowing +from webinterface import htmlFollowers +from webinterface import htmlInbox +from webinterface import htmlOutbox +from webinterface import htmlPostReplies import os import sys @@ -329,7 +336,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.lastGET=currTimeGET self.server.GETbusy=True - #print('Content-type: '+self.headers['Accept']) + #print('Accept: '+self.headers['Accept']) if not self._permittedDir(self.path): if self.server.debug: @@ -416,8 +423,12 @@ class PubServer(BaseHTTPRequestHandler): if not self._isAuthorized(): if postJsonObject.get('likes'): postJsonObject['likes']={} - self._set_headers('application/json') - self.wfile.write(json.dumps(postJsonObject).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlIndividualPost(postJsonObject).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(postJsonObject).encode('utf-8')) self.server.GETbusy=False return else: @@ -453,8 +464,12 @@ class PubServer(BaseHTTPRequestHandler): 'last': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true', 'totalItems': 0, 'type': 'OrderedCollection'} - self._set_headers('application/json') - self.wfile.write(json.dumps(repliesJson).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlPostReplies(repliesJson).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(repliesJson).encode('utf-8')) self.server.GETbusy=False return else: @@ -521,8 +536,12 @@ class PubServer(BaseHTTPRequestHandler): 'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to']: repliesJson['orderedItems'].append(postJsonObject) # send the replies json - self._set_headers('application/json') - self.wfile.write(json.dumps(repliesJson).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlPostReplies(repliesJson).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(repliesJson).encode('utf-8')) self.server.GETbusy=False return @@ -550,8 +569,12 @@ class PubServer(BaseHTTPRequestHandler): if not self._isAuthorized(): if postJsonObject.get('likes'): postJsonObject['likes']={} - self._set_headers('application/json') - self.wfile.write(json.dumps(postJsonObject).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlIndividualPost(postJsonObject).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(postJsonObject).encode('utf-8')) self.server.GETbusy=False return else: @@ -570,8 +593,12 @@ class PubServer(BaseHTTPRequestHandler): maxPostsInFeed, 'inbox', \ True,self.server.ocapAlways) if inboxFeed: - self._set_headers('application/json') - self.wfile.write(json.dumps(inboxFeed).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlInbox(inboxFeed).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(inboxFeed).encode('utf-8')) self.server.GETbusy=False return else: @@ -593,8 +620,12 @@ class PubServer(BaseHTTPRequestHandler): self._isAuthorized(), \ self.server.ocapAlways) if outboxFeed: - self._set_headers('application/json') - self.wfile.write(json.dumps(outboxFeed).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlOutbox(outboxFeed).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(outboxFeed).encode('utf-8')) self.server.GETbusy=False return authorized=self._isAuthorized() @@ -603,8 +634,12 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix, authorized,followsPerPage) if following: - self._set_headers('application/json') - self.wfile.write(json.dumps(following).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlFollowing(following).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(following).encode('utf-8')) self.server.GETbusy=False return followers=getFollowingFeed(self.server.baseDir,self.server.domain, \ @@ -612,16 +647,24 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix, \ authorized,followsPerPage,'followers') if followers: - self._set_headers('application/json') - self.wfile.write(json.dumps(followers).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlFollowers(followers).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(followers).encode('utf-8')) self.server.GETbusy=False return # look up a person getPerson = personLookup(self.server.domain,self.path, \ self.server.baseDir) if getPerson: - self._set_headers('application/json') - self.wfile.write(json.dumps(getPerson).encode('utf-8')) + if 'text/html' in self.headers['Accept']: + self._set_headers('text/html') + self.wfile.write(htmlProfile(getPerson).encode('utf-8')) + else: + self._set_headers('application/json') + self.wfile.write(json.dumps(getPerson).encode('utf-8')) self.server.GETbusy=False return # check that a json file was requested diff --git a/webinterface.py b/webinterface.py new file mode 100644 index 00000000..abb7eea0 --- /dev/null +++ b/webinterface.py @@ -0,0 +1,61 @@ +__filename__ = "webinterface.py" +__author__ = "Bob Mottram" +__license__ = "AGPL3+" +__version__ = "0.0.1" +__maintainer__ = "Bob Mottram" +__email__ = "bob@freedombone.net" +__status__ = "Production" + +import json + +def htmlHeader(lang='en') -> str: + htmlStr= \ + '\n' \ + '\n' \ + ' \n' \ + ' \n' \ + '
\n' + return htmlStr + +def htmlFooter() -> str: + htmlStr= \ + ' \n' \ + '\n' + return htmlStr + +def htmlProfile(profileJson: {}) -> str: + """Show the profile page as html + """ + return htmlHeader()+"