Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon

main
Bob Mottram 2021-02-03 10:06:55 +00:00
commit c3f6c42af4
50 changed files with 582 additions and 226 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
build/
dist/
*.egg-info/

View File

@ -14,6 +14,8 @@ from utils import getImageExtensions
from utils import loadJson from utils import loadJson
from utils import fileLastModified from utils import fileLastModified
from utils import getLinkPrefixes from utils import getLinkPrefixes
from utils import dangerousMarkup
from petnames import getPetName
def removeHtmlTag(htmlStr: str, tag: str) -> str: def removeHtmlTag(htmlStr: str, tag: str) -> str:
@ -153,38 +155,6 @@ def htmlReplaceQuoteMarks(content: str) -> str:
return newContent return newContent
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given content contains dangerous html markup
"""
if '<' not in content:
return False
if '>' not in content:
return False
contentSections = content.split('<')
invalidPartials = ()
if not allowLocalNetworkAccess:
invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
invalidStrings = ('script', 'canvas', 'style', 'abbr',
'frame', 'iframe', 'html', 'body',
'hr', 'allow-popups', 'allow-scripts')
for markup in contentSections:
if '>' not in markup:
continue
markup = markup.split('>')[0].strip()
for partialMatch in invalidPartials:
if partialMatch in markup:
return True
if ' ' not in markup:
for badStr in invalidStrings:
if badStr in markup:
return True
else:
for badStr in invalidStrings:
if badStr + ' ' in markup:
return True
return False
def dangerousCSS(filename: str, allowLocalNetworkAccess: bool) -> bool: def dangerousCSS(filename: str, allowLocalNetworkAccess: bool) -> bool:
"""Returns true is the css file contains code which """Returns true is the css file contains code which
can create security problems can create security problems
@ -489,7 +459,7 @@ def tagExists(tagType: str, tagName: str, tags: {}) -> bool:
return False return False
def _addMention(wordStr: str, httpPrefix: str, following: str, def _addMention(wordStr: str, httpPrefix: str, following: str, petnames: str,
replaceMentions: {}, recipients: [], tags: {}) -> bool: replaceMentions: {}, recipients: [], tags: {}) -> bool:
"""Detects mentions and adds them to the replacements dict and """Detects mentions and adds them to the replacements dict and
recipients list recipients list
@ -501,9 +471,12 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
# if no domain was specified. eg. @nick # if no domain was specified. eg. @nick
possibleNickname = possibleHandle possibleNickname = possibleHandle
for follow in following: for follow in following:
if follow.startswith(possibleNickname + '@'): if '@' not in follow:
replaceDomain = \ continue
follow.replace('\n', '').replace('\r', '').split('@')[1] followNick = follow.split('@')[0]
if possibleNickname == followNick:
followStr = follow.replace('\n', '').replace('\r', '')
replaceDomain = followStr.split('@')[1]
recipientActor = httpPrefix + "://" + \ recipientActor = httpPrefix + "://" + \
replaceDomain + "/users/" + possibleNickname replaceDomain + "/users/" + possibleNickname
if recipientActor not in recipients: if recipientActor not in recipients:
@ -519,6 +492,34 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
"\" class=\"u-url mention\">@<span>" + possibleNickname + \ "\" class=\"u-url mention\">@<span>" + possibleNickname + \
"</span></a></span>" "</span></a></span>"
return True return True
# try replacing petnames with mentions
followCtr = 0
for follow in following:
if '@' not in follow:
followCtr += 1
continue
pet = petnames[followCtr].replace('\n', '')
if pet:
if possibleNickname == pet:
followStr = follow.replace('\n', '').replace('\r', '')
replaceNickname = followStr.split('@')[0]
replaceDomain = followStr.split('@')[1]
recipientActor = httpPrefix + "://" + \
replaceDomain + "/users/" + replaceNickname
if recipientActor not in recipients:
recipients.append(recipientActor)
tags[wordStr] = {
'href': recipientActor,
'name': wordStr,
'type': 'Mention'
}
replaceMentions[wordStr] = \
"<span class=\"h-card\"><a href=\"" + httpPrefix + \
"://" + replaceDomain + "/@" + replaceNickname + \
"\" class=\"u-url mention\">@<span>" + \
replaceNickname + "</span></a></span>"
return True
followCtr += 1
return False return False
possibleNickname = None possibleNickname = None
possibleDomain = None possibleDomain = None
@ -752,10 +753,14 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
# read the following list so that we can detect just @nick # read the following list so that we can detect just @nick
# in addition to @nick@domain # in addition to @nick@domain
following = None following = None
petnames = None
if '@' in words: if '@' in words:
if os.path.isfile(followingFilename): if os.path.isfile(followingFilename):
with open(followingFilename, "r") as f: with open(followingFilename, "r") as f:
following = f.readlines() following = f.readlines()
for handle in following:
pet = getPetName(baseDir, nickname, domain, handle)
petnames.append(pet + '\n')
# extract mentions and tags from words # extract mentions and tags from words
longWordsList = [] longWordsList = []
@ -769,7 +774,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
longWordsList.append(wordStr) longWordsList.append(wordStr)
firstChar = wordStr[0] firstChar = wordStr[0]
if firstChar == '@': if firstChar == '@':
if _addMention(wordStr, httpPrefix, following, if _addMention(wordStr, httpPrefix, following, petnames,
replaceMentions, recipients, hashtags): replaceMentions, recipients, hashtags):
prevWordStr = '' prevWordStr = ''
continue continue

View File

@ -217,10 +217,10 @@ from utils import urlPermitted
from utils import loadJson from utils import loadJson
from utils import saveJson from utils import saveJson
from utils import isSuspended from utils import isSuspended
from utils import dangerousMarkup
from manualapprove import manualDenyFollowRequest from manualapprove import manualDenyFollowRequest
from manualapprove import manualApproveFollowRequest from manualapprove import manualApproveFollowRequest
from announce import createAnnounce from announce import createAnnounce
from content import dangerousMarkup
from content import replaceEmojiFromTags from content import replaceEmojiFromTags
from content import addHtmlTags from content import addHtmlTags
from content import extractMediaInFormPOST from content import extractMediaInFormPOST
@ -1136,7 +1136,7 @@ class PubServer(BaseHTTPRequestHandler):
""" """
if self.server.restartInboxQueueInProgress: if self.server.restartInboxQueueInProgress:
self._503() self._503()
print('Message arrrived but currently restarting inbox queue') print('Message arrived but currently restarting inbox queue')
self.server.POSTbusy = False self.server.POSTbusy = False
return 2 return 2
@ -2614,7 +2614,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.projectVersion, self.server.projectVersion,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
if hashtagStr: if hashtagStr:
msg = hashtagStr.encode('utf-8') msg = hashtagStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -2666,7 +2667,8 @@ class PubServer(BaseHTTPRequestHandler):
port, port,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
if historyStr: if historyStr:
msg = historyStr.encode('utf-8') msg = historyStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -2733,6 +2735,8 @@ class PubServer(BaseHTTPRequestHandler):
return return
else: else:
showPublishedDateOnly = self.server.showPublishedDateOnly showPublishedDateOnly = self.server.showPublishedDateOnly
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
profileStr = \ profileStr = \
htmlProfileAfterSearch(self.server.cssCache, htmlProfileAfterSearch(self.server.cssCache,
self.server.recentPostsCache, self.server.recentPostsCache,
@ -2753,7 +2757,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
self.server.defaultTimeline, self.server.defaultTimeline,
self.server.peertubeInstances) self.server.peertubeInstances,
allowLocalNetworkAccess)
if profileStr: if profileStr:
msg = profileStr.encode('utf-8') msg = profileStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -5674,7 +5679,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.projectVersion, self.server.projectVersion,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
if hashtagStr: if hashtagStr:
msg = hashtagStr.encode('utf-8') msg = hashtagStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -6636,7 +6642,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.personCache, callingDomain, self.server.personCache, callingDomain,
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
if deleteStr: if deleteStr:
deleteStrLen = len(deleteStr) deleteStrLen = len(deleteStr)
self._set_headers('text/html', deleteStrLen, self._set_headers('text/html', deleteStrLen,
@ -6840,7 +6847,8 @@ class PubServer(BaseHTTPRequestHandler):
projectVersion, projectVersion,
ytDomain, ytDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
peertubeInstances) peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -6926,7 +6934,8 @@ class PubServer(BaseHTTPRequestHandler):
projectVersion, projectVersion,
ytDomain, ytDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
peertubeInstances) peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7013,6 +7022,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
actorJson['roles'], actorJson['roles'],
None, None) None, None)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -7077,6 +7087,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.showPublishedDateOnly self.server.showPublishedDateOnly
iconsAsButtons = \ iconsAsButtons = \
self.server.iconsAsButtons self.server.iconsAsButtons
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
msg = \ msg = \
htmlProfile(self.server.rssIconAtTop, htmlProfile(self.server.rssIconAtTop,
self.server.cssCache, self.server.cssCache,
@ -7097,6 +7109,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
allowLocalNetworkAccess,
actorJson['skills'], actorJson['skills'],
None, None) None, None)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -7208,6 +7221,8 @@ class PubServer(BaseHTTPRequestHandler):
peertubeInstances = \ peertubeInstances = \
self.server.peertubeInstances self.server.peertubeInstances
cssCache = self.server.cssCache cssCache = self.server.cssCache
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
msg = \ msg = \
htmlIndividualPost(cssCache, htmlIndividualPost(cssCache,
recentPostsCache, recentPostsCache,
@ -7227,7 +7242,8 @@ class PubServer(BaseHTTPRequestHandler):
likedBy, likedBy,
ytDomain, ytDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances) peertubeInstances,
allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7329,6 +7345,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.showPublishedDateOnly self.server.showPublishedDateOnly
peertubeInstances = \ peertubeInstances = \
self.server.peertubeInstances self.server.peertubeInstances
allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess
msg = \ msg = \
htmlIndividualPost(self.server.cssCache, htmlIndividualPost(self.server.cssCache,
recentPostsCache, recentPostsCache,
@ -7348,7 +7366,8 @@ class PubServer(BaseHTTPRequestHandler):
likedBy, likedBy,
ytDomain, ytDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances) peertubeInstances,
allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7481,7 +7500,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
if GETstartTime: if GETstartTime:
self._benchmarkGETtimings(GETstartTime, GETtimings, self._benchmarkGETtimings(GETstartTime, GETtimings,
'show status done', 'show status done',
@ -7608,7 +7628,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, self.server.themeName, authorized, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7728,7 +7749,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, self.server.themeName, authorized, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7849,7 +7871,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7970,7 +7993,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8100,7 +8124,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8226,7 +8251,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8313,7 +8339,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.rssIconAtTop, self.server.rssIconAtTop,
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, self.server.themeName, authorized, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8417,7 +8444,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8541,7 +8569,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8657,7 +8686,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, authorized,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8763,7 +8793,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.publishButtonAtTop, self.server.publishButtonAtTop,
authorized, moderationActionStr, authorized, moderationActionStr,
self.server.themeName, self.server.themeName,
self.server.peertubeInstances) self.server.peertubeInstances,
self.server.allowLocalNetworkAccess)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -8863,6 +8894,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
shares, shares,
pageNumber, sharesPerPage) pageNumber, sharesPerPage)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
@ -8959,6 +8991,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
following, following,
pageNumber, pageNumber,
followsPerPage).encode('utf-8') followsPerPage).encode('utf-8')
@ -9055,6 +9088,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
followers, followers,
pageNumber, pageNumber,
followsPerPage).encode('utf-8') followsPerPage).encode('utf-8')
@ -9174,6 +9208,7 @@ class PubServer(BaseHTTPRequestHandler):
self.server.themeName, self.server.themeName,
self.server.dormantMonths, self.server.dormantMonths,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess,
None, None).encode('utf-8') None, None).encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,

View File

@ -16,10 +16,10 @@ if [[ "$1" == 'remove' ]]; then
rm "/etc/nginx/sites-availale/${username}" rm "/etc/nginx/sites-availale/${username}"
rm -rf ${install_destination} rm -rf ${install_destination}
if [ -d /var/www/cache ]; then if [ -d /var/www/cache ]; then
rm -rf /var/www/cache rm -rf /var/www/cache
fi fi
if [ -d /srv/http/cache ]; then if [ -d /srv/http/cache ]; then
rm -rf /srv/http/cache rm -rf /srv/http/cache
fi fi
userdel -r ${username} userdel -r ${username}
echo 'Epicyon onion instance removed' echo 'Epicyon onion instance removed'
@ -37,18 +37,36 @@ if [ -f /usr/bin/pacman ]; then
pacman -Syy pacman -Syy
pacman -S --noconfirm tor python-pip python-pysocks python-pycryptodome \ pacman -S --noconfirm tor python-pip python-pysocks python-pycryptodome \
imagemagick python-pillow python-requests \ imagemagick python-pillow python-requests \
perl-image-exiftool python-numpy python-dateutil \ perl-image-exiftool python-numpy python-dateutil \
certbot flake8 git qrencode bandit certbot flake8 git qrencode bandit
pip3 install pyLD pyqrcode pypng pip3 install pyLD pyqrcode pypng
else else
apt-get update apt-get update
apt-get -y install imagemagick python3-crypto python3-pycryptodome \ apt-get -y install imagemagick python3-crypto python3-pycryptodome \
python3-dateutil python3-idna python3-requests \ python3-dateutil python3-idna python3-requests \
python3-numpy python3-pil.imagetk python3-pip \ python3-numpy python3-pil.imagetk python3-pip \
python3-setuptools python3-socks python3-idna \ python3-setuptools python3-socks python3-idna \
libimage-exiftool-perl python3-flake8 python3-pyld \ libimage-exiftool-perl python3-flake8 python3-pyld \
python3-django-timezone-field tor nginx git qrencode \ python3-django-timezone-field tor nginx git qrencode \
python3-pyqrcode python3-png python3-bandit python3-pyqrcode python3-png python3-bandit
fi
if [[ "$(uname -a)" == *'Debian'* ]]; then
echo 'Fixing the tor daemon'
{ echo '[Unit]';
echo 'Description=Anonymizing overlay network for TCP (multi-instance-master)';
echo '';
echo '[Service]';
echo 'Type=simple';
echo 'User=root';
echo 'Group=debian-tor';
echo 'ExecStart=/usr/bin/tor --defaults-torrc /usr/share/tor/tor-service-defaults-torrc -f /etc/tor/torrc --RunAsDaemon 0';
echo '';
echo '[Install]';
echo 'WantedBy=multi-user.target'; } > /lib/systemd/system/tor.service
cp /lib/systemd/system/tor.service /root/tor.service
systemctl daemon-reload
systemctl restart tor
fi fi
echo 'Cloning the epicyon repo' echo 'Cloning the epicyon repo'
@ -56,8 +74,8 @@ if [ ! -d ${install_destination} ]; then
git clone https://gitlab.com/bashrc2/epicyon ${install_destination} git clone https://gitlab.com/bashrc2/epicyon ${install_destination}
if [ ! -d ${install_destination} ]; then if [ ! -d ${install_destination} ]; then
echo 'Epicyon repo failed to clone' echo 'Epicyon repo failed to clone'
exit 3 exit 3
fi fi
fi fi
@ -79,6 +97,7 @@ if [ ! -d /etc/torrc.d ]; then
fi fi
if ! grep -q '%include /etc/torrc.d' /etc/tor/torrc; then if ! grep -q '%include /etc/torrc.d' /etc/tor/torrc; then
echo '%include /etc/torrc.d' >> /etc/tor/torrc echo '%include /etc/torrc.d' >> /etc/tor/torrc
systemctl restart tor
fi fi
if [ ! -f /etc/torrc.d/epicyon ]; then if [ ! -f /etc/torrc.d/epicyon ]; then
@ -185,7 +204,7 @@ if [ ! -f /etc/nginx/nginx.conf ]; then
echo '}'; } > /etc/nginx/nginx.conf echo '}'; } > /etc/nginx/nginx.conf
else else
if ! grep -q 'include /etc/nginx/sites-enabled' /etc/nginx/nginx.conf; then if ! grep -q 'include /etc/nginx/sites-enabled' /etc/nginx/nginx.conf; then
echo 'include /etc/nginx/sites-enabled/*.conf;' >> /etc/nginx/nginx.conf echo 'include /etc/nginx/sites-enabled/*.conf;' >> /etc/nginx/nginx.conf
fi fi
fi fi
if [ ! -d /etc/nginx/conf.d ]; then if [ ! -d /etc/nginx/conf.d ]; then
@ -200,25 +219,25 @@ fi
if [ -f /usr/bin/pacman ]; then if [ -f /usr/bin/pacman ]; then
if [ ! -f /lib/systemd/system/nginx.service ]; then if [ ! -f /lib/systemd/system/nginx.service ]; then
echo 'Creating nginx daemon' echo 'Creating nginx daemon'
{ echo '[Unit]'; { echo '[Unit]';
echo 'Description=A high performance web server and a reverse proxy server'; echo 'Description=A high performance web server and a reverse proxy server';
echo 'Documentation=man:nginx(8)'; echo 'Documentation=man:nginx(8)';
echo 'After=network.target nss-lookup.target'; echo 'After=network.target nss-lookup.target';
echo '' echo ''
echo '[Service]'; echo '[Service]';
echo 'Type=forking'; echo 'Type=forking';
echo 'PIDFile=/run/nginx.pid'; echo 'PIDFile=/run/nginx.pid';
echo "ExecStartPre=$(which nginx) -t -q -g 'daemon on; master_process on;'"; echo "ExecStartPre=$(which nginx) -t -q -g 'daemon on; master_process on;'";
echo "ExecStart=$(which nginx) -g 'daemon on; master_process on;'"; echo "ExecStart=$(which nginx) -g 'daemon on; master_process on;'";
echo "ExecReload=$(which nginx) -g 'daemon on; master_process on;' -s reload"; echo "ExecReload=$(which nginx) -g 'daemon on; master_process on;' -s reload";
echo 'ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid'; echo 'ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid';
echo 'TimeoutStopSec=5'; echo 'TimeoutStopSec=5';
echo 'KillMode=mixed'; echo 'KillMode=mixed';
echo ''; echo '';
echo '[Install]'; echo '[Install]';
echo 'WantedBy=multi-user.target'; } > /etc/systemd/system/nginx.service echo 'WantedBy=multi-user.target'; } > /etc/systemd/system/nginx.service
systemctl enable nginx systemctl enable nginx
fi fi
fi fi
@ -257,7 +276,7 @@ echo "Creating nginx virtual host for ${ONION_DOMAIN}"
echo ' index index.html;'; echo ' index index.html;';
echo ''; echo '';
echo ' location /newsmirror {'; echo ' location /newsmirror {';
echo ' root /var/www/${ONION_DOMAIN}/htdocs;'; echo " root /var/www/${ONION_DOMAIN}/htdocs;";
echo ' try_files $uri =404;'; echo ' try_files $uri =404;';
echo ' }'; echo ' }';
echo ''; echo '';

View File

@ -51,8 +51,8 @@
--font-size-tox: 16px; --font-size-tox: 16px;
--font-size-tox2: 18px; --font-size-tox2: 18px;
--time-color: #aaa; --time-color: #aaa;
--time-vertical-align: 4px; --time-vertical-align: 0%;
--time-vertical-align-mobile: 25px; --time-vertical-align-mobile: 1.5%;
--publish-button-text: #FFFFFF; --publish-button-text: #FFFFFF;
--button-margin: 5px; --button-margin: 5px;
--button-left-margin: none; --button-left-margin: none;
@ -96,6 +96,7 @@
--column-right-width: 10vw; --column-right-width: 10vw;
--column-left-mobile-margin: 2%; --column-left-mobile-margin: 2%;
--column-left-top-margin: 0; --column-left-top-margin: 0;
--column-right-top-margin: 0;
--column-left-header-style: uppercase; --column-left-header-style: uppercase;
--column-left-header-background: #555; --column-left-header-background: #555;
--column-left-header-color: #fff; --column-left-header-color: #fff;
@ -1156,7 +1157,7 @@ div.container {
.col-right img.rightColImg { .col-right img.rightColImg {
background: var(--column-left-color); background: var(--column-left-color);
width: 100%; width: 100%;
margin: 0 0; margin-top: var(--column-right-top-margin);
padding: 0 0; padding: 0 0;
} }
.likesCount { .likesCount {

View File

@ -868,7 +868,11 @@ configPort = getConfigParam(baseDir, 'port')
if configPort: if configPort:
port = configPort port = configPort
else: else:
port = 8085 if domain.endswith('.onion') or \
domain.endswith('.i2p'):
port = 80
else:
port = 443
configProxyPort = getConfigParam(baseDir, 'proxyPort') configProxyPort = getConfigParam(baseDir, 'proxyPort')
if configProxyPort: if configProxyPort:
@ -1613,6 +1617,10 @@ if args.addaccount:
if os.path.isdir(baseDir + '/deactivated/' + nickname + '@' + domain): if os.path.isdir(baseDir + '/deactivated/' + nickname + '@' + domain):
print('Account is deactivated') print('Account is deactivated')
sys.exit() sys.exit()
if domain.endswith('.onion') or \
domain.endswith('.i2p'):
port = 80
httpPrefix = 'http'
createPerson(baseDir, nickname, domain, port, httpPrefix, createPerson(baseDir, nickname, domain, port, httpPrefix,
True, not args.noapproval, args.password.strip()) True, not args.noapproval, args.password.strip())
if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain): if os.path.isdir(baseDir + '/accounts/' + nickname + '@' + domain):

View File

@ -54,6 +54,7 @@ from blocking import isBlockedDomain
from filters import isFiltered from filters import isFiltered
from utils import updateAnnounceCollection from utils import updateAnnounceCollection
from utils import undoAnnounceCollectionEntry from utils import undoAnnounceCollectionEntry
from utils import dangerousMarkup
from httpsig import messageContentDigest from httpsig import messageContentDigest
from posts import validContentWarning from posts import validContentWarning
from posts import downloadAnnounce from posts import downloadAnnounce
@ -69,7 +70,6 @@ from media import replaceYouTube
from git import isGitPatch from git import isGitPatch
from git import receiveGitPatch from git import receiveGitPatch
from followingCalendar import receivingCalendarEvents from followingCalendar import receivingCalendarEvents
from content import dangerousMarkup
from happening import saveEventPost from happening import saveEventPost
from delete import removeOldHashtags from delete import removeOldHashtags
from follow import isFollowingActor from follow import isFollowingActor
@ -151,7 +151,8 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
postJsonObject: {}, postJsonObject: {},
allowDeletion: bool, boxname: str, allowDeletion: bool, boxname: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> None: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> None:
"""Converts the json post into html and stores it in a cache """Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later This enables the post to be quickly displayed later
""" """
@ -168,7 +169,7 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
avatarUrl, True, allowDeletion, avatarUrl, True, allowDeletion,
httpPrefix, __version__, boxname, None, httpPrefix, __version__, boxname, None,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances, allowLocalNetworkAccess,
not isDM(postJsonObject), not isDM(postJsonObject),
True, True, False, True) True, True, False, True)
@ -1259,7 +1260,8 @@ def _receiveAnnounce(recentPostsCache: {},
sendThreads: [], postLog: [], cachedWebfingers: {}, sendThreads: [], postLog: [], cachedWebfingers: {},
personCache: {}, messageJson: {}, federationList: [], personCache: {}, messageJson: {}, federationList: [],
debug: bool, translate: {}, debug: bool, translate: {},
YTReplacementDomain: str) -> bool: YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> bool:
"""Receives an announce activity within the POST section of HTTPServer """Receives an announce activity within the POST section of HTTPServer
""" """
if messageJson['type'] != 'Announce': if messageJson['type'] != 'Announce':
@ -1338,7 +1340,8 @@ def _receiveAnnounce(recentPostsCache: {},
postJsonObject = downloadAnnounce(session, baseDir, httpPrefix, postJsonObject = downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, messageJson, nickname, domain, messageJson,
__version__, translate, __version__, translate,
YTReplacementDomain) YTReplacementDomain,
allowLocalNetworkAccess)
if not postJsonObject: if not postJsonObject:
if domain not in messageJson['object'] and \ if domain not in messageJson['object'] and \
onionDomain not in messageJson['object']: onionDomain not in messageJson['object']:
@ -2119,7 +2122,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
messageJson, messageJson,
federationList, federationList,
debug, translate, debug, translate,
YTReplacementDomain): YTReplacementDomain,
allowLocalNetworkAccess):
if debug: if debug:
print('DEBUG: Announce accepted from ' + actor) print('DEBUG: Announce accepted from ' + actor)
@ -2299,7 +2303,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
if isImageMedia(session, baseDir, httpPrefix, if isImageMedia(session, baseDir, httpPrefix,
nickname, domain, postJsonObject, nickname, domain, postJsonObject,
translate, YTReplacementDomain): translate, YTReplacementDomain,
allowLocalNetworkAccess):
# media index will be updated # media index will be updated
updateIndexList.append('tlmedia') updateIndexList.append('tlmedia')
if isBlogPost(postJsonObject): if isBlogPost(postJsonObject):
@ -2349,7 +2354,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
allowDeletion, allowDeletion,
boxname, boxname,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances) peertubeInstances,
allowLocalNetworkAccess)
if debug: if debug:
timeDiff = \ timeDiff = \
str(int((time.time() - htmlCacheStartTime) * str(int((time.time() - htmlCacheStartTime) *

View File

@ -23,7 +23,6 @@ from newswire import getDictFromNewswire
# from posts import sendSignedJson # from posts import sendSignedJson
from posts import createNewsPost from posts import createNewsPost
from posts import archivePostsForPerson from posts import archivePostsForPerson
from content import dangerousMarkup
from content import validHashTag from content import validHashTag
from utils import removeHtml from utils import removeHtml
from utils import getFullDomain from utils import getFullDomain
@ -31,6 +30,7 @@ from utils import loadJson
from utils import saveJson from utils import saveJson
from utils import getStatusNumber from utils import getStatusNumber
from utils import clearFromPostCaches from utils import clearFromPostCaches
from utils import dangerousMarkup
from inbox import storeHashTags from inbox import storeHashTags
from session import createSession from session import createSession

View File

@ -17,6 +17,7 @@ from posts import sendToNamedAddresses
from utils import getFullDomain from utils import getFullDomain
from utils import removeIdEnding from utils import removeIdEnding
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import dangerousMarkup
from blocking import isBlockedDomain from blocking import isBlockedDomain
from blocking import outboxBlock from blocking import outboxBlock
from blocking import outboxUndoBlock from blocking import outboxUndoBlock
@ -36,7 +37,6 @@ from bookmarks import outboxUndoBookmark
from delete import outboxDelete from delete import outboxDelete
from shares import outboxShareUpload from shares import outboxShareUpload
from shares import outboxUndoShareUpload from shares import outboxUndoShareUpload
from content import dangerousMarkup
def postMessageToOutbox(messageJson: {}, postToNickname: str, def postMessageToOutbox(messageJson: {}, postToNickname: str,

View File

@ -55,6 +55,7 @@ from utils import locateNewsVotes
from utils import locateNewsArrival from utils import locateNewsArrival
from utils import votesOnNewswireItem from utils import votesOnNewswireItem
from utils import removeHtml from utils import removeHtml
from utils import dangerousMarkup
from media import attachMedia from media import attachMedia
from media import replaceYouTube from media import replaceYouTube
from content import tagExists from content import tagExists
@ -291,7 +292,13 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
avatarUrl = personJson['icon']['url'] avatarUrl = personJson['icon']['url']
displayName = None displayName = None
if personJson.get('name'): if personJson.get('name'):
displayName = removeHtml(personJson['name']) displayName = personJson['name']
if dangerousMarkup(personJson['name'], False):
displayName = '*ADVERSARY*'
elif isFiltered(baseDir,
nickname, domain,
displayName):
displayName = '*FILTERED*'
# have they moved? # have they moved?
if personJson.get('movedTo'): if personJson.get('movedTo'):
displayName += '' displayName += ''
@ -1824,11 +1831,16 @@ def threadSendPost(session, postJsonStr: str, federationList: [],
for attempt in range(20): for attempt in range(20):
postResult = None postResult = None
unauthorized = False unauthorized = False
if debug:
print('Getting postJsonString for ' + inboxUrl)
try: try:
postResult, unauthorized = \ postResult, unauthorized = \
postJsonString(session, postJsonStr, federationList, postJsonString(session, postJsonStr, federationList,
inboxUrl, signatureHeaderJson, inboxUrl, signatureHeaderJson,
debug) debug)
if debug:
print('Obtained postJsonString for ' + inboxUrl +
' unauthorized: ' + str(unauthorized))
except Exception as e: except Exception as e:
print('ERROR: postJsonString failed ' + str(e)) print('ERROR: postJsonString failed ' + str(e))
if unauthorized: if unauthorized:
@ -2908,7 +2920,8 @@ def isDM(postJsonObject: {}) -> bool:
def isImageMedia(session, baseDir: str, httpPrefix: str, def isImageMedia(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str, nickname: str, domain: str,
postJsonObject: {}, translate: {}, postJsonObject: {}, translate: {},
YTReplacementDomain: str) -> bool: YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given post has attached image media """Returns true if the given post has attached image media
""" """
if postJsonObject['type'] == 'Announce': if postJsonObject['type'] == 'Announce':
@ -2916,7 +2929,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
downloadAnnounce(session, baseDir, httpPrefix, downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject, nickname, domain, postJsonObject,
__version__, translate, __version__, translate,
YTReplacementDomain) YTReplacementDomain,
allowLocalNetworkAccess)
if postJsonAnnounce: if postJsonAnnounce:
postJsonObject = postJsonAnnounce postJsonObject = postJsonAnnounce
if postJsonObject['type'] != 'Create': if postJsonObject['type'] != 'Create':
@ -3831,7 +3845,8 @@ def _rejectAnnounce(announceFilename: str):
def downloadAnnounce(session, baseDir: str, httpPrefix: str, def downloadAnnounce(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str, nickname: str, domain: str,
postJsonObject: {}, projectVersion: str, postJsonObject: {}, projectVersion: str,
translate: {}, YTReplacementDomain: str) -> {}: translate: {}, YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> {}:
"""Download the post referenced by an announce """Download the post referenced by an announce
""" """
if not postJsonObject.get('object'): if not postJsonObject.get('object'):
@ -3911,20 +3926,16 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if '/statuses/' not in announcedJson['id']: if '/statuses/' not in announcedJson['id']:
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
return None return None
if '/users/' not in announcedJson['id'] and \ if not hasUsersPath(announcedJson['id']):
'/accounts/' not in announcedJson['id'] and \
'/channel/' not in announcedJson['id'] and \
'/profile/' not in announcedJson['id']:
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
return None return None
if not announcedJson.get('type'): if not announcedJson.get('type'):
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None return None
if announcedJson['type'] != 'Note' and \ if announcedJson['type'] != 'Note' and \
announcedJson['type'] != 'Article': announcedJson['type'] != 'Article':
# You can only announce Note or Article types
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None return None
if not announcedJson.get('content'): if not announcedJson.get('content'):
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
@ -3935,16 +3946,25 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if not validPostDate(announcedJson['published']): if not validPostDate(announcedJson['published']):
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
return None return None
if isFiltered(baseDir, nickname, domain, announcedJson['content']):
# Check the content of the announce
contentStr = announcedJson['content']
if dangerousMarkup(contentStr, allowLocalNetworkAccess):
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
return None return None
if isFiltered(baseDir, nickname, domain, contentStr):
_rejectAnnounce(announceFilename)
return None
# remove any long words # remove any long words
announcedJson['content'] = \ contentStr = removeLongWords(contentStr, 40, [])
removeLongWords(announcedJson['content'], 40, [])
# remove text formatting, such as bold/italics # remove text formatting, such as bold/italics
announcedJson['content'] = \ contentStr = removeTextFormatting(contentStr)
removeTextFormatting(announcedJson['content'])
# set the content after santitization
announcedJson['content'] = contentStr
# wrap in create to be consistent with other posts # wrap in create to be consistent with other posts
announcedJson = \ announcedJson = \
@ -3952,8 +3972,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
actorNickname, actorDomain, actorPort, actorNickname, actorDomain, actorPort,
announcedJson) announcedJson)
if announcedJson['type'] != 'Create': if announcedJson['type'] != 'Create':
# Create wrap failed
_rejectAnnounce(announceFilename) _rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None return None
# labelAccusatoryPost(postJsonObject, translate) # labelAccusatoryPost(postJsonObject, translate)

