forked from indymedia/epicyon
Begin on a web interface
parent
0350b9e33b
commit
2b02a1a442
19
README.md
19
README.md
|
@ -14,29 +14,30 @@ This project is currently *pre alpha* and not recommended for any real world use
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
* A minimal ActivityPub server, comparable to an email MTA.
|
* A minimal ActivityPub server, comparable to an email MTA
|
||||||
* AGPLv3+
|
* 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)
|
* Implemented in a common language (Python 3)
|
||||||
* Keyword filtering.
|
* Keyword filtering.
|
||||||
* Being able to build crowdsouced organizations with roles and skills
|
* Being able to build crowdsouced organizations with roles and skills
|
||||||
* Sharings collection, similar to the gnusocial sharings plugin
|
* Sharings collection, similar to the gnusocial sharings plugin
|
||||||
* Quotas for received posts per day, per domain and per account
|
* Quotas for received posts per day, per domain and per account
|
||||||
* Hellthread detection and removal
|
* Hellthread detection and removal
|
||||||
* Support content warnings, reporting and blocking.
|
* Support content warnings, reporting and blocking
|
||||||
* http signatures and basic auth.
|
* http signatures and basic auth
|
||||||
* Compatible with http (onion addresses), https and dat.
|
* Compatible with http (onion addresses), https and dat
|
||||||
* Minimal dependencies.
|
* Minimal dependencies.
|
||||||
* Capabilities based security
|
* Capabilities based security
|
||||||
* Support image blurhashes
|
* 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
|
* Likes and repeats only visible to authorized viewers
|
||||||
* ReplyGuy mitigation - maxmimum replies per post or posts per day
|
* ReplyGuy mitigation - maxmimum replies per post or posts per day
|
||||||
* Ability to delete or hide specific conversation threads
|
* Ability to delete or hide specific conversation threads
|
||||||
* Commandline interface. If there's a GUI it should be a separate project.
|
* Commandline interface
|
||||||
* Designed for intermittent connectivity. Assume network disruptions.
|
* Simple web interface
|
||||||
|
* Designed for intermittent connectivity. Assume network disruptions
|
||||||
* Limited visibility of follows/followers
|
* Limited visibility of follows/followers
|
||||||
* Suitable for single board computers.
|
* Suitable for single board computers
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|
81
daemon.py
81
daemon.py
|
@ -44,6 +44,13 @@ from config import setConfigParam
|
||||||
from roles import outboxDelegate
|
from roles import outboxDelegate
|
||||||
from skills import outboxSkills
|
from skills import outboxSkills
|
||||||
from availability import outboxAvailability
|
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 os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -329,7 +336,7 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.lastGET=currTimeGET
|
self.server.lastGET=currTimeGET
|
||||||
self.server.GETbusy=True
|
self.server.GETbusy=True
|
||||||
|
|
||||||
#print('Content-type: '+self.headers['Accept'])
|
#print('Accept: '+self.headers['Accept'])
|
||||||
|
|
||||||
if not self._permittedDir(self.path):
|
if not self._permittedDir(self.path):
|
||||||
if self.server.debug:
|
if self.server.debug:
|
||||||
|
@ -416,8 +423,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if not self._isAuthorized():
|
if not self._isAuthorized():
|
||||||
if postJsonObject.get('likes'):
|
if postJsonObject.get('likes'):
|
||||||
postJsonObject['likes']={}
|
postJsonObject['likes']={}
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(postJsonObject).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -453,8 +464,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'last': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
|
'last': self.server.httpPrefix+'://'+domainFull+'/users/'+nickname+'/statuses/'+statusNumber+'/replies?page=true',
|
||||||
'totalItems': 0,
|
'totalItems': 0,
|
||||||
'type': 'OrderedCollection'}
|
'type': 'OrderedCollection'}
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(repliesJson).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -521,8 +536,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to']:
|
'https://www.w3.org/ns/activitystreams#Public' in postJsonObject['object']['to']:
|
||||||
repliesJson['orderedItems'].append(postJsonObject)
|
repliesJson['orderedItems'].append(postJsonObject)
|
||||||
# send the replies json
|
# send the replies json
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(repliesJson).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -550,8 +569,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
if not self._isAuthorized():
|
if not self._isAuthorized():
|
||||||
if postJsonObject.get('likes'):
|
if postJsonObject.get('likes'):
|
||||||
postJsonObject['likes']={}
|
postJsonObject['likes']={}
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(postJsonObject).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -570,8 +593,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
maxPostsInFeed, 'inbox', \
|
maxPostsInFeed, 'inbox', \
|
||||||
True,self.server.ocapAlways)
|
True,self.server.ocapAlways)
|
||||||
if inboxFeed:
|
if inboxFeed:
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(inboxFeed).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -593,8 +620,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self._isAuthorized(), \
|
self._isAuthorized(), \
|
||||||
self.server.ocapAlways)
|
self.server.ocapAlways)
|
||||||
if outboxFeed:
|
if outboxFeed:
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(outboxFeed).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
authorized=self._isAuthorized()
|
authorized=self._isAuthorized()
|
||||||
|
@ -603,8 +634,12 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.httpPrefix,
|
self.server.httpPrefix,
|
||||||
authorized,followsPerPage)
|
authorized,followsPerPage)
|
||||||
if following:
|
if following:
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(following).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
followers=getFollowingFeed(self.server.baseDir,self.server.domain, \
|
||||||
|
@ -612,16 +647,24 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.httpPrefix, \
|
self.server.httpPrefix, \
|
||||||
authorized,followsPerPage,'followers')
|
authorized,followsPerPage,'followers')
|
||||||
if followers:
|
if followers:
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(followers).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
# look up a person
|
# look up a person
|
||||||
getPerson = personLookup(self.server.domain,self.path, \
|
getPerson = personLookup(self.server.domain,self.path, \
|
||||||
self.server.baseDir)
|
self.server.baseDir)
|
||||||
if getPerson:
|
if getPerson:
|
||||||
self._set_headers('application/json')
|
if 'text/html' in self.headers['Accept']:
|
||||||
self.wfile.write(json.dumps(getPerson).encode('utf-8'))
|
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
|
self.server.GETbusy=False
|
||||||
return
|
return
|
||||||
# check that a json file was requested
|
# check that a json file was requested
|
||||||
|
|
|
@ -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= \
|
||||||
|
'<!DOCTYPE html>\n' \
|
||||||
|
'<html lang="'+lang+'">\n' \
|
||||||
|
' <meta charset="utf-8">\n' \
|
||||||
|
' <style>\n' \
|
||||||
|
' @import url("epicyon.css");\n' \
|
||||||
|
' </style>\n' \
|
||||||
|
' <body>\n'
|
||||||
|
return htmlStr
|
||||||
|
|
||||||
|
def htmlFooter() -> str:
|
||||||
|
htmlStr= \
|
||||||
|
' </body>\n' \
|
||||||
|
'</html>\n'
|
||||||
|
return htmlStr
|
||||||
|
|
||||||
|
def htmlProfile(profileJson: {}) -> str:
|
||||||
|
"""Show the profile page as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Profile page</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlFollowing(followingJson: {}) -> str:
|
||||||
|
"""Show the following collection as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Following collection</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlFollowers(followersJson: {}) -> str:
|
||||||
|
"""Show the followers collection as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Followers collection</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlInbox(inboxJson: {}) -> str:
|
||||||
|
"""Show the inbox as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Inbox</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlOutbox(outboxJson: {}) -> str:
|
||||||
|
"""Show the Outbox as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Outbox</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlIndividualPost(postJsonObject: {}) -> str:
|
||||||
|
"""Show an individual post as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Post</h1>"+htmlFooter()
|
||||||
|
|
||||||
|
def htmlPostReplies(postJsonObject: {}) -> str:
|
||||||
|
"""Show the replies to an individual post as html
|
||||||
|
"""
|
||||||
|
return htmlHeader()+"<h1>Replies</h1>"+htmlFooter()
|
Loading…
Reference in New Issue