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

merge-requests/30/head
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 fileLastModified
from utils import getLinkPrefixes
from utils import dangerousMarkup
from petnames import getPetName
def removeHtmlTag(htmlStr: str, tag: str) -> str:
@ -153,38 +155,6 @@ def htmlReplaceQuoteMarks(content: str) -> str:
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:
"""Returns true is the css file contains code which
can create security problems
@ -489,7 +459,7 @@ def tagExists(tagType: str, tagName: str, tags: {}) -> bool:
return False
def _addMention(wordStr: str, httpPrefix: str, following: str,
def _addMention(wordStr: str, httpPrefix: str, following: str, petnames: str,
replaceMentions: {}, recipients: [], tags: {}) -> bool:
"""Detects mentions and adds them to the replacements dict and
recipients list
@ -501,9 +471,12 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
# if no domain was specified. eg. @nick
possibleNickname = possibleHandle
for follow in following:
if follow.startswith(possibleNickname + '@'):
replaceDomain = \
follow.replace('\n', '').replace('\r', '').split('@')[1]
if '@' not in follow:
continue
followNick = follow.split('@')[0]
if possibleNickname == followNick:
followStr = follow.replace('\n', '').replace('\r', '')
replaceDomain = followStr.split('@')[1]
recipientActor = httpPrefix + "://" + \
replaceDomain + "/users/" + possibleNickname
if recipientActor not in recipients:
@ -519,6 +492,34 @@ def _addMention(wordStr: str, httpPrefix: str, following: str,
"\" class=\"u-url mention\">@<span>" + possibleNickname + \
"</span></a></span>"
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
possibleNickname = None
possibleDomain = None
@ -752,10 +753,14 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
# read the following list so that we can detect just @nick
# in addition to @nick@domain
following = None
petnames = None
if '@' in words:
if os.path.isfile(followingFilename):
with open(followingFilename, "r") as f:
following = f.readlines()
for handle in following:
pet = getPetName(baseDir, nickname, domain, handle)
petnames.append(pet + '\n')
# extract mentions and tags from words
longWordsList = []
@ -769,7 +774,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str,
longWordsList.append(wordStr)
firstChar = wordStr[0]
if firstChar == '@':
if _addMention(wordStr, httpPrefix, following,
if _addMention(wordStr, httpPrefix, following, petnames,
replaceMentions, recipients, hashtags):
prevWordStr = ''
continue

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@ from utils import locateNewsVotes
from utils import locateNewsArrival
from utils import votesOnNewswireItem
from utils import removeHtml
from utils import dangerousMarkup
from media import attachMedia
from media import replaceYouTube
from content import tagExists
@ -291,7 +292,13 @@ def getPersonBox(baseDir: str, session, wfRequest: {},
avatarUrl = personJson['icon']['url']
displayName = None
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?
if personJson.get('movedTo'):
displayName += ''
@ -1824,11 +1831,16 @@ def threadSendPost(session, postJsonStr: str, federationList: [],
for attempt in range(20):
postResult = None
unauthorized = False
if debug:
print('Getting postJsonString for ' + inboxUrl)
try:
postResult, unauthorized = \
postJsonString(session, postJsonStr, federationList,
inboxUrl, signatureHeaderJson,
debug)
if debug:
print('Obtained postJsonString for ' + inboxUrl +
' unauthorized: ' + str(unauthorized))
except Exception as e:
print('ERROR: postJsonString failed ' + str(e))
if unauthorized:
@ -2908,7 +2920,8 @@ def isDM(postJsonObject: {}) -> bool:
def isImageMedia(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str,
postJsonObject: {}, translate: {},
YTReplacementDomain: str) -> bool:
YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> bool:
"""Returns true if the given post has attached image media
"""
if postJsonObject['type'] == 'Announce':
@ -2916,7 +2929,8 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject,
__version__, translate,
YTReplacementDomain)
YTReplacementDomain,
allowLocalNetworkAccess)
if postJsonAnnounce:
postJsonObject = postJsonAnnounce
if postJsonObject['type'] != 'Create':
@ -3831,7 +3845,8 @@ def _rejectAnnounce(announceFilename: str):
def downloadAnnounce(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str,
postJsonObject: {}, projectVersion: str,
translate: {}, YTReplacementDomain: str) -> {}:
translate: {}, YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> {}:
"""Download the post referenced by an announce
"""
if not postJsonObject.get('object'):
@ -3911,20 +3926,16 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if '/statuses/' not in announcedJson['id']:
_rejectAnnounce(announceFilename)
return None
if '/users/' not in announcedJson['id'] and \
'/accounts/' not in announcedJson['id'] and \
'/channel/' not in announcedJson['id'] and \
'/profile/' not in announcedJson['id']:
if not hasUsersPath(announcedJson['id']):
_rejectAnnounce(announceFilename)
return None
if not announcedJson.get('type'):
_rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None
if announcedJson['type'] != 'Note' and \
announcedJson['type'] != 'Article':
# You can only announce Note or Article types
_rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None
if not announcedJson.get('content'):
_rejectAnnounce(announceFilename)
@ -3935,16 +3946,25 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
if not validPostDate(announcedJson['published']):
_rejectAnnounce(announceFilename)
return None
if isFiltered(baseDir, nickname, domain, announcedJson['content']):
# Check the content of the announce
contentStr = announcedJson['content']
if dangerousMarkup(contentStr, allowLocalNetworkAccess):
_rejectAnnounce(announceFilename)
return None
if isFiltered(baseDir, nickname, domain, contentStr):
_rejectAnnounce(announceFilename)
return None
# remove any long words
announcedJson['content'] = \
removeLongWords(announcedJson['content'], 40, [])
contentStr = removeLongWords(contentStr, 40, [])
# remove text formatting, such as bold/italics
announcedJson['content'] = \
removeTextFormatting(announcedJson['content'])
contentStr = removeTextFormatting(contentStr)
# set the content after santitization
announcedJson['content'] = contentStr
# wrap in create to be consistent with other posts
announcedJson = \
@ -3952,8 +3972,8 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str,
actorNickname, actorDomain, actorPort,
announcedJson)
if announcedJson['type'] != 'Create':
# Create wrap failed
_rejectAnnounce(announceFilename)
# pprint(announcedJson)
return None
# 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 getFollowersOfPerson
from utils import removeHtml
from utils import dangerousMarkup
from follow import followerOfPerson
from follow import unfollowAccount
from follow import unfollowerOfAccount
@ -77,7 +78,6 @@ from inbox import validInboxFilenames
from categories import guessHashtagCategory
from content import htmlReplaceEmailQuote
from content import htmlReplaceQuoteMarks
from content import dangerousMarkup
from content import dangerousCSS
from content import addWebLinks
from content import replaceEmojiFromTags
@ -95,6 +95,7 @@ from newswire import getNewswireTags
from newswire import parseFeedDate
from mastoapiv1 import getMastoApiV1IdFromNickname
from mastoapiv1 import getNicknameFromMastoApiV1Id
from webapp_post import prepareHtmlPostNickname
testServerAliceRunning = False
testServerBobRunning = False
@ -3072,9 +3073,25 @@ def testDomainHandling():
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():
print('Running tests...')
testFunctions()
testPrepareHtmlPostNickname()
testDomainHandling()
testMastoApi()
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",
"newswire-publish-icon": "True",
"full-width-timeline-buttons": "False",

View File

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

View File

@ -363,5 +363,8 @@
"Other accounts": "Altres comptes",
"Pin this post to your profile.": "Fixa aquesta publicació al teu perfil.",
"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",
"Pin this post to your profile.": "Piniwch y post hwn i'ch proffil.",
"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",
"Pin this post to your profile.": "Pin diesen Beitrag zu Ihrem Profil.",
"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",
"Login": "Login",
"Edit": "Edit",
"Switch to timeline view": "Switch to timeline view",
"Switch to timeline view": "Timeline view",
"Approve": "Approve",
"Deny": "Deny",
"Posts": "Posts",
@ -63,7 +63,7 @@
"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 DM": "Create a new DM",
"Switch to profile view": "Switch to profile view",
"Switch to profile view": "Profile view",
"Inbox": "Inbox",
"Outbox": "Outbox",
"Search and follow": "Search/follow",
@ -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"
"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",
"Pin this post to your profile.": "Fija esta publicación a tu perfil.",
"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",
"Pin this post to your profile.": "Épinglez ce message à votre profil.",
"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",
"Pin this post to your profile.": "Bioráin an post seo le do phróifíl.",
"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": "अन्य खाते",
"Pin this post to your profile.": "इस पोस्ट को अपनी प्रोफाइल पर पिन करें।",
"Administered by": "द्वारा प्रशासित",
"Version": "संस्करण"
"Version": "संस्करण",
"Skip to timeline": "टाइमलाइन पर जाएं",
"Skip to Newswire": "Newswire पर जाएं",
"Skip to Links": "वेब लिंक पर जाएं"
}