3
pyproject.toml 100644
View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

16
setup.cfg 100644
View File

@ -0,0 +1,16 @@
[metadata]
name = epicyon
version = 1.2.0
[options]
packages = .
install_requires =
crypto
idna<3,>=2.5
numpy
pillow
pycryptodome
pyqrcode
python-dateutil
requests
socks

View File

@ -49,6 +49,7 @@ from utils import saveJson
from utils import getStatusNumber from utils import getStatusNumber
from utils import getFollowersOfPerson from utils import getFollowersOfPerson
from utils import removeHtml from utils import removeHtml
from utils import dangerousMarkup
from follow import followerOfPerson from follow import followerOfPerson
from follow import unfollowAccount from follow import unfollowAccount
from follow import unfollowerOfAccount from follow import unfollowerOfAccount
@ -77,7 +78,6 @@ from inbox import validInboxFilenames
from categories import guessHashtagCategory from categories import guessHashtagCategory
from content import htmlReplaceEmailQuote from content import htmlReplaceEmailQuote
from content import htmlReplaceQuoteMarks from content import htmlReplaceQuoteMarks
from content import dangerousMarkup
from content import dangerousCSS from content import dangerousCSS
from content import addWebLinks from content import addWebLinks
from content import replaceEmojiFromTags from content import replaceEmojiFromTags
@ -95,6 +95,7 @@ from newswire import getNewswireTags
from newswire import parseFeedDate from newswire import parseFeedDate
from mastoapiv1 import getMastoApiV1IdFromNickname from mastoapiv1 import getMastoApiV1IdFromNickname
from mastoapiv1 import getNicknameFromMastoApiV1Id from mastoapiv1 import getNicknameFromMastoApiV1Id
from webapp_post import prepareHtmlPostNickname
testServerAliceRunning = False testServerAliceRunning = False
testServerBobRunning = False testServerBobRunning = False
@ -3072,9 +3073,25 @@ def testDomainHandling():
assert decodedHost(testDomain) == "españa.icom.museum" assert decodedHost(testDomain) == "españa.icom.museum"
def testPrepareHtmlPostNickname():
print('testPrepareHtmlPostNickname')
postHtml = '<a class="imageAnchor" href="/users/bob?replyfollowers='
postHtml += '<a class="imageAnchor" href="/users/bob?repeatprivate='
result = prepareHtmlPostNickname('alice', postHtml)
assert result == postHtml.replace('/bob?', '/alice?')
postHtml = '<a class="imageAnchor" href="/users/bob?replyfollowers='
postHtml += '<a class="imageAnchor" href="/users/bob;repeatprivate='
expectedHtml = '<a class="imageAnchor" href="/users/alice?replyfollowers='
expectedHtml += '<a class="imageAnchor" href="/users/bob;repeatprivate='
result = prepareHtmlPostNickname('alice', postHtml)
assert result == expectedHtml
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testFunctions() testFunctions()
testPrepareHtmlPostNickname()
testDomainHandling() testDomainHandling()
testMastoApi() testMastoApi()
testLinksWithinPost() testLinksWithinPost()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,10 @@
{ {
"column-left-top-margin": "0.4cm",
"column-right-top-margin": "0.8cm",
"time-vertical-align": "0%",
"time-vertical-align-mobile": "1.5%",
"post-separator-margin-top": "10px",
"post-separator-margin-bottom": "10px",
"column-left-header-background": "#35244d", "column-left-header-background": "#35244d",
"newswire-publish-icon": "True", "newswire-publish-icon": "True",
"full-width-timeline-buttons": "False", "full-width-timeline-buttons": "False",

View File

@ -363,5 +363,8 @@
"Other accounts": "حسابات أخرى", "Other accounts": "حسابات أخرى",
"Pin this post to your profile.": "تثبيت هذه الوظيفة في ملف التعريف الخاص بك.", "Pin this post to your profile.": "تثبيت هذه الوظيفة في ملف التعريف الخاص بك.",
"Administered by": "تدار من قبل", "Administered by": "تدار من قبل",
"Version": "الإصدار" "Version": "الإصدار",
"Skip to timeline": "تخطي إلى الجدول الزمني",
"Skip to Newswire": "انتقل إلى Newswire",
"Skip to Links": "تخطي إلى روابط الويب"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Altres comptes", "Other accounts": "Altres comptes",
"Pin this post to your profile.": "Fixa aquesta publicació al teu perfil.", "Pin this post to your profile.": "Fixa aquesta publicació al teu perfil.",
"Administered by": "Administrat per", "Administered by": "Administrat per",
"Version": "Versió" "Version": "Versió",
"Skip to timeline": "Ves a la cronologia",
"Skip to Newswire": "Vés a Newswire",
"Skip to Links": "Vés als enllaços web"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Cyfrifon eraill", "Other accounts": "Cyfrifon eraill",
"Pin this post to your profile.": "Piniwch y post hwn i'ch proffil.", "Pin this post to your profile.": "Piniwch y post hwn i'ch proffil.",
"Administered by": "Gweinyddir gan", "Administered by": "Gweinyddir gan",
"Version": "Fersiwn" "Version": "Fersiwn",
"Skip to timeline": "Neidio i'r llinell amser",
"Skip to Newswire": "Neidio i Newswire",
"Skip to Links": "Neidio i Dolenni Gwe"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Andere Konten", "Other accounts": "Andere Konten",
"Pin this post to your profile.": "Pin diesen Beitrag zu Ihrem Profil.", "Pin this post to your profile.": "Pin diesen Beitrag zu Ihrem Profil.",
"Administered by": "Verwaltet von", "Administered by": "Verwaltet von",
"Version": "Ausführung" "Version": "Ausführung",
"Skip to timeline": "Zur Zeitleiste springen",
"Skip to Newswire": "Springe zu Newswire",
"Skip to Links": "Springe zu Weblinks"
} }

