mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of ssh://code.freedombone.net:2222/bashrc/epicyon
commit
b1e6354e0c
50
content.py
50
content.py
|
|
@ -202,35 +202,35 @@ def dangerousCSS(filename: str, allowLocalNetworkAccess: bool) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def switchWords(baseDir: str, nickname: str, domain: str, content: str) -> str:
|
||||
def switchWords(baseDir: str, nickname: str, domain: str, content: str,
|
||||
rules: [] = []) -> str:
|
||||
"""Performs word replacements. eg. Trump -> The Orange Menace
|
||||
"""
|
||||
if isPGPEncrypted(content) or containsPGPPublicKey(content):
|
||||
return content
|
||||
switchWordsFilename = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/replacewords.txt'
|
||||
if not os.path.isfile(switchWordsFilename):
|
||||
return content
|
||||
with open(switchWordsFilename, 'r') as fp:
|
||||
for line in fp:
|
||||
replaceStr = line.replace('\n', '').replace('\r', '')
|
||||
wordTransform = None
|
||||
if '->' in replaceStr:
|
||||
wordTransform = replaceStr.split('->')
|
||||
elif ':' in replaceStr:
|
||||
wordTransform = replaceStr.split(':')
|
||||
elif ',' in replaceStr:
|
||||
wordTransform = replaceStr.split(',')
|
||||
elif ';' in replaceStr:
|
||||
wordTransform = replaceStr.split(';')
|
||||
elif '-' in replaceStr:
|
||||
wordTransform = replaceStr.split('-')
|
||||
if not wordTransform:
|
||||
continue
|
||||
if len(wordTransform) == 2:
|
||||
replaceStr1 = wordTransform[0].strip().replace('"', '')
|
||||
replaceStr2 = wordTransform[1].strip().replace('"', '')
|
||||
content = content.replace(replaceStr1, replaceStr2)
|
||||
|
||||
if not rules:
|
||||
switchWordsFilename = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/replacewords.txt'
|
||||
if not os.path.isfile(switchWordsFilename):
|
||||
return content
|
||||
with open(switchWordsFilename, 'r') as fp:
|
||||
rules = fp.readlines()
|
||||
|
||||
for line in rules:
|
||||
replaceStr = line.replace('\n', '').replace('\r', '')
|
||||
splitters = ('->', ':', ',', ';', '-')
|
||||
wordTransform = None
|
||||
for splitStr in splitters:
|
||||
if splitStr in replaceStr:
|
||||
wordTransform = replaceStr.split(splitStr)
|
||||
break
|
||||
if not wordTransform:
|
||||
continue
|
||||
if len(wordTransform) == 2:
|
||||
replaceStr1 = wordTransform[0].strip().replace('"', '')
|
||||
replaceStr2 = wordTransform[1].strip().replace('"', '')
|
||||
content = content.replace(replaceStr1, replaceStr2)
|
||||
return content
|
||||
|
||||
|
||||
|
|
|
|||
393
daemon.py
393
daemon.py
|
|
@ -286,6 +286,8 @@ from bookmarks import undoBookmark
|
|||
from petnames import setPetName
|
||||
from followingCalendar import addPersonToCalendar
|
||||
from followingCalendar import removePersonFromCalendar
|
||||
from notifyOnPost import addNotifyOnPost
|
||||
from notifyOnPost import removeNotifyOnPost
|
||||
from devices import E2EEdevicesCollection
|
||||
from devices import E2EEvalidDevice
|
||||
from devices import E2EEaddDevice
|
||||
|
|
@ -2087,6 +2089,34 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# person options screen, on notify checkbox
|
||||
# See htmlPersonOptions
|
||||
if '&submitNotifyOnPost=' in optionsConfirmParams:
|
||||
notify = None
|
||||
if 'notifyOnPost=' in optionsConfirmParams:
|
||||
notify = optionsConfirmParams.split('notifyOnPost=')[1]
|
||||
if '&' in notify:
|
||||
notify = notify.split('&')[0]
|
||||
if notify == 'on':
|
||||
addNotifyOnPost(baseDir,
|
||||
chooserNickname,
|
||||
domain,
|
||||
optionsNickname,
|
||||
optionsDomainFull)
|
||||
else:
|
||||
removeNotifyOnPost(baseDir,
|
||||
chooserNickname,
|
||||
domain,
|
||||
optionsNickname,
|
||||
optionsDomainFull)
|
||||
usersPathStr = \
|
||||
usersPath + '/' + self.server.defaultTimeline + \
|
||||
'?page=' + str(pageNumber)
|
||||
self._redirect_headers(usersPathStr, cookie,
|
||||
callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# person options screen, permission to post to newswire
|
||||
# See htmlPersonOptions
|
||||
if '&submitPostToNews=' in optionsConfirmParams:
|
||||
|
|
@ -7670,111 +7700,106 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if '/' not in namedStatus:
|
||||
# show actor
|
||||
nickname = namedStatus
|
||||
return False
|
||||
|
||||
postSections = namedStatus.split('/')
|
||||
if len(postSections) != 2:
|
||||
return False
|
||||
nickname = postSections[0]
|
||||
statusNumber = postSections[1]
|
||||
if len(statusNumber) <= 10 or not statusNumber.isdigit():
|
||||
return False
|
||||
|
||||
postFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \
|
||||
httpPrefix + ':##' + domainFull + '#users#' + nickname + \
|
||||
'#statuses#' + statusNumber + '.json'
|
||||
|
||||
return self._showPostFromFile(postFilename, likedBy,
|
||||
authorized, callingDomain, path,
|
||||
baseDir, httpPrefix, nickname,
|
||||
domain, domainFull, port,
|
||||
onionDomain, i2pDomain,
|
||||
GETstartTime, GETtimings,
|
||||
proxyType, cookie, debug)
|
||||
|
||||
def _showPostFromFile(self, postFilename: str, likedBy: str,
|
||||
authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str, nickname: str,
|
||||
domain: str, domainFull: str, port: int,
|
||||
onionDomain: str, i2pDomain: str,
|
||||
GETstartTime, GETtimings: {},
|
||||
proxyType: str, cookie: str,
|
||||
debug: str) -> bool:
|
||||
"""Shows an individual post from its filename
|
||||
"""
|
||||
if not os.path.isfile(postFilename):
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if not postJsonObject:
|
||||
self.send_response(429)
|
||||
self.end_headers()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
|
||||
# Only authorized viewers get to see likes on posts
|
||||
# Otherwize marketers could gain more social graph info
|
||||
if not authorized:
|
||||
pjo = postJsonObject
|
||||
if not isPublicPost(pjo):
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
removePostInteractions(pjo, True)
|
||||
if self._requestHTTP():
|
||||
msg = \
|
||||
htmlIndividualPost(self.server.cssCache,
|
||||
self.server.recentPostsCache,
|
||||
self.server.maxRecentPosts,
|
||||
self.server.translate,
|
||||
baseDir,
|
||||
self.server.session,
|
||||
self.server.cachedWebfingers,
|
||||
self.server.personCache,
|
||||
nickname, domain, port,
|
||||
authorized,
|
||||
postJsonObject,
|
||||
httpPrefix,
|
||||
self.server.projectVersion,
|
||||
likedBy,
|
||||
self.server.YTReplacementDomain,
|
||||
self.server.showPublishedDateOnly,
|
||||
self.server.peertubeInstances,
|
||||
self.server.allowLocalNetworkAccess,
|
||||
self.server.themeName)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime,
|
||||
GETtimings,
|
||||
'show skills ' +
|
||||
'done',
|
||||
'show status')
|
||||
else:
|
||||
postSections = namedStatus.split('/')
|
||||
if len(postSections) == 2:
|
||||
nickname = postSections[0]
|
||||
statusNumber = postSections[1]
|
||||
if len(statusNumber) > 10 and statusNumber.isdigit():
|
||||
postFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + \
|
||||
domain + '/outbox/' + \
|
||||
httpPrefix + ':##' + \
|
||||
domainFull + '#users#' + \
|
||||
nickname + '#statuses#' + \
|
||||
statusNumber + '.json'
|
||||
if os.path.isfile(postFilename):
|
||||
postJsonObject = loadJson(postFilename)
|
||||
loadedPost = False
|
||||
if postJsonObject:
|
||||
loadedPost = True
|
||||
else:
|
||||
postJsonObject = {}
|
||||
if loadedPost:
|
||||
# Only authorized viewers get to see likes
|
||||
# on posts. Otherwize marketers could gain
|
||||
# more social graph info
|
||||
if not authorized:
|
||||
pjo = postJsonObject
|
||||
if not isPublicPost(pjo):
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
removePostInteractions(pjo, True)
|
||||
if self._requestHTTP():
|
||||
recentPostsCache = \
|
||||
self.server.recentPostsCache
|
||||
maxRecentPosts = \
|
||||
self.server.maxRecentPosts
|
||||
translate = \
|
||||
self.server.translate
|
||||
cachedWebfingers = \
|
||||
self.server.cachedWebfingers
|
||||
personCache = \
|
||||
self.server.personCache
|
||||
projectVersion = \
|
||||
self.server.projectVersion
|
||||
ytDomain = \
|
||||
self.server.YTReplacementDomain
|
||||
showPublishedDateOnly = \
|
||||
self.server.showPublishedDateOnly
|
||||
peertubeInstances = \
|
||||
self.server.peertubeInstances
|
||||
cssCache = self.server.cssCache
|
||||
allowLocalNetworkAccess = \
|
||||
self.server.allowLocalNetworkAccess
|
||||
themeName = \
|
||||
self.server.themeName
|
||||
msg = \
|
||||
htmlIndividualPost(cssCache,
|
||||
recentPostsCache,
|
||||
maxRecentPosts,
|
||||
translate,
|
||||
self.server.baseDir,
|
||||
self.server.session,
|
||||
cachedWebfingers,
|
||||
personCache,
|
||||
nickname,
|
||||
domain,
|
||||
port,
|
||||
authorized,
|
||||
postJsonObject,
|
||||
httpPrefix,
|
||||
projectVersion,
|
||||
likedBy,
|
||||
ytDomain,
|
||||
showPublishedDateOnly,
|
||||
peertubeInstances,
|
||||
allowLocalNetworkAccess,
|
||||
themeName)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
if self._fetchAuthenticated():
|
||||
msg = json.dumps(postJsonObject,
|
||||
ensure_ascii=False)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json',
|
||||
msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
self._benchmarkGETtimings(GETstartTime, GETtimings,
|
||||
'new post done',
|
||||
'individual post shown')
|
||||
return True
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
if self._fetchAuthenticated():
|
||||
msg = json.dumps(postJsonObject,
|
||||
ensure_ascii=False)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json',
|
||||
msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
|
||||
def _showIndividualPost(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
|
|
@ -7802,108 +7827,51 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
statusNumber = postSections[2]
|
||||
if len(statusNumber) <= 10 or (not statusNumber.isdigit()):
|
||||
return False
|
||||
postFilename = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + \
|
||||
domain + '/outbox/' + \
|
||||
httpPrefix + ':##' + \
|
||||
domainFull + '#users#' + \
|
||||
nickname + '#statuses#' + \
|
||||
statusNumber + '.json'
|
||||
if os.path.isfile(postFilename):
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if not postJsonObject:
|
||||
self.send_response(429)
|
||||
self.end_headers()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
else:
|
||||
# Only authorized viewers get to see likes
|
||||
# on posts
|
||||
# Otherwize marketers could gain more social
|
||||
# graph info
|
||||
if not authorized:
|
||||
pjo = postJsonObject
|
||||
if not isPublicPost(pjo):
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
removePostInteractions(pjo, True)
|
||||
|
||||
if self._requestHTTP():
|
||||
recentPostsCache = \
|
||||
self.server.recentPostsCache
|
||||
maxRecentPosts = \
|
||||
self.server.maxRecentPosts
|
||||
translate = \
|
||||
self.server.translate
|
||||
cachedWebfingers = \
|
||||
self.server.cachedWebfingers
|
||||
personCache = \
|
||||
self.server.personCache
|
||||
projectVersion = \
|
||||
self.server.projectVersion
|
||||
ytDomain = \
|
||||
self.server.YTReplacementDomain
|
||||
showPublishedDateOnly = \
|
||||
self.server.showPublishedDateOnly
|
||||
peertubeInstances = \
|
||||
self.server.peertubeInstances
|
||||
allowLocalNetworkAccess = \
|
||||
self.server.allowLocalNetworkAccess
|
||||
themeName = \
|
||||
self.server.themeName
|
||||
msg = \
|
||||
htmlIndividualPost(self.server.cssCache,
|
||||
recentPostsCache,
|
||||
maxRecentPosts,
|
||||
translate,
|
||||
baseDir,
|
||||
self.server.session,
|
||||
cachedWebfingers,
|
||||
personCache,
|
||||
nickname,
|
||||
domain,
|
||||
port,
|
||||
authorized,
|
||||
postJsonObject,
|
||||
httpPrefix,
|
||||
projectVersion,
|
||||
likedBy,
|
||||
ytDomain,
|
||||
showPublishedDateOnly,
|
||||
peertubeInstances,
|
||||
allowLocalNetworkAccess,
|
||||
themeName)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('text/html', msglen,
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
self._benchmarkGETtimings(GETstartTime,
|
||||
GETtimings,
|
||||
'show skills ' +
|
||||
'done',
|
||||
'show status')
|
||||
else:
|
||||
if self._fetchAuthenticated():
|
||||
msg = json.dumps(postJsonObject,
|
||||
ensure_ascii=False)
|
||||
msg = msg.encode('utf-8')
|
||||
msglen = len(msg)
|
||||
self._set_headers('application/json',
|
||||
msglen,
|
||||
None, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
postFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '/outbox/' + \
|
||||
httpPrefix + ':##' + domainFull + '#users#' + nickname + \
|
||||
'#statuses#' + statusNumber + '.json'
|
||||
|
||||
return self._showPostFromFile(postFilename, likedBy,
|
||||
authorized, callingDomain, path,
|
||||
baseDir, httpPrefix, nickname,
|
||||
domain, domainFull, port,
|
||||
onionDomain, i2pDomain,
|
||||
GETstartTime, GETtimings,
|
||||
proxyType, cookie, debug)
|
||||
|
||||
def _showNotifyPost(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
domain: str, domainFull: str, port: int,
|
||||
onionDomain: str, i2pDomain: str,
|
||||
GETstartTime, GETtimings: {},
|
||||
proxyType: str, cookie: str,
|
||||
debug: str) -> bool:
|
||||
"""Shows an individual post from an account which you are following
|
||||
and where you have the notify checkbox set on person options
|
||||
"""
|
||||
likedBy = None
|
||||
postId = path.split('?notifypost=')[1].strip()
|
||||
postId = postId.replace('-', '/')
|
||||
path = path.split('?notifypost=')[0]
|
||||
nickname = path.split('/users/')[1]
|
||||
if '/' in nickname:
|
||||
return False
|
||||
replies = False
|
||||
|
||||
postFilename = locatePost(baseDir, nickname, domain, postId, replies)
|
||||
if not postFilename:
|
||||
return False
|
||||
|
||||
return self._showPostFromFile(postFilename, likedBy,
|
||||
authorized, callingDomain, path,
|
||||
baseDir, httpPrefix, nickname,
|
||||
domain, domainFull, port,
|
||||
onionDomain, i2pDomain,
|
||||
GETstartTime, GETtimings,
|
||||
proxyType, cookie, debug)
|
||||
|
||||
def _showInbox(self, authorized: bool,
|
||||
callingDomain: str, path: str,
|
||||
|
|
@ -12483,6 +12451,21 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'post roles done',
|
||||
'show skills done')
|
||||
|
||||
if '?notifypost=' in self.path and usersInPath and authorized:
|
||||
if self._showNotifyPost(authorized,
|
||||
callingDomain, self.path,
|
||||
self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.domainFull,
|
||||
self.server.port,
|
||||
self.server.onionDomain,
|
||||
self.server.i2pDomain,
|
||||
GETstartTime, GETtimings,
|
||||
self.server.proxyType,
|
||||
cookie, self.server.debug):
|
||||
return
|
||||
|
||||
# get an individual post from the path
|
||||
# /users/nickname/statuses/number
|
||||
if '/statuses/' in self.path and usersInPath:
|
||||
|
|
|
|||
45
inbox.py
45
inbox.py
|
|
@ -86,6 +86,7 @@ from categories import guessHashtagCategory
|
|||
from context import hasValidContext
|
||||
from speaker import updateSpeaker
|
||||
from announce import isSelfAnnounce
|
||||
from notifyOnPost import notifyWhenPersonPosts
|
||||
|
||||
|
||||
def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None:
|
||||
|
|
@ -1850,6 +1851,25 @@ def _likeNotify(baseDir: str, domain: str, onionDomain: str,
|
|||
pass
|
||||
|
||||
|
||||
def _notifyPostArrival(baseDir: str, handle: str, url: str) -> None:
|
||||
"""Creates a notification that a new post has arrived.
|
||||
This is for followed accounts with the notify checkbox enabled
|
||||
on the person options screen
|
||||
"""
|
||||
accountDir = baseDir + '/accounts/' + handle
|
||||
if not os.path.isdir(accountDir):
|
||||
return
|
||||
notifyFile = accountDir + '/.newNotifiedPost'
|
||||
if os.path.isfile(notifyFile):
|
||||
# check that the same notification is not repeatedly sent
|
||||
with open(notifyFile, 'r') as fp:
|
||||
existingNotificationMessage = fp.read()
|
||||
if url in existingNotificationMessage:
|
||||
return
|
||||
with open(notifyFile, 'w+') as fp:
|
||||
fp.write(url)
|
||||
|
||||
|
||||
def _replyNotify(baseDir: str, handle: str, url: str) -> None:
|
||||
"""Creates a notification that a new reply has arrived
|
||||
"""
|
||||
|
|
@ -2275,6 +2295,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
|
||||
_updateLastSeen(baseDir, handle, actor)
|
||||
|
||||
postIsDM = False
|
||||
isGroup = _groupHandle(baseDir, handle)
|
||||
|
||||
if _receiveLike(recentPostsCache,
|
||||
|
|
@ -2392,6 +2413,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
postJsonObject = messageJson
|
||||
|
||||
nickname = handle.split('@')[0]
|
||||
jsonObj = None
|
||||
if _validPostContent(baseDir, nickname, domain,
|
||||
postJsonObject, maxMentions, maxEmoji,
|
||||
allowLocalNetworkAccess, debug):
|
||||
|
|
@ -2473,8 +2495,7 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
|
||||
# get the actor being replied to
|
||||
domainFull = getFullDomain(domain, port)
|
||||
actor = httpPrefix + '://' + domainFull + \
|
||||
'/users/' + handle.split('@')[0]
|
||||
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
|
||||
|
||||
# create a reply notification file if needed
|
||||
if not postIsDM and isReply(postJsonObject, actor):
|
||||
|
|
@ -2512,6 +2533,26 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
|
|||
|
||||
# save the post to file
|
||||
if saveJson(postJsonObject, destinationFilename):
|
||||
# should we notify that a post from this person has arrived?
|
||||
# This is for cases where the notify checkbox is enabled
|
||||
# on the person options screen
|
||||
if not postIsDM and jsonObj:
|
||||
if jsonObj.get('attributedTo') and jsonObj.get('id'):
|
||||
attributedTo = jsonObj['attributedTo']
|
||||
if isinstance(attributedTo, str):
|
||||
fromNickname = getNicknameFromActor(attributedTo)
|
||||
fromDomain, fromPort = getDomainFromActor(attributedTo)
|
||||
fromDomainFull = getFullDomain(fromDomain, fromPort)
|
||||
if notifyWhenPersonPosts(baseDir, nickname, domain,
|
||||
fromNickname, fromDomainFull):
|
||||
postId = removeIdEnding(jsonObj['id'])
|
||||
postLink = \
|
||||
httpPrefix + '://' + \
|
||||
getFullDomain(domain, port) + \
|
||||
'/users/' + nickname + \
|
||||
'?notifypost=' + postId.replace('/', '-')
|
||||
_notifyPostArrival(baseDir, handle, postLink)
|
||||
|
||||
# If this is a reply to a muted post then also mute it.
|
||||
# This enables you to ignore a threat that's getting boring
|
||||
if isReplyToMutedPost:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
__filename__ = "notifyOnPost.py"
|
||||
__author__ = "Bob Mottram"
|
||||
__license__ = "AGPL3+"
|
||||
__version__ = "1.2.0"
|
||||
__maintainer__ = "Bob Mottram"
|
||||
__email__ = "bob@freedombone.net"
|
||||
__status__ = "Production"
|
||||
__module_group__ = "Calendar"
|
||||
|
||||
import os
|
||||
from utils import removeDomainPort
|
||||
|
||||
|
||||
def _notifyOnPostArrival(baseDir: str, nickname: str, domain: str,
|
||||
followingNickname: str,
|
||||
followingDomain: str,
|
||||
add: bool) -> None:
|
||||
"""Adds or removes a handle from the following.txt list into a list
|
||||
indicating whether to notify when a new post arrives from that account
|
||||
"""
|
||||
# check that a following file exists
|
||||
domain = removeDomainPort(domain)
|
||||
followingFilename = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/following.txt'
|
||||
if not os.path.isfile(followingFilename):
|
||||
print("WARN: following.txt doesn't exist for " +
|
||||
nickname + '@' + domain)
|
||||
return
|
||||
handle = followingNickname + '@' + followingDomain
|
||||
|
||||
# check that you are following this handle
|
||||
if handle + '\n' not in open(followingFilename).read():
|
||||
print('WARN: ' + handle + ' is not in ' + followingFilename)
|
||||
return
|
||||
|
||||
notifyOnPostFilename = baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + '/notifyOnPost.txt'
|
||||
|
||||
# get the contents of the notifyOnPost file, which is
|
||||
# a set of handles
|
||||
followingHandles = ''
|
||||
if os.path.isfile(notifyOnPostFilename):
|
||||
print('notify file exists')
|
||||
with open(notifyOnPostFilename, 'r') as calendarFile:
|
||||
followingHandles = calendarFile.read()
|
||||
else:
|
||||
# create a new notifyOnPost file from the following file
|
||||
print('Creating notifyOnPost file ' + notifyOnPostFilename)
|
||||
followingHandles = ''
|
||||
with open(followingFilename, 'r') as followingFile:
|
||||
followingHandles = followingFile.read()
|
||||
if add:
|
||||
with open(notifyOnPostFilename, 'w+') as fp:
|
||||
fp.write(followingHandles + handle + '\n')
|
||||
|
||||
# already in the notifyOnPost file?
|
||||
if handle + '\n' in followingHandles:
|
||||
print(handle + ' exists in notifyOnPost.txt')
|
||||
if add:
|
||||
# already added
|
||||
return
|
||||
# remove from calendar file
|
||||
followingHandles = followingHandles.replace(handle + '\n', '')
|
||||
with open(notifyOnPostFilename, 'w+') as fp:
|
||||
fp.write(followingHandles)
|
||||
else:
|
||||
print(handle + ' not in notifyOnPost.txt')
|
||||
# not already in the notifyOnPost file
|
||||
if add:
|
||||
# append to the list of handles
|
||||
followingHandles += handle + '\n'
|
||||
with open(notifyOnPostFilename, 'w+') as fp:
|
||||
fp.write(followingHandles)
|
||||
|
||||
|
||||
def addNotifyOnPost(baseDir: str, nickname: str, domain: str,
|
||||
followingNickname: str,
|
||||
followingDomain: str) -> None:
|
||||
_notifyOnPostArrival(baseDir, nickname, domain,
|
||||
followingNickname, followingDomain, True)
|
||||
|
||||
|
||||
def removeNotifyOnPost(baseDir: str, nickname: str, domain: str,
|
||||
followingNickname: str,
|
||||
followingDomain: str) -> None:
|
||||
_notifyOnPostArrival(baseDir, nickname, domain,
|
||||
followingNickname, followingDomain, False)
|
||||
|
||||
|
||||
def notifyWhenPersonPosts(baseDir: str, nickname: str, domain: str,
|
||||
followingNickname: str,
|
||||
followingDomain: str) -> bool:
|
||||
"""Returns true if receiving notifications when the given publishes a post
|
||||
"""
|
||||
if followingNickname == nickname and followingDomain == domain:
|
||||
return False
|
||||
notifyOnPostFilename = \
|
||||
baseDir + '/accounts/' + nickname + '@' + domain + '/notifyOnPost.txt'
|
||||
handle = followingNickname + '@' + followingDomain
|
||||
if not os.path.isfile(notifyOnPostFilename):
|
||||
# create a new notifyOnPost file
|
||||
with open(notifyOnPostFilename, 'w+') as fp:
|
||||
fp.write('')
|
||||
return handle + '\n' in open(notifyOnPostFilename).read()
|
||||
|
|
@ -215,6 +215,21 @@ function notifications {
|
|||
fi
|
||||
fi
|
||||
|
||||
# send notifications for posts arriving from a particular person
|
||||
epicyonNotifyFile="$epicyonDir/.newNotifiedPost"
|
||||
if [ -f "$epicyonNotifyFile" ]; then
|
||||
if ! grep -q "##sent##" "$epicyonNotifyFile"; then
|
||||
epicyonNotifyMessage=$(notification_translate_text 'New post')
|
||||
epicyonNotifyFileContent=$(echo "$epicyonNotifyMessage")" "$(cat "$epicyonNotifyFile")
|
||||
if [[ "$epicyonNotifyFileContent" == *':'* ]]; then
|
||||
epicyonNotifyMessage="Epicyon: $epicyonNotifyFileContent"
|
||||
fi
|
||||
sendNotification "$USERNAME" "Epicyon" "$epicyonNotifyMessage"
|
||||
echo "##sent##" > "$epicyonNotifyFile"
|
||||
chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonNotifyFile"
|
||||
fi
|
||||
fi
|
||||
|
||||
# send notifications for replies to XMPP/email users
|
||||
epicyonReplyFile="$epicyonDir/.newReply"
|
||||
if [ -f "$epicyonReplyFile" ]; then
|
||||
|
|
|
|||
81
tests.py
81
tests.py
|
|
@ -94,6 +94,7 @@ from inbox import jsonPostAllowsComments
|
|||
from inbox import validInbox
|
||||
from inbox import validInboxFilenames
|
||||
from categories import guessHashtagCategory
|
||||
from content import switchWords
|
||||
from content import extractTextFieldsInPOST
|
||||
from content import validHashTag
|
||||
from content import htmlReplaceEmailQuote
|
||||
|
|
@ -3384,62 +3385,6 @@ def _testFunctions():
|
|||
_diagramGroups(['Core', 'Accessibility'], ['utils'],
|
||||
modules, modGroups, maxModuleCalls)
|
||||
|
||||
callGraphStr = 'digraph Epicyon {\n\n'
|
||||
callGraphStr += ' size="8,6"; ratio=fill;\n'
|
||||
callGraphStr += ' graph [fontsize=10 fontname="Verdana" compound=true];\n'
|
||||
callGraphStr += ' node [shape=record fontsize=10 fontname="Verdana"];\n\n'
|
||||
|
||||
for modName, modProperties in modules.items():
|
||||
callGraphStr += ' subgraph cluster_' + modName + ' {\n'
|
||||
callGraphStr += ' label = "' + modName + '";\n'
|
||||
callGraphStr += ' node [style=filled];\n'
|
||||
moduleFunctionsStr = ''
|
||||
for name in modProperties['functions']:
|
||||
if name.startswith('test'):
|
||||
continue
|
||||
if name not in excludeFuncs:
|
||||
if not functionProperties[name]['calls']:
|
||||
moduleFunctionsStr += \
|
||||
' "' + name + '" [fillcolor=yellow style=filled];\n'
|
||||
continue
|
||||
noOfCalls = len(functionProperties[name]['calls'])
|
||||
if noOfCalls < int(maxFunctionCalls / 4):
|
||||
moduleFunctionsStr += ' "' + name + \
|
||||
'" [fillcolor=orange style=filled];\n'
|
||||
else:
|
||||
moduleFunctionsStr += ' "' + name + \
|
||||
'" [fillcolor=red style=filled];\n'
|
||||
|
||||
if moduleFunctionsStr:
|
||||
callGraphStr += moduleFunctionsStr + '\n'
|
||||
callGraphStr += ' color=blue;\n'
|
||||
callGraphStr += ' }\n\n'
|
||||
|
||||
for name, properties in functionProperties.items():
|
||||
if not properties['calls']:
|
||||
continue
|
||||
noOfCalls = len(properties['calls'])
|
||||
if noOfCalls <= int(maxFunctionCalls / 8):
|
||||
modColor = 'blue'
|
||||
elif noOfCalls < int(maxFunctionCalls / 4):
|
||||
modColor = 'green'
|
||||
else:
|
||||
modColor = 'red'
|
||||
for calledFunc in properties['calls']:
|
||||
if calledFunc.startswith('test'):
|
||||
continue
|
||||
if calledFunc not in excludeFuncs:
|
||||
callGraphStr += ' "' + name + '" -> "' + calledFunc + \
|
||||
'" [color=' + modColor + '];\n'
|
||||
|
||||
callGraphStr += '\n}\n'
|
||||
with open('epicyon.dot', 'w+') as fp:
|
||||
fp.write(callGraphStr)
|
||||
print('Call graph saved to epicyon.dot')
|
||||
print('Plot using: ' +
|
||||
'sfdp -x -Goverlap=prism -Goverlap_scaling=8 ' +
|
||||
'-Gsep=+120 -Tx11 epicyon.dot')
|
||||
|
||||
|
||||
def _testLinksWithinPost() -> None:
|
||||
baseDir = os.getcwd()
|
||||
|
|
@ -4173,9 +4118,33 @@ def _testUserAgentDomain() -> None:
|
|||
assert userAgentDomain(userAgent, False) is None
|
||||
|
||||
|
||||
def _testSwitchWords() -> None:
|
||||
print('testSwitchWords')
|
||||
rules = [
|
||||
"rock -> hamster",
|
||||
"orange -> lemon"
|
||||
]
|
||||
baseDir = os.getcwd()
|
||||
nickname = 'testuser'
|
||||
domain = 'testdomain.com'
|
||||
|
||||
content = 'This is a test'
|
||||
result = switchWords(baseDir, nickname, domain, content, rules)
|
||||
assert result == content
|
||||
|
||||
content = 'This is orange test'
|
||||
result = switchWords(baseDir, nickname, domain, content, rules)
|
||||
assert result == 'This is lemon test'
|
||||
|
||||
content = 'This is a test rock'
|
||||
result = switchWords(baseDir, nickname, domain, content, rules)
|
||||
assert result == 'This is a test hamster'
|
||||
|
||||
|
||||
def runAllTests():
|
||||
print('Running tests...')
|
||||
updateDefaultThemesList(os.getcwd())
|
||||
_testSwitchWords()
|
||||
_testFunctions()
|
||||
_testUserAgentDomain()
|
||||
_testRoles()
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "استيراد الموضوع",
|
||||
"Export Theme": "موضوع التصدير",
|
||||
"Custom post submit button text": "عرف نشر إرسال نص زر",
|
||||
"Blocked User Agents": "عوامل المستخدم المحظورة"
|
||||
"Blocked User Agents": "عوامل المستخدم المحظورة",
|
||||
"Notify me when this account posts": "أعلمني عندما ينشر الحساب هذا"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Importació temàtica",
|
||||
"Export Theme": "Tema d'exportació",
|
||||
"Custom post submit button text": "Text de botó d'enviament de publicacions personalitzades",
|
||||
"Blocked User Agents": "Agents d'usuari bloquejats"
|
||||
"Blocked User Agents": "Agents d'usuari bloquejats",
|
||||
"Notify me when this account posts": "Aviseu-me quan publiqui aquest compte"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Thema Mewnforio",
|
||||
"Export Theme": "Thema Allforio",
|
||||
"Custom post submit button text": "Testun Post Post Post",
|
||||
"Blocked User Agents": "Asiantau defnyddwyr wedi'u blocio"
|
||||
"Blocked User Agents": "Asiantau defnyddwyr wedi'u blocio",
|
||||
"Notify me when this account posts": "Rhoi gwybod i mi pan fydd y cyfrifon cyfrif hwn"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Theme importieren",
|
||||
"Export Theme": "Theme exportieren",
|
||||
"Custom post submit button text": "Benutzerdefinierte Post-Senden Schaltfläche Text",
|
||||
"Blocked User Agents": "Blockierte Benutzeragenten"
|
||||
"Blocked User Agents": "Blockierte Benutzeragenten",
|
||||
"Notify me when this account posts": "Benachrichtigen Sie mich, wenn dieses Konto postet"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Import Theme",
|
||||
"Export Theme": "Export Theme",
|
||||
"Custom post submit button text": "Custom post submit button text",
|
||||
"Blocked User Agents": "Blocked User Agents"
|
||||
"Blocked User Agents": "Blocked User Agents",
|
||||
"Notify me when this account posts": "Notify me when this account posts"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Tema de importación",
|
||||
"Export Theme": "Tema de exportación",
|
||||
"Custom post submit button text": "POST POST PERSONALIZADO Botón Texto",
|
||||
"Blocked User Agents": "Agentes de usuario bloqueados"
|
||||
"Blocked User Agents": "Agentes de usuario bloqueados",
|
||||
"Notify me when this account posts": "Notifíqueme cuando se publique esta cuenta"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Import thème",
|
||||
"Export Theme": "Thème d'exportation",
|
||||
"Custom post submit button text": "Texte de bouton d'envoi postal personnalisé",
|
||||
"Blocked User Agents": "Agents d'utilisateur bloqués"
|
||||
"Blocked User Agents": "Agents d'utilisateur bloqués",
|
||||
"Notify me when this account posts": "Avertissez-moi quand ce compte publie"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Téama Iompórtáil",
|
||||
"Export Theme": "Téama Easpórtála",
|
||||
"Custom post submit button text": "Post saincheaptha Cuir isteach an cnaipe Téacs",
|
||||
"Blocked User Agents": "Gníomhairí úsáideora blocáilte"
|
||||
"Blocked User Agents": "Gníomhairí úsáideora blocáilte",
|
||||
"Notify me when this account posts": "Cuir in iúl dom nuair a phostófar an cuntas seo"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "आयात विषय",
|
||||
"Export Theme": "निर्यात विषय",
|
||||
"Custom post submit button text": "कस्टम पोस्ट सबमिट बटन टेक्स्ट",
|
||||
"Blocked User Agents": "अवरुद्ध उपयोगकर्ता एजेंट"
|
||||
"Blocked User Agents": "अवरुद्ध उपयोगकर्ता एजेंट",
|
||||
"Notify me when this account posts": "यह खाता पोस्ट होने पर मुझे सूचित करें"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Tema dell'importazione",
|
||||
"Export Theme": "Esportare tema",
|
||||
"Custom post submit button text": "Pulsante di invio del post personalizzato",
|
||||
"Blocked User Agents": "Agenti utente bloccati"
|
||||
"Blocked User Agents": "Agenti utente bloccati",
|
||||
"Notify me when this account posts": "Avvisami quando questo account messaggi"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "輸入テーマ",
|
||||
"Export Theme": "テーマをエクスポートします",
|
||||
"Custom post submit button text": "カスタムポスト送信ボタンテキスト",
|
||||
"Blocked User Agents": "ブロックされたユーザーエージェント"
|
||||
"Blocked User Agents": "ブロックされたユーザーエージェント",
|
||||
"Notify me when this account posts": "この口座投稿を通知する"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Mijara Import",
|
||||
"Export Theme": "Mijara Export",
|
||||
"Custom post submit button text": "Nivîsa bişkojka paşîn a paşîn",
|
||||
"Blocked User Agents": "Karmendên bikarhêner asteng kirin"
|
||||
"Blocked User Agents": "Karmendên bikarhêner asteng kirin",
|
||||
"Notify me when this account posts": "Dema ku ev postên hesabê min agahdar bikin"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,5 +445,6 @@
|
|||
"Import Theme": "Import Theme",
|
||||
"Export Theme": "Export Theme",
|
||||
"Custom post submit button text": "Custom post submit button text",
|
||||
"Blocked User Agents": "Blocked User Agents"
|
||||
"Blocked User Agents": "Blocked User Agents",
|
||||
"Notify me when this account posts": "Notify me when this account posts"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Importar tema",
|
||||
"Export Theme": "Exportar tema",
|
||||
"Custom post submit button text": "Texto de botão de envio de post personalizado",
|
||||
"Blocked User Agents": "Agentes de usuário bloqueados"
|
||||
"Blocked User Agents": "Agentes de usuário bloqueados",
|
||||
"Notify me when this account posts": "Notifique-me quando esta conta posts"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Импортировать тему",
|
||||
"Export Theme": "Экспортная тема",
|
||||
"Custom post submit button text": "Пользовательский пост Отправить кнопку текста",
|
||||
"Blocked User Agents": "Заблокированные пользовательские агенты"
|
||||
"Blocked User Agents": "Заблокированные пользовательские агенты",
|
||||
"Notify me when this account posts": "Сообщите мне, когда эта учетная запись"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "Ingiza mandhari",
|
||||
"Export Theme": "Tuma mandhari",
|
||||
"Custom post submit button text": "Ujumbe wa Desturi Wasilisha Nakala ya kifungo",
|
||||
"Blocked User Agents": "Wakala wa watumiaji waliozuiwa"
|
||||
"Blocked User Agents": "Wakala wa watumiaji waliozuiwa",
|
||||
"Notify me when this account posts": "Nijulishe wakati akaunti hii ya akaunti."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,5 +449,6 @@
|
|||
"Import Theme": "进口主题",
|
||||
"Export Theme": "出口主题",
|
||||
"Custom post submit button text": "自定义发布提交按钮文本",
|
||||
"Blocked User Agents": "阻止用户代理商"
|
||||
"Blocked User Agents": "阻止用户代理商",
|
||||
"Notify me when this account posts": "此帐户帖子时通知我"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from blocking import isBlocked
|
|||
from follow import isFollowerOfPerson
|
||||
from follow import isFollowingActor
|
||||
from followingCalendar import receivingCalendarEvents
|
||||
from notifyOnPost import notifyWhenPersonPosts
|
||||
from webapp_utils import htmlHeaderWithExternalStyle
|
||||
from webapp_utils import htmlFooter
|
||||
from webapp_utils import getBrokenLinkSubstitute
|
||||
|
|
@ -246,8 +247,21 @@ def htmlPersonOptions(defaultTimeline: str,
|
|||
'name="submitPetname">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
|
||||
# checkbox for receiving calendar events
|
||||
# Notify when a post arrives from this person
|
||||
if isFollowingActor(baseDir, nickname, domain, optionsActor):
|
||||
checkboxStr = \
|
||||
' <input type="checkbox" class="profilecheckbox" ' + \
|
||||
'name="notifyOnPost" checked> 🔔' + \
|
||||
translate['Notify me when this account posts'] + \
|
||||
'\n <button type="submit" class="buttonsmall" ' + \
|
||||
'name="submitNotifyOnPost">' + \
|
||||
translate['Submit'] + '</button><br>\n'
|
||||
if not notifyWhenPersonPosts(baseDir, nickname, domain,
|
||||
optionsNickname,
|
||||
optionsDomainFull):
|
||||
checkboxStr = checkboxStr.replace(' checked>', '>')
|
||||
optionsStr += checkboxStr
|
||||
|
||||
checkboxStr = \
|
||||
' <input type="checkbox" ' + \
|
||||
'class="profilecheckbox" name="onCalendar" checked> ' + \
|
||||
|
|
|
|||
Loading…
Reference in New Issue