View File

@ -363,5 +363,8 @@
"Other accounts": "Altri account",
"Pin this post to your profile.": "Metti questo post sul tuo profilo.",
"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": "その他のアカウント",
"Pin this post to your profile.": "この投稿をプロフィールに固定します。",
"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",
"Pin this post to your profile.": "Pin this post to your profile.",
"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",
"Pin this post to your profile.": "Fixar esta postagem em seu perfil.",
"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": "Другие аккаунты",
"Pin this post to your profile.": "Закрепите это сообщение в своем профиле.",
"Administered by": "Под управлением",
"Version": "Версия"
"Version": "Версия",
"Skip to timeline": "Перейти к временной шкале",
"Skip to Newswire": "Перейти к ленте новостей",
"Skip to Links": "Перейти к веб-ссылкам"
}

View File

@ -363,5 +363,8 @@
"Other accounts": "其他账户",
"Pin this post to your profile.": "将此帖子固定到您的个人资料。",
"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
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:
"""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]
if not personCache.get(actor):
return None
nameFound = None
if personCache[actor].get('actor'):
if personCache[actor]['actor'].get('name'):
return personCache[actor]['actor']['name']
nameFound = personCache[actor]['actor']['name']
else:
# Try to obtain from the cached actors
cachedActorFilename = \
@ -572,8 +605,11 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str:
actorJson = loadJson(cachedActorFilename, 1)
if actorJson:
if actorJson.get('name'):
return(actorJson['name'])
return None
nameFound = actorJson['name']
if nameFound:
if dangerousMarkup(nameFound, False):
nameFound = "*ADVERSARY*"
return nameFound
def getNicknameFromActor(actor: str) -> str:
@ -1721,6 +1757,11 @@ def siteIsActive(url: str) -> bool:
"""
if not url.startswith('http'):
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:
req = urllib.request.Request(url)
urllib.request.urlopen(req, timeout=10) # nosec

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,7 +75,46 @@ def _logPostTiming(enableTimingLog: bool, postStartTime, debugId: str) -> None:
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:
"""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 = withPageNumber.replace('?page=-999',
'?page=' + str(pageNumber))
return withPageNumber
return prepareHtmlPostNickname(nickname, withPageNumber)
def _saveIndividualPostAsHtmlToCache(baseDir: str,
@ -173,7 +212,8 @@ def _getPostFromRecentCache(session,
if not postHtml:
return None
postHtml = preparePostFromHtmlCache(postHtml, boxName, pageNumber)
postHtml = \
preparePostFromHtmlCache(nickname, postHtml, boxName, pageNumber)
updateRecentPostsCache(recentPostsCache, maxRecentPosts,
postJsonObject, postHtml)
_logPostTiming(enableTimingLog, postStartTime, '3')
@ -1081,6 +1121,7 @@ def individualPostAsHtml(allowDownloads: bool,
boxName: str, YTReplacementDomain: str,
showPublishedDateOnly: bool,
peertubeInstances: [],
allowLocalNetworkAccess: bool,
showRepeats=True,
showIcons=False,
manuallyApprovesFollowers=False,
@ -1231,7 +1272,8 @@ def individualPostAsHtml(allowDownloads: bool,
downloadAnnounce(session, baseDir, httpPrefix,
nickname, domain, postJsonObject,
projectVersion, translate,
YTReplacementDomain)
YTReplacementDomain,
allowLocalNetworkAccess)
if not postJsonAnnounce:
return ''
postJsonObject = postJsonAnnounce
@ -1605,7 +1647,8 @@ def htmlIndividualPost(cssCache: {},
projectVersion: str, likedBy: str,
YTReplacementDomain: str,
showPublishedDateOnly: bool,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show an individual post as html
"""
postStr = ''
@ -1646,6 +1689,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances,
allowLocalNetworkAccess,
False, authorized, False, False, False)
messageId = removeIdEnding(postJsonObject['id'])
@ -1672,6 +1716,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances,
allowLocalNetworkAccess,
False, authorized,
False, False, False) + postStr
@ -1701,6 +1746,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances,
allowLocalNetworkAccess,
False, authorized,
False, False, False)
cssFilename = baseDir + '/epicyon-profile.css'
@ -1721,7 +1767,8 @@ def htmlPostReplies(cssCache: {},
httpPrefix: str, projectVersion: str,
YTReplacementDomain: str,
showPublishedDateOnly: bool,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the replies to an individual post as html
"""
repliesStr = ''
@ -1739,6 +1786,7 @@ def htmlPostReplies(cssCache: {},
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances,
allowLocalNetworkAccess,
False, False, False, False, False)
cssFilename = baseDir + '/epicyon-profile.css'

View File

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

View File

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

View File

@ -63,7 +63,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
authorized: bool,
moderationActionStr: str,
theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the timeline as html
"""
enableTimingLog = False
@ -362,12 +363,25 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'<button class="button"><span>' + \
translate['Post'] + '</span></button></a>'
# This creates a link to the profile page when viewed
# in lynx, but should be invisible in a graphical web browser
# This creates a link to skip to the timeline and change to
# profile view when accessed within lynx, but should be
# invisible in a graphical web browser
tlStr += \
'<div class="transparent"><label class="transparent">' + \
'<div class="transparent">' + \
'<label class="transparent">' + \
'<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
tlStr += \
@ -375,8 +389,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
'<a href="/users/' + nickname + '" title="' + \
translate['Switch to profile view'] + '" alt="' + \
translate['Switch to profile view'] + '">\n'
tlStr += '<img loading="lazy" class="timeline-banner" src="' + \
usersPath + '/' + bannerFile + '" /></a>\n' + \
tlStr += '<img loading="lazy" class="timeline-banner" ' + \
'alt="" ' + \
'src="' + usersPath + '/' + bannerFile + '" /></a>\n' + \
'</header>\n'
if fullWidthTimelineButtonHeader:
@ -413,10 +428,12 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
httpPrefix, translate,
editor, False, None, rssIconAtTop,
True, False, theme)
tlStr += ' <td valign="top" class="col-left">' + \
tlStr += ' <td valign="top" class="col-left" ' + \
'id="links" tabindex="-1">' + \
leftColumnStr + ' </td>\n'
# center column containing posts
tlStr += ' <td valign="top" class="col-center">\n'
tlStr += ' <main id="timeline" tabindex="-1">\n'
if not fullWidthTimelineButtonHeader:
tlStr += \
@ -546,7 +563,8 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
if recentPostsCache['html'].get(postId):
currTlStr = recentPostsCache['html'][postId]
currTlStr = \
preparePostFromHtmlCache(currTlStr,
preparePostFromHtmlCache(nickname,
currTlStr,
boxName,
pageNumber)
_logTimelineTiming(enableTimingLog,
@ -574,6 +592,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
YTReplacementDomain,
showPublishedDateOnly,
peertubeInstances,
allowLocalNetworkAccess,
boxName != 'dm',
showIndividualPostIcons,
manuallyApproveFollowers,
@ -616,7 +635,9 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
showPublishAsIcon,
rssIconAtTop, publishButtonAtTop,
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'
tlStr += ' </tr>\n'
@ -730,7 +751,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the shares timeline as html
"""
manuallyApproveFollowers = \
@ -750,7 +772,8 @@ def htmlShares(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInbox(cssCache: {}, defaultTimeline: str,
@ -770,7 +793,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the inbox as html
"""
manuallyApproveFollowers = \
@ -790,7 +814,8 @@ def htmlInbox(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlBookmarks(cssCache: {}, defaultTimeline: str,
@ -810,7 +835,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the bookmarks as html
"""
manuallyApproveFollowers = \
@ -830,7 +856,8 @@ def htmlBookmarks(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlEvents(cssCache: {}, defaultTimeline: str,
@ -850,7 +877,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the events as html
"""
manuallyApproveFollowers = \
@ -870,7 +898,8 @@ def htmlEvents(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
@ -890,7 +919,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the DM timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -905,7 +935,8 @@ def htmlInboxDMs(cssCache: {}, defaultTimeline: str,
showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
@ -925,7 +956,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the replies timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -941,7 +973,8 @@ def htmlInboxReplies(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
@ -961,7 +994,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the media timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -977,7 +1011,8 @@ def htmlInboxMedia(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
@ -997,7 +1032,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the blogs timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1013,7 +1049,8 @@ def htmlInboxBlogs(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
@ -1034,7 +1071,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
publishButtonAtTop: bool,
authorized: bool,
theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the features timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1050,7 +1088,8 @@ def htmlInboxFeatures(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlInboxNews(cssCache: {}, defaultTimeline: str,
@ -1070,7 +1109,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the news timeline as html
"""
return htmlTimeline(cssCache, defaultTimeline,
@ -1086,7 +1126,8 @@ def htmlInboxNews(cssCache: {}, defaultTimeline: str,
positiveVoting, showPublishAsIcon,
fullWidthTimelineButtonHeader,
iconsAsButtons, rssIconAtTop, publishButtonAtTop,
authorized, None, theme, peertubeInstances)
authorized, None, theme, peertubeInstances,
allowLocalNetworkAccess)
def htmlOutbox(cssCache: {}, defaultTimeline: str,
@ -1106,7 +1147,8 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
rssIconAtTop: bool,
publishButtonAtTop: bool,
authorized: bool, theme: str,
peertubeInstances: []) -> str:
peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str:
"""Show the Outbox as html
"""
manuallyApproveFollowers = \
@ -1123,4 +1165,5 @@ def htmlOutbox(cssCache: {}, defaultTimeline: str,
newswire, False, False, positiveVoting,
showPublishAsIcon, fullWidthTimelineButtonHeader,
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>', '')
emojiTags = {}
print('TAG: displayName before tags: ' + displayName)
# print('TAG: displayName before tags: ' + displayName)
displayName = \
addHtmlTags(baseDir, httpPrefix,
nickname, domain, displayName, [], emojiTags)
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
emojiTagsList = []
for tagName, tag in emojiTags.items():
emojiTagsList.append(tag)
print('TAG: emoji tags list: ' + str(emojiTagsList))
# print('TAG: emoji tags list: ' + str(emojiTagsList))
if not inProfileName:
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'post header')
else:
displayName = \
replaceEmojiFromTags(displayName, emojiTagsList, 'profile')
print('TAG: displayName after tags 2: ' + displayName)
# print('TAG: displayName after tags 2: ' + displayName)
# remove any stray emoji
while ':' in displayName:
@ -619,8 +619,8 @@ def addEmojiToDisplayName(baseDir: str, httpPrefix: str,
displayName = displayName.replace(':' + emojiStr + ':', '').strip()
if prevDisplayName == displayName:
break
print('TAG: displayName after tags 3: ' + displayName)
print('TAG: displayName after tag replacements: ' + displayName)
# print('TAG: displayName after tags 3: ' + displayName)
# print('TAG: displayName after tag replacements: ' + displayName)
return displayName
@ -838,8 +838,8 @@ def htmlPostSeparator(baseDir: str, column: str) -> str:
if os.path.isfile(separatorImageFilename):
separatorStr = \
'<div class="' + separatorClass + '"><center>' + \
'<img src="/icons/' + filename + '"/>' + \
'</center></div>\n'
'<img src="/icons/' + filename + '" ' + \
'alt="" /></center></div>\n'
return separatorStr

View File

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