View File

@ -48,7 +48,7 @@
"Location": "Location", "Location": "Location",
"Login": "Login", "Login": "Login",
"Edit": "Edit", "Edit": "Edit",
"Switch to timeline view": "Switch to timeline view", "Switch to timeline view": "Timeline view",
"Approve": "Approve", "Approve": "Approve",
"Deny": "Deny", "Deny": "Deny",
"Posts": "Posts", "Posts": "Posts",
@ -63,7 +63,7 @@
"Your browser does not support the video element.": "Your browser does not support the video element.", "Your browser does not support the video element.": "Your browser does not support the video element.",
"Create a new post": "New post", "Create a new post": "New post",
"Create a new DM": "Create a new DM", "Create a new DM": "Create a new DM",
"Switch to profile view": "Switch to profile view", "Switch to profile view": "Profile view",
"Inbox": "Inbox", "Inbox": "Inbox",
"Outbox": "Outbox", "Outbox": "Outbox",
"Search and follow": "Search/follow", "Search and follow": "Search/follow",
@ -363,5 +363,8 @@
"Other accounts": "Other accounts", "Other accounts": "Other accounts",
"Pin this post to your profile.": "Pin this post to your profile.", "Pin this post to your profile.": "Pin this post to your profile.",
"Administered by": "Administered by", "Administered by": "Administered by",
"Version": "Version" "Version": "Version",
"Skip to timeline": "Skip to timeline",
"Skip to Newswire": "Skip to Newswire",
"Skip to Links": "Skip to Links"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Otras cuentas", "Other accounts": "Otras cuentas",
"Pin this post to your profile.": "Fija esta publicación a tu perfil.", "Pin this post to your profile.": "Fija esta publicación a tu perfil.",
"Administered by": "Administrado por", "Administered by": "Administrado por",
"Version": "Versión" "Version": "Versión",
"Skip to timeline": "Saltar a la línea de tiempo",
"Skip to Newswire": "Saltar a Newswire",
"Skip to Links": "Saltar a enlaces web"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Autres comptes", "Other accounts": "Autres comptes",
"Pin this post to your profile.": "Épinglez ce message à votre profil.", "Pin this post to your profile.": "Épinglez ce message à votre profil.",
"Administered by": "Administré par", "Administered by": "Administré par",
"Version": "Version" "Version": "Version",
"Skip to timeline": "Passer à la chronologie",
"Skip to Newswire": "Passer à Newswire",
"Skip to Links": "Passer aux liens Web"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Cuntais eile", "Other accounts": "Cuntais eile",
"Pin this post to your profile.": "Bioráin an post seo le do phróifíl.", "Pin this post to your profile.": "Bioráin an post seo le do phróifíl.",
"Administered by": "Riartha ag", "Administered by": "Riartha ag",
"Version": "Leagan" "Version": "Leagan",
"Skip to timeline": "Scipeáil chuig an amlíne",
"Skip to Newswire": "Scipeáil chuig Newswire",
"Skip to Links": "Scipeáil chuig Naisc Ghréasáin"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "अन्य खाते", "Other accounts": "अन्य खाते",
"Pin this post to your profile.": "इस पोस्ट को अपनी प्रोफाइल पर पिन करें।", "Pin this post to your profile.": "इस पोस्ट को अपनी प्रोफाइल पर पिन करें।",
"Administered by": "द्वारा प्रशासित", "Administered by": "द्वारा प्रशासित",
"Version": "संस्करण" "Version": "संस्करण",
"Skip to timeline": "टाइमलाइन पर जाएं",
"Skip to Newswire": "Newswire पर जाएं",
"Skip to Links": "वेब लिंक पर जाएं"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Altri account", "Other accounts": "Altri account",
"Pin this post to your profile.": "Metti questo post sul tuo profilo.", "Pin this post to your profile.": "Metti questo post sul tuo profilo.",
"Administered by": "Amministrato da", "Administered by": "Amministrato da",
"Version": "Versione" "Version": "Versione",
"Skip to timeline": "Passa alla sequenza temporale",
"Skip to Newswire": "Passa a Newswire",
"Skip to Links": "Passa a collegamenti Web"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "その他のアカウント", "Other accounts": "その他のアカウント",
"Pin this post to your profile.": "この投稿をプロフィールに固定します。", "Pin this post to your profile.": "この投稿をプロフィールに固定します。",
"Administered by": "管理者", "Administered by": "管理者",
"Version": "バージョン" "Version": "バージョン",
"Skip to timeline": "タイムラインにスキップ",
"Skip to Newswire": "Newswireにスキップ",
"Skip to Links": "Webリンクにスキップ"
} }

