mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon into main
commit
97ff118508
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
5
cache.py
5
cache.py
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
37
epicyon.py
37
epicyon.py
|
|
@ -1819,21 +1819,22 @@ if YTDomain:
|
|||
if setTheme(baseDir, themeName):
|
||||
print('Theme set to ' + themeName)
|
||||
|
||||
runDaemon(args.blogsinstance, args.mediainstance,
|
||||
args.maxRecentPosts,
|
||||
not args.nosharedinbox,
|
||||
registration, args.language, __version__,
|
||||
instanceId, args.client, baseDir,
|
||||
domain, onionDomain, i2pDomain,
|
||||
args.YTReplacementDomain,
|
||||
port, proxyPort, httpPrefix,
|
||||
federationList, args.maxMentions,
|
||||
args.maxEmoji, args.authenticatedFetch,
|
||||
args.noreply, args.nolike, args.nopics,
|
||||
args.noannounce, args.cw, ocapAlways,
|
||||
proxyType, args.maxReplies,
|
||||
args.domainMaxPostsPerDay,
|
||||
args.accountMaxPostsPerDay,
|
||||
args.allowdeletion, debug, False,
|
||||
args.instanceOnlySkillsSearch, [],
|
||||
args.blurhash, not args.noapproval)
|
||||
if __name__ == "__main__":
|
||||
runDaemon(args.blogsinstance, args.mediainstance,
|
||||
args.maxRecentPosts,
|
||||
not args.nosharedinbox,
|
||||
registration, args.language, __version__,
|
||||
instanceId, args.client, baseDir,
|
||||
domain, onionDomain, i2pDomain,
|
||||
args.YTReplacementDomain,
|
||||
port, proxyPort, httpPrefix,
|
||||
federationList, args.maxMentions,
|
||||
args.maxEmoji, args.authenticatedFetch,
|
||||
args.noreply, args.nolike, args.nopics,
|
||||
args.noannounce, args.cw, ocapAlways,
|
||||
proxyType, args.maxReplies,
|
||||
args.domainMaxPostsPerDay,
|
||||
args.accountMaxPostsPerDay,
|
||||
args.allowdeletion, debug, False,
|
||||
args.instanceOnlySkillsSearch, [],
|
||||
args.blurhash, not args.noapproval)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
33
inbox.py
33
inbox.py
|
|
@ -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,17 +2370,18 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int,
|
|||
if nickname != 'inbox':
|
||||
# replies index will be updated
|
||||
updateIndexList.append('tlreplies')
|
||||
inReplyTo = postJsonObject['object']['inReplyTo']
|
||||
if inReplyTo:
|
||||
if isinstance(inReplyTo, str):
|
||||
if not isMuted(baseDir, nickname, domain,
|
||||
inReplyTo):
|
||||
replyNotify(baseDir, handle,
|
||||
httpPrefix + '://' + domain +
|
||||
'/users/' + nickname +
|
||||
'/tlreplies')
|
||||
else:
|
||||
isReplyToMutedPost = True
|
||||
if postJsonObject['object'].get('inReplyTo'):
|
||||
inReplyTo = postJsonObject['object']['inReplyTo']
|
||||
if inReplyTo:
|
||||
if isinstance(inReplyTo, str):
|
||||
if not isMuted(baseDir, nickname, domain,
|
||||
inReplyTo):
|
||||
replyNotify(baseDir, handle,
|
||||
httpPrefix + '://' + domain +
|
||||
'/users/' + nickname +
|
||||
'/tlreplies')
|
||||
else:
|
||||
isReplyToMutedPost = True
|
||||
|
||||
if isImageMedia(session, baseDir, httpPrefix,
|
||||
nickname, domain, postJsonObject,
|
||||
|
|
|
|||
42
posts.py
42
posts.py
|
|
@ -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,
|
||||
|
|
|
|||
6
theme.py
6
theme.py
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
52
utils.py
52
utils.py
|
|
@ -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')
|
||||
|
||||
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')
|
||||
# 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)
|
||||
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: {},
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue