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

main
Bob Mottram 2020-09-03 11:22:16 +01:00
commit 97ff118508
13 changed files with 6927 additions and 5822 deletions

22
README_roadmap.md 100644
View File

@ -0,0 +1,22 @@
# Roadman
## UX
* Change animation on buttons (themeable?)
## Teams
* Test groups
* Groups can be defined as having particular roles/skills
* Templates for different group organizations
## Events
* Events timeline
* Events appear on calendar
* Check compatibility with Mobilizon
## Code
* Modularize daemon
* Move modules out of the daemon
* Make comment notes linking daemon functions to webinterface

View File

@ -40,7 +40,7 @@ def createAcceptReject(baseDir: str, federationList: [],
newAccept = {
"@context": "https://www.w3.org/ns/activitystreams",
'type': acceptType,
'actor': httpPrefix+'://'+domain+'/users/'+nickname,
'actor': httpPrefix+'://' + domain + '/users/' + nickname,
'to': [toUrl],
'cc': [],
'object': objectJson
@ -107,33 +107,33 @@ def acceptFollow(baseDir: str, domain: str, messageJson: {},
thisActor = messageJson['object']['actor']
nickname = getNicknameFromActor(thisActor)
if not nickname:
print('WARN: no nickname found in '+thisActor)
print('WARN: no nickname found in ' + thisActor)
return
acceptedDomain, acceptedPort = getDomainFromActor(thisActor)
if not acceptedDomain:
if debug:
print('DEBUG: domain not found in '+thisActor)
print('DEBUG: domain not found in ' + thisActor)
return
if not nickname:
if debug:
print('DEBUG: nickname not found in '+thisActor)
print('DEBUG: nickname not found in ' + thisActor)
return
if acceptedPort:
if '/' + acceptedDomain + ':' + str(acceptedPort) + \
'/users/' + nickname not in thisActor:
if debug:
print('Port: '+str(acceptedPort))
print('Port: ' + str(acceptedPort))
print('Expected: /' + acceptedDomain + ':' +
str(acceptedPort) + '/users/'+nickname)
print('Actual: '+thisActor)
print('DEBUG: unrecognized actor '+thisActor)
str(acceptedPort) + '/users/' + nickname)
print('Actual: ' + thisActor)
print('DEBUG: unrecognized actor ' + thisActor)
return
else:
if not '/' + acceptedDomain+'/users/' + nickname in thisActor:
if debug:
print('Expected: /'+acceptedDomain+'/users/'+nickname)
print('Actual: '+thisActor)
print('DEBUG: unrecognized actor '+thisActor)
print('Expected: /' + acceptedDomain+'/users/' + nickname)
print('Actual: ' + thisActor)
print('DEBUG: unrecognized actor ' + thisActor)
return
followedActor = messageJson['object']['object']
followedDomain, port = getDomainFromActor(followedActor)

View File

@ -45,8 +45,9 @@ def getPersonFromCache(baseDir: str, personUrl: str, personCache: {},
# does the person exist as a cached file?
cacheFilename = baseDir + '/cache/actors/' + \
personUrl.replace('/', '#')+'.json'
if os.path.isfile(getFileCaseInsensitive(cacheFilename)):
personJson = loadJson(getFileCaseInsensitive(cacheFilename))
actorFilename = getFileCaseInsensitive(cacheFilename)
if actorFilename:
personJson = loadJson(actorFilename)
if personJson:
storePersonInCache(baseDir, personUrl, personJson,
personCache, False)

12056
daemon.py

File diff suppressed because it is too large Load Diff

View File

@ -992,8 +992,8 @@ aside .toggle-inside li {
font-family: Arial, Helvetica, sans-serif;
padding: var(--button-height-padding);
width: 10%;
max-width: 100px;
min-width: 80px;
max-width: 200px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 5px;
@ -1009,7 +1009,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding);
width: 10%;
max-width: 100px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 5px;
@ -1025,7 +1025,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding);
width: 10%;
max-width: 100px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 5px;
@ -1041,7 +1041,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding);
width: 10%;
max-width: 100px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 5px;
@ -1432,7 +1432,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile);
width: 20%;
max-width: 400px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 15px;
@ -1448,7 +1448,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile);
width: 20%;
max-width: 400px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 15px;
@ -1464,7 +1464,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile);
width: 20%;
max-width: 400px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 15px;
@ -1480,7 +1480,7 @@ aside .toggle-inside li {
padding: var(--button-height-padding-mobile);
width: 20%;
max-width: 400px;
min-width: 80px;
min-width: 10ch;
transition: all 0.5s;
cursor: pointer;
margin: 15px;

View File

@ -1819,7 +1819,8 @@ if YTDomain:
if setTheme(baseDir, themeName):
print('Theme set to ' + themeName)
runDaemon(args.blogsinstance, args.mediainstance,
if __name__ == "__main__":
runDaemon(args.blogsinstance, args.mediainstance,
args.maxRecentPosts,
not args.nosharedinbox,
registration, args.language, __version__,

Binary file not shown.

View File

@ -8,6 +8,7 @@ Domestic_Manners by Dustin Norlander is under GPLv2. See https://www.1001fonts.c
Edition is public domain. See https://www.fontspace.com/edition-font-f35311
ElectrumADFExp-Regular is by Arkandis Digital Foundry under GPLv2. See https://www.1001fonts.com/electrum-adf-exp-font.html
GeneralFailureRegular is public domain. See https://www.fontspace.com/general-failure-font-f32565
JetBrains is Apache 2.0. See https://www.jetbrains.com/lp/mono/
Judges is under GPL. See https://webfonts.ffonts.net/Judges.font
LinBiolinum is under GPLv2. See https://www.1001fonts.com/linux-biolinum-font.html
LcdSolid is public domain. See https://www.fontspace.com/lcd-solid-font-f11346

View File

@ -87,7 +87,13 @@ def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
return
if not isinstance(postJsonObject['object']['tag'], list):
return
tagsDir = baseDir+'/tags'
tagsDir = baseDir + '/tags'
# add tags directory if it doesn't exist
if not os.path.isdir(tagsDir):
print('Creating tags directory')
os.mkdir(tagsDir)
for tag in postJsonObject['object']['tag']:
if not tag.get('type'):
continue
@ -134,7 +140,7 @@ def inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
avatarUrl = None
if boxname != 'tlevents' and boxname != 'outbox':
boxName = 'inbox'
individualPostAsHtml(recentPostsCache, maxRecentPosts,
individualPostAsHtml(True, recentPostsCache, maxRecentPosts,
getIconsDir(baseDir), translate, pageNumber,
baseDir, session, cachedWebfingers, personCache,
nickname, domain, port, postJsonObject,
@ -2364,6 +2370,7 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int,
if nickname != 'inbox':
# replies index will be updated
updateIndexList.append('tlreplies')
if postJsonObject['object'].get('inReplyTo'):
inReplyTo = postJsonObject['object']['inReplyTo']
if inReplyTo:
if isinstance(inReplyTo, str):

View File

@ -31,7 +31,6 @@ from webfinger import webfingerHandle
from httpsig import createSignedHeader
from utils import removeIdEnding
from utils import siteIsActive
from utils import removePostFromCache
from utils import getCachedPostFilename
from utils import getStatusNumber
from utils import createPersonDir
@ -3535,19 +3534,20 @@ def mutePost(baseDir: str, nickname: str, domain: str, postId: str,
if not postJsonObject:
return
print('MUTE: ' + postFilename)
muteFile = open(postFilename + '.muted', 'w+')
if muteFile:
muteFile.write('\n')
muteFile.close()
# remove cached posts so that the muted version gets created
# remove cached post so that the muted version gets recreated
# without its content text and/or image
cachedPostFilename = \
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
os.remove(cachedPostFilename)
muteFile = open(postFilename + '.muted', 'w+')
if muteFile:
muteFile.write('\n')
muteFile.close()
print('MUTE: ' + postFilename + '.muted file added')
# if the post is in the recent posts cache then mark it as muted
if recentPostsCache.get('index'):
postId = \
@ -3557,8 +3557,11 @@ def mutePost(baseDir: str, nickname: str, domain: str, postId: str,
if recentPostsCache['json'].get(postId):
postJsonObject['muted'] = True
recentPostsCache['json'][postId] = json.dumps(postJsonObject)
if recentPostsCache.get('html'):
if recentPostsCache['html'].get(postId):
del recentPostsCache['html'][postId]
print('MUTE: ' + postId +
' marked as muted in recent posts cache')
' marked as muted in recent posts memory cache')
def unmutePost(baseDir: str, nickname: str, domain: str, postId: str,
@ -3572,18 +3575,33 @@ def unmutePost(baseDir: str, nickname: str, domain: str, postId: str,
if not postJsonObject:
return
print('UNMUTE: ' + postFilename)
muteFilename = postFilename + '.muted'
if os.path.isfile(muteFilename):
os.remove(muteFilename)
print('UNMUTE: ' + muteFilename + ' file removed')
# remove cached posts so that it gets recreated
# remove cached post so that the muted version gets recreated
# with its content text and/or image
cachedPostFilename = \
getCachedPostFilename(baseDir, nickname, domain, postJsonObject)
if cachedPostFilename:
if os.path.isfile(cachedPostFilename):
os.remove(cachedPostFilename)
removePostFromCache(postJsonObject, recentPostsCache)
# if the post is in the recent posts cache then mark it as unmuted
if recentPostsCache.get('index'):
postId = \
removeIdEnding(postJsonObject['id']).replace('/', '#')
if postId in recentPostsCache['index']:
print('UNMUTE: ' + postId + ' is in recent posts cache')
if recentPostsCache['json'].get(postId):
postJsonObject['muted'] = False
recentPostsCache['json'][postId] = json.dumps(postJsonObject)
if recentPostsCache.get('html'):
if recentPostsCache['html'].get(postId):
del recentPostsCache['html'][postId]
print('UNMUTE: ' + postId +
' marked as unmuted in recent posts cache')
def sendBlockViaServer(baseDir: str, session,

View File

@ -223,15 +223,15 @@ def setThemeDefault(baseDir: str):
name = 'default'
removeTheme(baseDir)
setThemeInConfig(baseDir, name)
themeParams = {
"dummyValue": "1234"
}
bgParams = {
"login": "jpg",
"follow": "jpg",
"options": "jpg",
"search": "jpg"
}
themeParams = {
"dummy": "1234"
}
setThemeFromDict(baseDir, name, themeParams, bgParams)

View File

@ -365,26 +365,30 @@ def followPerson(baseDir: str, nickname: str, domain: str,
return True
# prepend to follow file
try:
with open(filename, 'r+') as followFile:
content = followFile.read()
followFile.seek(0, 0)
followFile.write(handleToFollow + '\n' + content)
with open(filename, 'r+') as f:
content = f.read()
f.seek(0, 0)
f.write(handleToFollow + '\n' + content)
if debug:
print('DEBUG: follow added')
return True
except Exception as e:
print('WARN: Failed to write entry to follow file ' +
filename + ' ' + str(e))
else:
# first follow
if debug:
print('DEBUG: creating new following file to follow ' +
handleToFollow)
with open(filename, 'w+') as f:
f.write(handleToFollow + '\n')
# Default to adding new follows to the calendar.
# Possibly this could be made optional
if followFile == 'following.txt':
# if following a person add them to the list of
# calendar follows
addPersonToCalendar(baseDir, nickname, domain,
followNickname, followDomain)
if debug:
print('DEBUG: creating new following file to follow ' + handleToFollow)
with open(filename, 'w+') as followfile:
followfile.write(handleToFollow + '\n')
return True
@ -621,7 +625,7 @@ def validNickname(domain: str, nickname: str) -> bool:
'likes', 'users', 'statuses',
'accounts', 'channels', 'profile',
'updates', 'repeat', 'announce',
'shares', 'fonts', 'icons')
'shares', 'fonts', 'icons', 'avatars')
if nickname in reservedNames:
return False
return True
@ -907,21 +911,19 @@ def searchBoxPosts(baseDir: str, nickname: str, domain: str,
def getFileCaseInsensitive(path: str) -> str:
"""Returns a case specific filename given a case insensitive version of it
"""
# does the given file exist? If so then we don't need
# to do a directory search
if os.path.isfile(path):
return path
if path != path.lower():
if os.path.isfile(path.lower()):
return path.lower()
directory, filename = os.path.split(path)
directory, filename = (directory or '.'), filename.lower()
for f in os.listdir(directory):
if f.lower() == filename:
newpath = os.path.join(directory, f)
if os.path.isfile(newpath):
return newpath
return path
# directory, filename = os.path.split(path)
# directory, filename = (directory or '.'), filename.lower()
# for f in os.listdir(directory):
# if f.lower() == filename:
# newpath = os.path.join(directory, f)
# if os.path.isfile(newpath):
# return newpath
return None
def undoLikesCollectionEntry(recentPostsCache: {},

View File

@ -27,7 +27,6 @@ from matrix import getMatrixAddress
from donate import getDonationUrl
from utils import removeIdEnding
from utils import getProtocolPrefixes
from utils import getFileCaseInsensitive
from utils import searchBoxPosts
from utils import isEventPost
from utils import isBlogPost
@ -304,16 +303,13 @@ def getPersonAvatarUrl(baseDir: str, personUrl: str, personCache: {},
# get from locally stored image
actorStr = personJson['id'].replace('/', '-')
avatarImagePath = baseDir + '/cache/avatars/' + actorStr
if os.path.isfile(getFileCaseInsensitive(avatarImagePath + '.png')):
return '/avatars/' + actorStr + '.png'
elif os.path.isfile(getFileCaseInsensitive(avatarImagePath + '.jpg')):
return '/avatars/' + actorStr + '.jpg'
elif os.path.isfile(getFileCaseInsensitive(avatarImagePath + '.gif')):
return '/avatars/' + actorStr + '.gif'
elif os.path.isfile(getFileCaseInsensitive(avatarImagePath + '.webp')):
return '/avatars/' + actorStr + '.webp'
elif os.path.isfile(getFileCaseInsensitive(avatarImagePath)):
return '/avatars/' + actorStr
imageExtension = ('png', 'jpg', 'jpeg', 'gif', 'webp')
for ext in imageExtension:
if os.path.isfile(avatarImagePath + '.' + ext):
return '/avatars/' + actorStr + '.' + ext
elif os.path.isfile(avatarImagePath.lower() + '.' + ext):
return '/avatars/' + actorStr.lower() + '.' + ext
if personJson.get('icon'):
if personJson['icon'].get('url'):
@ -3812,6 +3808,9 @@ def individualPostAsHtml(allowDownloads: bool,
storeToCache=True) -> str:
""" Shows a single post as html
"""
if not postJsonObject:
return ''
# benchmark
postStartTime = time.time()