View File

@ -359,5 +359,8 @@
"Other accounts": "Other accounts", "Other accounts": "Other accounts",
"Pin this post to your profile.": "Pin this post to your profile.", "Pin this post to your profile.": "Pin this post to your profile.",
"Administered by": "Administered by", "Administered by": "Administered by",
"Version": "Version" "Version": "Version",
"Skip to timeline": "Skip to timeline",
"Skip to Newswire": "Skip to Newswire",
"Skip to Links": "Skip to Links"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Outras contas", "Other accounts": "Outras contas",
"Pin this post to your profile.": "Fixar esta postagem em seu perfil.", "Pin this post to your profile.": "Fixar esta postagem em seu perfil.",
"Administered by": "Administrado por", "Administered by": "Administrado por",
"Version": "Versão" "Version": "Versão",
"Skip to timeline": "Pular para a linha do tempo",
"Skip to Newswire": "Pular para Newswire",
"Skip to Links": "Pular para links da web"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "Другие аккаунты", "Other accounts": "Другие аккаунты",
"Pin this post to your profile.": "Закрепите это сообщение в своем профиле.", "Pin this post to your profile.": "Закрепите это сообщение в своем профиле.",
"Administered by": "Под управлением", "Administered by": "Под управлением",
"Version": "Версия" "Version": "Версия",
"Skip to timeline": "Перейти к временной шкале",
"Skip to Newswire": "Перейти к ленте новостей",
"Skip to Links": "Перейти к веб-ссылкам"
} }

View File

@ -363,5 +363,8 @@
"Other accounts": "其他账户", "Other accounts": "其他账户",
"Pin this post to your profile.": "将此帖子固定到您的个人资料。", "Pin this post to your profile.": "将此帖子固定到您的个人资料。",
"Administered by": "由...管理", "Administered by": "由...管理",
"Version": "版" "Version": "版",
"Skip to timeline": "跳到时间线",
"Skip to Newswire": "跳到新闻专线",
"Skip to Links": "跳到网页链接"
} }

View File

@ -554,6 +554,38 @@ def urlPermitted(url: str, federationList: []):
return False return False
def dangerousMarkup(content: str, allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given content contains dangerous html markup
"""
if '<' not in content:
return False
if '>' not in content:
return False
contentSections = content.split('<')
invalidPartials = ()
if not allowLocalNetworkAccess:
invalidPartials = ('localhost', '127.0.', '192.168', '10.0.')
invalidStrings = ('script', 'canvas', 'style', 'abbr',
'frame', 'iframe', 'html', 'body',
'hr', 'allow-popups', 'allow-scripts')
for markup in contentSections:
if '>' not in markup:
continue
markup = markup.split('>')[0].strip()
for partialMatch in invalidPartials:
if partialMatch in markup:
return True
if ' ' not in markup:
for badStr in invalidStrings:
if badStr in markup:
return True
else:
for badStr in invalidStrings:
if badStr + ' ' in markup:
return True
return False
def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str: def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
"""Returns the display name for the given actor """Returns the display name for the given actor
""" """
@ -561,9 +593,10 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
actor = actor.split('/statuses/')[0] actor = actor.split('/statuses/')[0]
if not personCache.get(actor): if not personCache.get(actor):
return None return None
nameFound = None
if personCache[actor].get('actor'): if personCache[actor].get('actor'):
if personCache[actor]['actor'].get('name'): if personCache[actor]['actor'].get('name'):
return personCache[actor]['actor']['name'] nameFound = personCache[actor]['actor']['name']
else: else:
# Try to obtain from the cached actors # Try to obtain from the cached actors
cachedActorFilename = \ cachedActorFilename = \
@ -572,8 +605,11 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
actorJson = loadJson(cachedActorFilename, 1) actorJson = loadJson(cachedActorFilename, 1)
if actorJson: if actorJson:
if actorJson.get('name'): if actorJson.get('name'):
return(actorJson['name']) nameFound = actorJson['name']
return None if nameFound:
if dangerousMarkup(nameFound, False):
nameFound = "*ADVERSARY*"
return nameFound
def getNicknameFromActor(actor: str) -> str: def getNicknameFromActor(actor: str) -> str:
@ -1721,6 +1757,11 @@ def siteIsActive(url: str) -> bool:
""" """
if not url.startswith('http'): if not url.startswith('http'):
return False return False
if '.onion/' in url or '.i2p/' in url or \
url.endswith('.onion') or \
url.endswith('.i2p'):
# skip this check for onion and i2p
return True
try: try:
req = urllib.request.Request(url) req = urllib.request.Request(url)
urllib.request.urlopen(req, timeout=10) # nosec urllib.request.urlopen(req, timeout=10) # nosec

View File

@ -90,7 +90,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
htmlStr += \ htmlStr += \
'\n <center>\n' + \ '\n <center>\n' + \
' <img class="leftColImg" ' + \ ' <img class="leftColImg" ' + \
'loading="lazy" src="/users/' + \ 'alt="" loading="lazy" src="/users/' + \
nickname + '/' + leftImageFile + '" />\n' + \ nickname + '/' + leftImageFile + '" />\n' + \
' </center>\n' ' </center>\n'
@ -115,7 +115,7 @@ def getLeftColumnContent(baseDir: str, nickname: str, domainFull: str,
'/users/' + nickname + '/editlinks">' + \ '/users/' + nickname + '/editlinks">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['Edit Links'] + '" title="' + \ translate['Edit Links'] + ' | " title="' + \
translate['Edit Links'] + '" src="/' + \ translate['Edit Links'] + '" src="/' + \
'icons/edit.png" /></a>\n' 'icons/edit.png" /></a>\n'
@ -268,6 +268,7 @@ def htmlLinksMobile(cssCache: {}, baseDir: str,
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'alt="' + translate['Switch to timeline view'] + '" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n' 'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += '<div class="col-left-mobile">\n' htmlStr += '<div class="col-left-mobile">\n'
@ -333,7 +334,8 @@ def htmlEditLinks(cssCache: {}, translate: {}, baseDir: str, path: str,
'<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
editLinksForm += '<img loading="lazy" class="timeline-banner" src="' + \ editLinksForm += '<img loading="lazy" class="timeline-banner" ' + \
'alt = "" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \ '/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \
'</header>\n' '</header>\n'

View File

@ -92,7 +92,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
htmlStr += \ htmlStr += \
'\n <center>\n' + \ '\n <center>\n' + \
' <img class="rightColImg" ' + \ ' <img class="rightColImg" ' + \
'loading="lazy" src="/users/' + \ 'alt="" loading="lazy" src="/users/' + \
nickname + '/' + rightImageFile + '" />\n' + \ nickname + '/' + rightImageFile + '" />\n' + \
' </center>\n' ' </center>\n'
@ -123,7 +123,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
'/users/' + nickname + '/editnewswire">' + \ '/users/' + nickname + '/editnewswire">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['Edit newswire'] + '" title="' + \ translate['Edit newswire'] + ' | " title="' + \
translate['Edit newswire'] + '" src="/' + \ translate['Edit newswire'] + '" src="/' + \
'icons/edit_notify.png" /></a>\n' 'icons/edit_notify.png" /></a>\n'
else: else:
@ -133,7 +133,7 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
'/users/' + nickname + '/editnewswire">' + \ '/users/' + nickname + '/editnewswire">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['Edit newswire'] + '" title="' + \ translate['Edit newswire'] + ' | " title="' + \
translate['Edit newswire'] + '" src="/' + \ translate['Edit newswire'] + '" src="/' + \
'icons/edit.png" /></a>\n' 'icons/edit.png" /></a>\n'
@ -142,14 +142,14 @@ def getRightColumnContent(baseDir: str, nickname: str, domainFull: str,
' <a href="/categories.xml">' + \ ' <a href="/categories.xml">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['Hashtag Categories RSS Feed'] + '" title="' + \ translate['Hashtag Categories RSS Feed'] + ' | " title="' + \
translate['Hashtag Categories RSS Feed'] + '" src="/' + \ translate['Hashtag Categories RSS Feed'] + '" src="/' + \
'icons/categoriesrss.png" /></a>\n' 'icons/categoriesrss.png" /></a>\n'
rssIconStr += \ rssIconStr += \
' <a href="/newswire.xml">' + \ ' <a href="/newswire.xml">' + \
'<img class="' + editImageClass + \ '<img class="' + editImageClass + \
'" loading="lazy" alt="' + \ '" loading="lazy" alt="' + \
translate['Newswire RSS Feed'] + '" title="' + \ translate['Newswire RSS Feed'] + ' | " title="' + \
translate['Newswire RSS Feed'] + '" src="/' + \ translate['Newswire RSS Feed'] + '" src="/' + \
'icons/logorss.png" /></a>\n' 'icons/logorss.png" /></a>\n'
if rssIconAtTop: if rssIconAtTop:
@ -241,6 +241,7 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
if faviconUrl: if faviconUrl:
faviconLink = \ faviconLink = \
'<img loading="lazy" src="' + faviconUrl + '" ' + \ '<img loading="lazy" src="' + faviconUrl + '" ' + \
'alt="" ' + \
_getBrokenFavSubstitute() + '/>' _getBrokenFavSubstitute() + '/>'
moderatedItem = item[5] moderatedItem = item[5]
htmlStr += separatorStr htmlStr += separatorStr
@ -265,6 +266,7 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
'/newswireunvote=' + dateStrLink + '" ' + \ '/newswireunvote=' + dateStrLink + '" ' + \
'title="' + translate['Remove Vote'] + '">' 'title="' + translate['Remove Vote'] + '">'
htmlStr += '<img loading="lazy" class="voteicon" src="/' + \ htmlStr += '<img loading="lazy" class="voteicon" src="/' + \
'alt="' + translate['Remove Vote'] + '" ' + \
'icons/vote.png" /></a></p>\n' 'icons/vote.png" /></a></p>\n'
else: else:
htmlStr += ' <span class="newswireDateVotedOn">' htmlStr += ' <span class="newswireDateVotedOn">'
@ -290,8 +292,9 @@ def _htmlNewswire(baseDir: str, newswire: {}, nickname: str, moderator: bool,
htmlStr += '<a href="/users/' + nickname + \ htmlStr += '<a href="/users/' + nickname + \
'/newswirevote=' + dateStrLink + '" ' + \ '/newswirevote=' + dateStrLink + '" ' + \
'title="' + translate['Vote'] + '">' 'title="' + translate['Vote'] + '">'
htmlStr += '<img class="voteicon" src="/' + \ htmlStr += '<img class="voteicon" ' + \
'icons/vote.png" /></a>' 'alt="' + translate['Vote'] + '" ' + \
'src="/icons/vote.png" /></a>'
htmlStr += '</p>\n' htmlStr += '</p>\n'
else: else:
htmlStr += '<p class="newswireItem">' + \ htmlStr += '<p class="newswireItem">' + \
@ -354,7 +357,8 @@ def htmlCitations(baseDir: str, nickname: str, domain: str,
'<a href="/users/' + nickname + '/newblog" title="' + \ '<a href="/users/' + nickname + '/newblog" title="' + \
translate['Go Back'] + '" alt="' + \ translate['Go Back'] + '" alt="' + \
translate['Go Back'] + '">\n' translate['Go Back'] + '">\n'
htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \ htmlStr += '<img loading="lazy" class="timeline-banner" ' + \
'alt="" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' '/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += \ htmlStr += \
@ -464,6 +468,7 @@ def htmlNewswireMobile(cssCache: {}, baseDir: str, nickname: str,
htmlStr += \ htmlStr += \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \ '<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
'<img loading="lazy" class="timeline-banner" ' + \ '<img loading="lazy" class="timeline-banner" ' + \
'alt="' + translate['Timeline banner image'] + '" ' + \
'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n' 'src="/users/' + nickname + '/' + bannerFile + '" /></a>\n'
htmlStr += '<div class="col-right-mobile">\n' htmlStr += '<div class="col-right-mobile">\n'
@ -531,8 +536,8 @@ def htmlEditNewswire(cssCache: {}, translate: {}, baseDir: str, path: str,
translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
editNewswireForm += '<img loading="lazy" class="timeline-banner" src="' + \ editNewswireForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \ '/users/' + nickname + '/' + bannerFile + '" ' + \
'</header>' 'alt="" /></a>\n</header>'
editNewswireForm += \ editNewswireForm += \
'<form enctype="multipart/form-data" method="POST" ' + \ '<form enctype="multipart/form-data" method="POST" ' + \

View File

@ -30,7 +30,8 @@ def htmlConfirmDelete(cssCache: {},
callingDomain: str, callingDomain: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Shows a screen asking to confirm the deletion of a post """Shows a screen asking to confirm the deletion of a post
""" """
if '/statuses/' not in messageId: if '/statuses/' not in messageId:
@ -70,7 +71,7 @@ def htmlConfirmDelete(cssCache: {},
httpPrefix, projectVersion, 'outbox', httpPrefix, projectVersion, 'outbox',
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances, allowLocalNetworkAccess,
False, False, False, False, False) False, False, False, False, False)
deletePostStr += '<center>' deletePostStr += '<center>'
deletePostStr += \ deletePostStr += \

View File

@ -572,7 +572,7 @@ def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \ newPostForm += '<img loading="lazy" class="timeline-banner" src="' + \
'/users/' + nickname + '/' + bannerFile + '" /></a>\n' + \ '/users/' + nickname + '/' + bannerFile + '" alt="" /></a>\n' + \
'</header>\n' '</header>\n'
mentionsStr = '' mentionsStr = ''

View File

@ -29,7 +29,8 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
projectVersion: str, projectVersion: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Shows posts on the front screen of a news instance """Shows posts on the front screen of a news instance
These should only be public blog posts from the features timeline These should only be public blog posts from the features timeline
which is the blog timeline of the news actor which is the blog timeline of the news actor
@ -69,6 +70,7 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, False, False, True, False) False, False, False, True, False)
if postStr: if postStr:
profileStr += postStr + separatorStr profileStr += postStr + separatorStr
@ -91,6 +93,7 @@ def htmlFrontScreen(rssIconAtTop: bool,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
newswire: {}, theme: str, newswire: {}, theme: str,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool,
extraJson=None, extraJson=None,
pageNumber=None, maxItemsPerPage=None) -> str: pageNumber=None, maxItemsPerPage=None) -> str:
"""Show the news instance front screen """Show the news instance front screen
@ -155,7 +158,8 @@ def htmlFrontScreen(rssIconAtTop: bool,
projectVersion, projectVersion,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances) + licenseStr peertubeInstances,
allowLocalNetworkAccess) + licenseStr
# Footer which is only used for system accounts # Footer which is only used for system accounts
profileFooterStr = ' </td>\n' profileFooterStr = ' </td>\n'

View File

@ -263,7 +263,7 @@ def htmlSearchHashtagCategory(cssCache: {}, translate: {},
if os.path.isfile(searchBannerFilename): if os.path.isfile(searchBannerFilename):
htmlStr += '<a href="' + actor + '/search">\n' htmlStr += '<a href="' + actor + '/search">\n'
htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \ htmlStr += '<img loading="lazy" class="timeline-banner" src="' + \
actor + '/' + searchBannerFile + '" /></a>\n' actor + '/' + searchBannerFile + '" alt="" /></a>\n'
htmlStr += '<div class="follow">' htmlStr += '<div class="follow">'
htmlStr += '<center><br><br><br>' htmlStr += '<center><br><br><br>'

View File

@ -138,9 +138,12 @@ def htmlLogin(cssCache: {}, translate: {},
loginForm += '<br>\n' loginForm += '<br>\n'
loginForm += '<form method="POST" action="/login">\n' loginForm += '<form method="POST" action="/login">\n'
loginForm += ' <div class="imgcontainer">\n' loginForm += ' <div class="imgcontainer">\n'
instanceTitle = getConfigParam(baseDir, 'instanceTitle')
if not instanceTitle:
instanceTitle = "Epicyon"
loginForm += \ loginForm += \
' <img loading="lazy" src="' + loginImage + \ ' <img loading="lazy" src="' + loginImage + \
'" alt="login image" class="loginimage">\n' '" alt="' + instanceTitle + '" class="loginimage">\n'
loginForm += loginText + TOSstr + '\n' loginForm += loginText + TOSstr + '\n'
loginForm += ' </div>\n' loginForm += ' </div>\n'
loginForm += '\n' loginForm += '\n'

View File

@ -42,7 +42,8 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, moderationActionStr: str, authorized: bool, moderationActionStr: str,
theme: str, peertubeInstances: []) -> str: theme: str, peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the moderation feed as html """Show the moderation feed as html
This is what you see when selecting the "mod" timeline This is what you see when selecting the "mod" timeline
""" """
@ -57,7 +58,7 @@ def htmlModeration(cssCache: {}, defaultTimeline: str,
showPublishAsIcon, fullWidthTimelineButtonHeader, showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, moderationActionStr, theme, authorized, moderationActionStr, theme,
peertubeInstances) peertubeInstances, allowLocalNetworkAccess)
def htmlAccountInfo(cssCache: {}, translate: {}, def htmlAccountInfo(cssCache: {}, translate: {},

View File

@ -120,7 +120,7 @@ def htmlPersonOptions(defaultTimeline: str,
optionsStr += ' <center>\n' optionsStr += ' <center>\n'
optionsStr += ' <a href="' + optionsActor + '">\n' optionsStr += ' <a href="' + optionsActor + '">\n'
optionsStr += ' <img loading="lazy" src="' + optionsProfileUrl + \ optionsStr += ' <img loading="lazy" src="' + optionsProfileUrl + \
'" ' + getBrokenLinkSubstitute() + '/></a>\n' '" alt="" ' + getBrokenLinkSubstitute() + '/></a>\n'
handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain handle = getNicknameFromActor(optionsActor) + '@' + optionsDomain
handleShown = handle handleShown = handle
if lockedAccount: if lockedAccount:

View File

@ -75,7 +75,46 @@ def _logPostTiming(enableTimingLog: bool, postStartTime, debugId: str) -> None:
print('TIMING INDIV ' + debugId + ' = ' + str(timeDiff)) print('TIMING INDIV ' + debugId + ' = ' + str(timeDiff))
def preparePostFromHtmlCache(postHtml: str, boxName: str, def prepareHtmlPostNickname(nickname: str, postHtml: str) -> str:
"""html posts stored in memory are for all accounts on the instance
and they're indexed by id. However, some incoming posts may be
destined for multiple accounts (followers). This creates a problem
where the icon links whose urls begin with href="/users/nickname?
need to be changed for different nicknames to display correctly
within their timelines.
This function changes the nicknames for the icon links.
"""
# replace the nickname
usersStr = ' href="/users/'
if usersStr not in postHtml:
return postHtml
userFound = True
postStr = postHtml
newPostStr = ''
while userFound:
if usersStr not in postStr:
newPostStr += postStr
break
# the next part, after href="/users/nickname?
nextStr = postStr.split(usersStr, 1)[1]
if '?' in nextStr:
nextStr = nextStr.split('?', 1)[1]
else:
newPostStr += postStr
break
# append the previous text to the result
newPostStr += postStr.split(usersStr)[0]
newPostStr += usersStr + nickname + '?'
# post is now the next part
postStr = nextStr
return newPostStr
def preparePostFromHtmlCache(nickname: str, postHtml: str, boxName: str,
pageNumber: int) -> str: pageNumber: int) -> str:
"""Sets the page number on a cached html post """Sets the page number on a cached html post
""" """
@ -91,7 +130,7 @@ def preparePostFromHtmlCache(postHtml: str, boxName: str,
withPageNumber = postHtml.replace(';-999;', ';' + str(pageNumber) + ';') withPageNumber = postHtml.replace(';-999;', ';' + str(pageNumber) + ';')
withPageNumber = withPageNumber.replace('?page=-999', withPageNumber = withPageNumber.replace('?page=-999',
'?page=' + str(pageNumber)) '?page=' + str(pageNumber))
return withPageNumber return prepareHtmlPostNickname(nickname, withPageNumber)
def _saveIndividualPostAsHtmlToCache(baseDir: str, def _saveIndividualPostAsHtmlToCache(baseDir: str,
@ -173,7 +212,8 @@ def _getPostFromRecentCache(session,
if not postHtml: if not postHtml:
return None return None
postHtml = preparePostFromHtmlCache(postHtml, boxName, pageNumber) postHtml = \
preparePostFromHtmlCache(nickname, postHtml, boxName, pageNumber)
updateRecentPostsCache(recentPostsCache, maxRecentPosts, updateRecentPostsCache(recentPostsCache, maxRecentPosts,
postJsonObject, postHtml) postJsonObject, postHtml)
_logPostTiming(enableTimingLog, postStartTime, '3') _logPostTiming(enableTimingLog, postStartTime, '3')
@ -1081,6 +1121,7 @@ def individualPostAsHtml(allowDownloads: bool,
boxName: str, YTReplacementDomain: str, boxName: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool,
showRepeats=True, showRepeats=True,
showIcons=False, showIcons=False,
manuallyApprovesFollowers=False, manuallyApprovesFollowers=False,
@ -1231,7 +1272,8 @@ def individualPostAsHtml(allowDownloads: bool,
downloadAnnounce(session, baseDir, httpPrefix, downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject, nickname, domain, postJsonObject,
projectVersion, translate, projectVersion, translate,
YTReplacementDomain) YTReplacementDomain,
allowLocalNetworkAccess)
if not postJsonAnnounce: if not postJsonAnnounce:
return '' return ''
postJsonObject = postJsonAnnounce postJsonObject = postJsonAnnounce
@ -1605,7 +1647,8 @@ def htmlIndividualPost(cssCache: {},
projectVersion: str, likedBy: str, projectVersion: str, likedBy: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show an individual post as html """Show an individual post as html
""" """
postStr = '' postStr = ''
@ -1646,6 +1689,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, authorized, False, False, False) False, authorized, False, False, False)
messageId = removeIdEnding(postJsonObject['id']) messageId = removeIdEnding(postJsonObject['id'])
@ -1672,6 +1716,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, authorized, False, authorized,
False, False, False) + postStr False, False, False) + postStr
@ -1701,6 +1746,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, authorized, False, authorized,
False, False, False) False, False, False)
cssFilename = baseDir + '/epicyon-profile.css' cssFilename = baseDir + '/epicyon-profile.css'
@ -1721,7 +1767,8 @@ def htmlPostReplies(cssCache: {},
httpPrefix: str, projectVersion: str, httpPrefix: str, projectVersion: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the replies to an individual post as html """Show the replies to an individual post as html
""" """
repliesStr = '' repliesStr = ''
@ -1739,6 +1786,7 @@ def htmlPostReplies(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, False, False, False, False) False, False, False, False, False)
cssFilename = baseDir + '/epicyon-profile.css' cssFilename = baseDir + '/epicyon-profile.css'

View File

@ -63,7 +63,8 @@ def htmlProfileAfterSearch(cssCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
defaultTimeline: str, defaultTimeline: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show a profile page after a search for a fediverse address """Show a profile page after a search for a fediverse address
""" """
if hasUsersPath(profileHandle) or '/@' in profileHandle: if hasUsersPath(profileHandle) or '/@' in profileHandle:
@ -292,7 +293,7 @@ def htmlProfileAfterSearch(cssCache: {},
httpPrefix, projectVersion, 'inbox', httpPrefix, projectVersion, 'inbox',
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances, allowLocalNetworkAccess,
False, False, False, False, False) False, False, False, False, False)
i += 1 i += 1
if i >= 20: if i >= 20:
@ -323,6 +324,7 @@ def _getProfileHeader(baseDir: str, httpPrefix: str,
nickname + '/' + defaultTimeline + '" title="' + \ nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
htmlStr += ' <img class="profileBackground" ' + \ htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
'src="/users/' + nickname + '/image_' + theme + '.png" /></a>\n' 'src="/users/' + nickname + '/image_' + theme + '.png" /></a>\n'
htmlStr += ' <figcaption>\n' htmlStr += ' <figcaption>\n'
htmlStr += \ htmlStr += \
@ -330,7 +332,7 @@ def _getProfileHeader(baseDir: str, httpPrefix: str,
nickname + '/' + defaultTimeline + '" title="' + \ nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n' + \ translate['Switch to timeline view'] + '">\n' + \
' <img loading="lazy" src="' + avatarUrl + '" ' + \ ' <img loading="lazy" src="' + avatarUrl + '" ' + \
' class="title"></a>\n' 'alt="" class="title"></a>\n'
htmlStr += ' <h1>' + displayName + '</h1>\n' htmlStr += ' <h1>' + displayName + '</h1>\n'
htmlStr += \ htmlStr += \
' <p><b>@' + nickname + '@' + domainFull + '</b><br>\n' ' <p><b>@' + nickname + '@' + domainFull + '</b><br>\n'
@ -372,8 +374,8 @@ def _getProfileHeader(baseDir: str, httpPrefix: str,
' <a href="/users/' + nickname + \ ' <a href="/users/' + nickname + \
'/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \ '/qrcode.png" alt="' + translate['QR Code'] + '" title="' + \
translate['QR Code'] + '">' + \ translate['QR Code'] + '">' + \
'<img class="qrcode" src="/icons' + \ '<img class="qrcode" alt="' + translate['QR Code'] + \
'/qrcode.png" /></a></p>\n' '" src="/icons/qrcode.png" /></a></p>\n'
htmlStr += ' <p>' + profileDescriptionShort + '</p>\n' htmlStr += ' <p>' + profileDescriptionShort + '</p>\n'
htmlStr += loginButton htmlStr += loginButton
if pinnedContent: if pinnedContent:
@ -402,6 +404,7 @@ def _getProfileHeaderAfterSearch(baseDir: str,
nickname + '/' + defaultTimeline + '" title="' + \ nickname + '/' + defaultTimeline + '" title="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
htmlStr += ' <img class="profileBackground" ' + \ htmlStr += ' <img class="profileBackground" ' + \
'alt="" ' + \
'src="' + imageUrl + '" /></a>\n' 'src="' + imageUrl + '" /></a>\n'
htmlStr += ' <figcaption>\n' htmlStr += ' <figcaption>\n'
if avatarUrl: if avatarUrl:
@ -410,7 +413,7 @@ def _getProfileHeaderAfterSearch(baseDir: str,
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
htmlStr += \ htmlStr += \
' <img loading="lazy" src="' + avatarUrl + '" ' + \ ' <img loading="lazy" src="' + avatarUrl + '" ' + \
' class="title"></a>\n' 'alt="" class="title"></a>\n'
htmlStr += ' <h1>' + displayName + '</h1>\n' htmlStr += ' <h1>' + displayName + '</h1>\n'
htmlStr += \ htmlStr += \
' <p><b>@' + searchNickname + '@' + searchDomainFull + '</b><br>\n' ' <p><b>@' + searchNickname + '@' + searchDomainFull + '</b><br>\n'
@ -468,6 +471,7 @@ def htmlProfile(rssIconAtTop: bool,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
newswire: {}, theme: str, dormantMonths: int, newswire: {}, theme: str, dormantMonths: int,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool,
extraJson=None, pageNumber=None, extraJson=None, pageNumber=None,
maxItemsPerPage=None) -> str: maxItemsPerPage=None) -> str:
"""Show the profile page as html """Show the profile page as html
@ -487,6 +491,7 @@ def htmlProfile(rssIconAtTop: bool,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
newswire, theme, extraJson, newswire, theme, extraJson,
allowLocalNetworkAccess,
pageNumber, maxItemsPerPage) pageNumber, maxItemsPerPage)
domain, port = getDomainFromActor(profileJson['id']) domain, port = getDomainFromActor(profileJson['id'])
@ -702,7 +707,18 @@ def htmlProfile(rssIconAtTop: bool,
movedTo, alsoKnownAs, movedTo, alsoKnownAs,
pinnedContent) pinnedContent)
profileStr = profileHeaderStr + donateSection # Links for keyboard navigation
profileStr = \
'<div class="transparent">' + \
'<label class="transparent">' + \
'<a href="/users/' + nickname + '/' + defaultTimeline + '">' + \
translate['Switch to timeline view'] + '</a></label> | ' + \
'<label class="transparent">' + \
'<a class="skip-main" href="#buttonheader">' + \
translate['Skip to timeline'] + '</a></label>' + \
'</div>\n'
profileStr += profileHeaderStr + donateSection
profileStr += '<div class="container" id="buttonheader">\n' profileStr += '<div class="container" id="buttonheader">\n'
profileStr += ' <center>' profileStr += ' <center>'
profileStr += \ profileStr += \
@ -756,7 +772,8 @@ def htmlProfile(rssIconAtTop: bool,
projectVersion, projectVersion,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances) + licenseStr peertubeInstances,
allowLocalNetworkAccess) + licenseStr
elif selected == 'following': elif selected == 'following':
profileStr += \ profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix, _htmlProfileFollowing(translate, baseDir, httpPrefix,
@ -805,7 +822,8 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
projectVersion: str, projectVersion: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Shows posts on the profile screen """Shows posts on the profile screen
These should only be public posts These should only be public posts
""" """
@ -844,6 +862,7 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
False, False, False, True, False) False, False, False, True, False)
if postStr: if postStr:
profileStr += postStr + separatorStr profileStr += postStr + separatorStr

View File

@ -348,7 +348,7 @@ def htmlSearch(cssCache: {}, translate: {},
translate['Switch to timeline view'] + '" alt="' + \ translate['Switch to timeline view'] + '" alt="' + \
translate['Switch to timeline view'] + '">\n' translate['Switch to timeline view'] + '">\n'
followStr += '<img loading="lazy" class="timeline-banner" src="' + \ followStr += '<img loading="lazy" class="timeline-banner" src="' + \
usersPath + '/' + searchBannerFile + '" /></a>\n' + \ usersPath + '/' + searchBannerFile + '" alt="" /></a>\n' + \
'</header>\n' '</header>\n'
# show the search box # show the search box
@ -496,7 +496,7 @@ def htmlSkillsSearch(actor: str,
actor + '/skills">' actor + '/skills">'
skillSearchForm += \ skillSearchForm += \
'<img loading="lazy" src="' + avatarUrl + \ '<img loading="lazy" src="' + avatarUrl + \
'"/><span class="search-result-text">' + actorName + \ '" alt="" /><span class="search-result-text">' + actorName + \
'</span></a></div>' '</span></a></div>'
ctr += 1 ctr += 1
if ctr >= postsPerPage: if ctr >= postsPerPage:
@ -520,7 +520,8 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
port: int, port: int,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show a page containing search results for your post history """Show a page containing search results for your post history
""" """
if historysearch.startswith('!'): if historysearch.startswith('!'):
@ -596,6 +597,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
showIndividualPostIcons, showIndividualPostIcons,
showIndividualPostIcons, showIndividualPostIcons,
False, False, False) False, False, False)
@ -617,7 +619,8 @@ def htmlHashtagSearch(cssCache: {},
httpPrefix: str, projectVersion: str, httpPrefix: str, projectVersion: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show a page containing search results for a hashtag """Show a page containing search results for a hashtag
""" """
if hashtag.startswith('#'): if hashtag.startswith('#'):
@ -766,6 +769,7 @@ def htmlHashtagSearch(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
showRepeats, showIcons, showRepeats, showIcons,
manuallyApprovesFollowers, manuallyApprovesFollowers,
showPublicOnly, showPublicOnly,

View File

@ -63,7 +63,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
authorized: bool, authorized: bool,
moderationActionStr: str, moderationActionStr: str,
theme: str, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the timeline as html """Show the timeline as html
""" """
enableTimingLog = False enableTimingLog = False
@ -362,12 +363,25 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'<button class="button"><span>' + \ '<button class="button"><span>' + \
translate['Post'] + '</span></button></a>' translate['Post'] + '</span></button></a>'
# This creates a link to the profile page when viewed # This creates a link to skip to the timeline and change to
# in lynx, but should be invisible in a graphical web browser # profile view when accessed within lynx, but should be
# invisible in a graphical web browser
tlStr += \ tlStr += \
'<div class="transparent"><label class="transparent">' + \ '<div class="transparent">' + \
'<label class="transparent">' + \
'<a href="/users/' + nickname + '">' + \ '<a href="/users/' + nickname + '">' + \
translate['Switch to profile view'] + '</a></label></div>\n' translate['Switch to profile view'] + '</a></label> | ' + \
'<label class="transparent">' + \
'<a class="skip-main" href="' + usersPath + '/' + boxName + \
'#timeline">' + \
translate['Skip to timeline'] + '</a></label> | ' + \
'<label class="transparent">' + \
'<a class="skip-newswire" href="#newswire">' + \
translate['Skip to Newswire'] + '</a></label> | ' + \
'<label class="transparent">' + \
'<a class="skip-links" href="#links">' + \
translate['Skip to Links'] + '</a></label>' + \
'</div>\n'
# banner and row of buttons # banner and row of buttons
tlStr += \ tlStr += \
@ -375,8 +389,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'<a href="/users/' + nickname + '" title="' + \ '<a href="/users/' + nickname + '" title="' + \
translate['Switch to profile view'] + '" alt="' + \ translate['Switch to profile view'] + '" alt="' + \
translate['Switch to profile view'] + '">\n' translate['Switch to profile view'] + '">\n'
tlStr += '<img loading="lazy" class="timeline-banner" src="' + \ tlStr += '<img loading="lazy" class="timeline-banner" ' + \
usersPath + '/' + bannerFile + '" /></a>\n' + \ 'alt="" ' + \
'src="' + usersPath + '/' + bannerFile + '" /></a>\n' + \
'</header>\n' '</header>\n'
if fullWidthTimelineButtonHeader: if fullWidthTimelineButtonHeader:
@ -413,10 +428,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
httpPrefix, translate, httpPrefix, translate,
editor, False, None, rssIconAtTop, editor, False, None, rssIconAtTop,
True, False, theme) True, False, theme)
tlStr += ' <td valign="top" class="col-left">' + \ tlStr += ' <td valign="top" class="col-left" ' + \
'id="links" tabindex="-1">' + \
leftColumnStr + ' </td>\n' leftColumnStr + ' </td>\n'
# center column containing posts # center column containing posts
tlStr += ' <td valign="top" class="col-center">\n' tlStr += ' <td valign="top" class="col-center">\n'
tlStr += ' <main id="timeline" tabindex="-1">\n'
if not fullWidthTimelineButtonHeader: if not fullWidthTimelineButtonHeader:
tlStr += \ tlStr += \
@ -546,7 +563,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if recentPostsCache['html'].get(postId): if recentPostsCache['html'].get(postId):
currTlStr = recentPostsCache['html'][postId] currTlStr = recentPostsCache['html'][postId]
currTlStr = \ currTlStr = \
preparePostFromHtmlCache(currTlStr, preparePostFromHtmlCache(nickname,
currTlStr,
boxName, boxName,
pageNumber) pageNumber)
_logTimelineTiming(enableTimingLog, _logTimelineTiming(enableTimingLog,
@ -574,6 +592,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess,
boxName != 'dm', boxName != 'dm',
showIndividualPostIcons, showIndividualPostIcons,
manuallyApproveFollowers, manuallyApproveFollowers,
@ -616,7 +635,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
showPublishAsIcon, showPublishAsIcon,
rssIconAtTop, publishButtonAtTop, rssIconAtTop, publishButtonAtTop,
authorized, True, theme) authorized, True, theme)
tlStr += ' <td valign="top" class="col-right">' + \ tlStr += ' </main>\n'
tlStr += ' <td valign="top" class="col-right" ' + \
'id="newswire" tabindex="-1">' + \
rightColumnStr + ' </td>\n' rightColumnStr + ' </td>\n'
tlStr += ' </tr>\n' tlStr += ' </tr>\n'
@ -730,7 +751,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the shares timeline as html """Show the shares timeline as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -750,7 +772,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInbox(cssCache: {}, defaultTimeline: str, def htmlInbox(cssCache: {}, defaultTimeline: str,
@ -770,7 +793,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the inbox as html """Show the inbox as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -790,7 +814,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlBookmarks(cssCache: {}, defaultTimeline: str, def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@ -810,7 +835,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the bookmarks as html """Show the bookmarks as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -830,7 +856,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlEvents(cssCache: {}, defaultTimeline: str, def htmlEvents(cssCache: {}, defaultTimeline: str,
@ -850,7 +877,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the events as html """Show the events as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -870,7 +898,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str, def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@ -890,7 +919,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the DM timeline as html """Show the DM timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -905,7 +935,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
showPublishAsIcon, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str, def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@ -925,7 +956,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the replies timeline as html """Show the replies timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -941,7 +973,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str, def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@ -961,7 +994,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the media timeline as html """Show the media timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -977,7 +1011,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str, def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@ -997,7 +1032,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the blogs timeline as html """Show the blogs timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -1013,7 +1049,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str, def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@ -1034,7 +1071,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, authorized: bool,
theme: str, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the features timeline as html """Show the features timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -1050,7 +1088,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxNews(cssCache: {}, defaultTimeline: str, def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@ -1070,7 +1109,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the news timeline as html """Show the news timeline as html
""" """
return htmlTimeline(cssCache, defaultTimeline, return htmlTimeline(cssCache, defaultTimeline,
@ -1086,7 +1126,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon, positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlOutbox(cssCache: {}, defaultTimeline: str, def htmlOutbox(cssCache: {}, defaultTimeline: str,
@ -1106,7 +1147,8 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool, rssIconAtTop: bool,
publishButtonAtTop: bool, publishButtonAtTop: bool,
authorized: bool, theme: str, authorized: bool, theme: str,
peertubeInstances: []) -> str: peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the Outbox as html """Show the Outbox as html
""" """
manuallyApproveFollowers = \ manuallyApproveFollowers = \
@ -1123,4 +1165,5 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
newswire, False, False, positiveVoting, newswire, False, False, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader, showPublishAsIcon, fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop, iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances) authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)

View File

@ -591,24 +591,24 @@ def addEmojiToDisplayName(baseDir: str, httpPrefix: str,
displayName = displayName.replace('<p>', '').replace('</p>', '') displayName = displayName.replace('<p>', '').replace('</p>', '')
emojiTags = {} emojiTags = {}
print('TAG: displayName before tags: ' + displayName) # print('TAG: displayName before tags: ' + displayName)
displayName = \ displayName = \
addHtmlTags(baseDir, httpPrefix, addHtmlTags(baseDir, httpPrefix,
nickname, domain, displayName, [], emojiTags) nickname, domain, displayName, [], emojiTags)
displayName = displayName.replace('<p>', '').replace('</p>', '') displayName = displayName.replace('<p>', '').replace('</p>', '')
print('TAG: displayName after tags: ' + displayName) # print('TAG: displayName after tags: ' + displayName)
# convert the emoji dictionary to a list # convert the emoji dictionary to a list
emojiTagsList = [] emojiTagsList = []
for tagName, tag in emojiTags.items(): for tagName, tag in emojiTags.items():
emojiTagsList.append(tag) emojiTagsList.append(tag)
print('TAG: emoji tags list: ' + str(emojiTagsList)) # print('TAG: emoji tags list: ' + str(emojiTagsList))
if not inProfileName: if not inProfileName:
displayName = \ displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'post header') replaceEmojiFromTags(displayName, emojiTagsList, 'post header')
else: else:
displayName = \ displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'profile') replaceEmojiFromTags(displayName, emojiTagsList, 'profile')
print('TAG: displayName after tags 2: ' + displayName) # print('TAG: displayName after tags 2: ' + displayName)
# remove any stray emoji # remove any stray emoji
while ':' in displayName: while ':' in displayName:
@ -619,8 +619,8 @@ def addEmojiToDisplayName(baseDir: str, httpPrefix: str,
displayName = displayName.replace(':' + emojiStr + ':', '').strip() displayName = displayName.replace(':' + emojiStr + ':', '').strip()
if prevDisplayName == displayName: if prevDisplayName == displayName:
break break
print('TAG: displayName after tags 3: ' + displayName) # print('TAG: displayName after tags 3: ' + displayName)
print('TAG: displayName after tag replacements: ' + displayName) # print('TAG: displayName after tag replacements: ' + displayName)
return displayName return displayName
@ -838,8 +838,8 @@ def htmlPostSeparator(baseDir: str, column: str) -> str:
if os.path.isfile(separatorImageFilename): if os.path.isfile(separatorImageFilename):
separatorStr = \ separatorStr = \
'<div class="' + separatorClass + '"><center>' + \ '<div class="' + separatorClass + '"><center>' + \
'<img src="/icons/' + filename + '"/>' + \ '<img src="/icons/' + filename + '" ' + \
'</center></div>\n' 'alt="" /></center></div>\n'
return separatorStr return separatorStr

View File

@ -221,6 +221,7 @@ def webfingerLookup(path: str, baseDir: str,
handle = None handle = None
if 'resource=acct:' in path: if 'resource=acct:' in path:
handle = path.split('resource=acct:')[1].strip() handle = path.split('resource=acct:')[1].strip()
handle = urllib.parse.unquote(handle)
if debug: if debug:
print('DEBUG: WEBFINGER handle ' + handle) print('DEBUG: WEBFINGER handle ' + handle)
else: else: