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

main
Bob Mottram 2021-03-09 19:57:36 +00:00
commit bb3ea15e34
19 changed files with 323 additions and 156 deletions

View File

@ -2719,7 +2719,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
if hashtagStr: if hashtagStr:
msg = hashtagStr.encode('utf-8') msg = hashtagStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -2772,7 +2773,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
if historyStr: if historyStr:
msg = historyStr.encode('utf-8') msg = historyStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -2862,7 +2864,8 @@ class PubServer(BaseHTTPRequestHandler):
showPublishedDateOnly, showPublishedDateOnly,
self.server.defaultTimeline, self.server.defaultTimeline,
self.server.peertubeInstances, self.server.peertubeInstances,
allowLocalNetworkAccess) allowLocalNetworkAccess,
self.server.themeName)
if profileStr: if profileStr:
msg = profileStr.encode('utf-8') msg = profileStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -4853,8 +4856,7 @@ class PubServer(BaseHTTPRequestHandler):
# only receive DMs from accounts you follow # only receive DMs from accounts you follow
followDMsFilename = \ followDMsFilename = \
baseDir + '/accounts/' + \ baseDir + '/accounts/' + \
nickname + '@' + domain + \ nickname + '@' + domain + '/.followDMs'
'/.followDMs'
if onFinalWelcomeScreen: if onFinalWelcomeScreen:
# initial default setting created via # initial default setting created via
# the welcome screen # the welcome screen
@ -5988,7 +5990,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
if hashtagStr: if hashtagStr:
msg = hashtagStr.encode('utf-8') msg = hashtagStr.encode('utf-8')
msglen = len(msg) msglen = len(msg)
@ -6951,7 +6954,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.YTReplacementDomain, self.server.YTReplacementDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
self.server.peertubeInstances, self.server.peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
if deleteStr: if deleteStr:
deleteStrLen = len(deleteStr) deleteStrLen = len(deleteStr)
self._set_headers('text/html', deleteStrLen, self._set_headers('text/html', deleteStrLen,
@ -7156,7 +7160,8 @@ class PubServer(BaseHTTPRequestHandler):
ytDomain, ytDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
peertubeInstances, peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7243,7 +7248,8 @@ class PubServer(BaseHTTPRequestHandler):
ytDomain, ytDomain,
self.server.showPublishedDateOnly, self.server.showPublishedDateOnly,
peertubeInstances, peertubeInstances,
self.server.allowLocalNetworkAccess) self.server.allowLocalNetworkAccess,
self.server.themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7533,6 +7539,8 @@ class PubServer(BaseHTTPRequestHandler):
cssCache = self.server.cssCache cssCache = self.server.cssCache
allowLocalNetworkAccess = \ allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess self.server.allowLocalNetworkAccess
themeName = \
self.server.themeName
msg = \ msg = \
htmlIndividualPost(cssCache, htmlIndividualPost(cssCache,
recentPostsCache, recentPostsCache,
@ -7553,7 +7561,8 @@ class PubServer(BaseHTTPRequestHandler):
ytDomain, ytDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess) allowLocalNetworkAccess,
themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -7657,6 +7666,8 @@ class PubServer(BaseHTTPRequestHandler):
self.server.peertubeInstances self.server.peertubeInstances
allowLocalNetworkAccess = \ allowLocalNetworkAccess = \
self.server.allowLocalNetworkAccess self.server.allowLocalNetworkAccess
themeName = \
self.server.themeName
msg = \ msg = \
htmlIndividualPost(self.server.cssCache, htmlIndividualPost(self.server.cssCache,
recentPostsCache, recentPostsCache,
@ -7677,7 +7688,8 @@ class PubServer(BaseHTTPRequestHandler):
ytDomain, ytDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess) allowLocalNetworkAccess,
themeName)
msg = msg.encode('utf-8') msg = msg.encode('utf-8')
msglen = len(msg) msglen = len(msg)
self._set_headers('text/html', msglen, self._set_headers('text/html', msglen,
@ -14668,7 +14680,8 @@ def runDaemon(brochMode: bool,
httpd.maxFollowers, httpd.maxFollowers,
httpd.allowLocalNetworkAccess, httpd.allowLocalNetworkAccess,
httpd.peertubeInstances, httpd.peertubeInstances,
verifyAllSignatures), daemon=True) verifyAllSignatures,
httpd.themeName), daemon=True)
print('Creating scheduled post thread') print('Creating scheduled post thread')
httpd.thrPostSchedule = \ httpd.thrPostSchedule = \

View File

@ -58,12 +58,12 @@ from filters import isFiltered
from utils import updateAnnounceCollection from utils import updateAnnounceCollection
from utils import undoAnnounceCollectionEntry from utils import undoAnnounceCollectionEntry
from utils import dangerousMarkup from utils import dangerousMarkup
from utils import isDM
from utils import isReply
from httpsig import messageContentDigest from httpsig import messageContentDigest
from posts import createDirectMessagePost from posts import createDirectMessagePost
from posts import validContentWarning from posts import validContentWarning
from posts import downloadAnnounce from posts import downloadAnnounce
from posts import isDM
from posts import isReply
from posts import isMuted from posts import isMuted
from posts import isImageMedia from posts import isImageMedia
from posts import sendSignedJson from posts import sendSignedJson
@ -158,7 +158,8 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
allowDeletion: bool, boxname: str, allowDeletion: bool, boxname: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> None: allowLocalNetworkAccess: bool,
themeName: str) -> None:
"""Converts the json post into html and stores it in a cache """Converts the json post into html and stores it in a cache
This enables the post to be quickly displayed later This enables the post to be quickly displayed later
""" """
@ -176,6 +177,7 @@ def _inboxStorePostToHtmlCache(recentPostsCache: {}, maxRecentPosts: int,
httpPrefix, __version__, boxname, None, httpPrefix, __version__, boxname, None,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, allowLocalNetworkAccess, peertubeInstances, allowLocalNetworkAccess,
themeName,
not isDM(postJsonObject), not isDM(postJsonObject),
True, True, False, True) True, True, False, True)
@ -1290,7 +1292,8 @@ def _receiveAnnounce(recentPostsCache: {},
personCache: {}, messageJson: {}, federationList: [], personCache: {}, messageJson: {}, federationList: [],
debug: bool, translate: {}, debug: bool, translate: {},
YTReplacementDomain: str, YTReplacementDomain: str,
allowLocalNetworkAccess: bool) -> bool: allowLocalNetworkAccess: bool,
themeName: str) -> bool:
"""Receives an announce activity within the POST section of HTTPServer """Receives an announce activity within the POST section of HTTPServer
""" """
if messageJson['type'] != 'Announce': if messageJson['type'] != 'Announce':
@ -1408,9 +1411,12 @@ def _receiveAnnounce(recentPostsCache: {},
if isRecentPost(postJsonObject): if isRecentPost(postJsonObject):
if not os.path.isfile(postFilename + '.tts'): if not os.path.isfile(postFilename + '.tts'):
updateSpeaker(baseDir, nickname, domain, domainFull = getFullDomain(domain, port)
updateSpeaker(baseDir, httpPrefix,
nickname, domain, domainFull,
postJsonObject, personCache, postJsonObject, personCache,
translate, lookupActor) translate, lookupActor,
themeName)
ttsFile = open(postFilename + '.tts', "w+") ttsFile = open(postFilename + '.tts', "w+")
if ttsFile: if ttsFile:
ttsFile.write('\n') ttsFile.write('\n')
@ -2164,7 +2170,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
allowLocalNetworkAccess: bool, allowLocalNetworkAccess: bool,
peertubeInstances: [], peertubeInstances: [],
lastBounceMessage: []) -> bool: lastBounceMessage: [],
themeName: str) -> bool:
""" Anything which needs to be done after initial checks have passed """ Anything which needs to be done after initial checks have passed
""" """
actor = keyId actor = keyId
@ -2243,7 +2250,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
federationList, federationList,
debug, translate, debug, translate,
YTReplacementDomain, YTReplacementDomain,
allowLocalNetworkAccess): allowLocalNetworkAccess,
themeName):
if debug: if debug:
print('DEBUG: Announce accepted from ' + actor) print('DEBUG: Announce accepted from ' + actor)
@ -2491,9 +2499,11 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
else: else:
if boxname == 'inbox': if boxname == 'inbox':
if isRecentPost(postJsonObject): if isRecentPost(postJsonObject):
updateSpeaker(baseDir, nickname, domain, domainFull = getFullDomain(domain, port)
updateSpeaker(baseDir, httpPrefix,
nickname, domain, domainFull,
postJsonObject, personCache, postJsonObject, personCache,
translate, None) translate, None, themeName)
if not unitTest: if not unitTest:
if debug: if debug:
print('Saving inbox post as html to cache') print('Saving inbox post as html to cache')
@ -2513,7 +2523,8 @@ def _inboxAfterInitial(recentPostsCache: {}, maxRecentPosts: int,
boxname, boxname,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess) allowLocalNetworkAccess,
themeName)
if debug: if debug:
timeDiff = \ timeDiff = \
str(int((time.time() - htmlCacheStartTime) * str(int((time.time() - htmlCacheStartTime) *
@ -2614,7 +2625,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
maxFollowers: int, allowLocalNetworkAccess: bool, maxFollowers: int, allowLocalNetworkAccess: bool,
peertubeInstances: [], peertubeInstances: [],
verifyAllSignatures: bool) -> None: verifyAllSignatures: bool,
themeName: str) -> None:
"""Processes received items and moves them to the appropriate """Processes received items and moves them to the appropriate
directories directories
""" """
@ -3107,7 +3119,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly, showPublishedDateOnly,
allowLocalNetworkAccess, allowLocalNetworkAccess,
peertubeInstances, peertubeInstances,
lastBounceMessage) lastBounceMessage,
themeName)
if debug: if debug:
pprint(queueJson['post']) pprint(queueJson['post'])

View File

@ -66,6 +66,17 @@ def _speakerPicospeaker(pitch: int, rate: int, systemLanguage: str,
os.system(speakerCmd) os.system(speakerCmd)
def _playNotificationSound(soundFilename: str, player='ffplay') -> None:
"""Plays a sound
"""
if not os.path.isfile(soundFilename):
return
if player == 'ffplay':
os.system('ffplay ' + soundFilename +
' -autoexit -hide_banner -nodisp')
def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str, def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str,
nickname: str, domain: str, port: int, nickname: str, domain: str, port: int,
password: str, screenreader: str, password: str, screenreader: str,
@ -73,64 +84,121 @@ def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str,
"""Runs the notifications and screen reader client, """Runs the notifications and screen reader client,
which announces new inbox items which announces new inbox items
""" """
if screenreader == 'espeak': if screenreader:
print('Setting up espeak') if screenreader == 'espeak':
from espeak import espeak print('Setting up espeak')
elif screenreader != 'picospeaker': from espeak import espeak
print(screenreader + ' is not a supported TTS system') elif screenreader != 'picospeaker':
return print(screenreader + ' is not a supported TTS system')
return
print('Running ' + screenreader + ' for ' + nickname + '@' + domain) print('Running ' + screenreader + ' for ' + nickname + '@' + domain)
prevSay = '' prevSay = ''
prevDM = False
prevReply = False
prevCalendar = False
prevFollow = False
prevLike = ''
prevShare = False
dmSoundFilename = 'dm.ogg'
replySoundFilename = 'reply.ogg'
calendarSoundFilename = 'calendar.ogg'
followSoundFilename = 'follow.ogg'
likeSoundFilename = 'like.ogg'
shareSoundFilename = 'share.ogg'
player = 'ffplay'
while (1): while (1):
session = createSession(proxyType) session = createSession(proxyType)
speakerJson = \ speakerJson = \
getSpeakerFromServer(baseDir, session, nickname, password, getSpeakerFromServer(baseDir, session, nickname, password,
domain, port, httpPrefix, True, __version__) domain, port, httpPrefix, True, __version__)
if speakerJson: if speakerJson:
if speakerJson['say'] != prevSay: if speakerJson.get('notify'):
if speakerJson.get('name'): soundsDir = 'theme/default/sounds'
nameStr = speakerJson['name'] if speakerJson['notify'].get('theme'):
gender = 'They/Them' soundsDir = \
if speakerJson.get('gender'): 'theme/' + speakerJson['notify']['theme'] + '/sounds'
gender = speakerJson['gender'] if not os.path.isdir(soundsDir):
soundsDir = 'theme/default/sounds'
if dmSoundFilename:
if speakerJson['notify']['dm'] != prevDM:
_playNotificationSound(soundsDir + '/' +
dmSoundFilename, player)
elif replySoundFilename:
if speakerJson['notify']['reply'] != prevReply:
_playNotificationSound(soundsDir + '/' +
replySoundFilename, player)
elif calendarSoundFilename:
if speakerJson['notify']['calendar'] != prevCalendar:
_playNotificationSound(soundsDir + '/' +
calendarSoundFilename, player)
elif followSoundFilename:
if speakerJson['notify']['followRequests'] != prevFollow:
_playNotificationSound(soundsDir + '/' +
followSoundFilename, player)
elif likeSoundFilename:
if speakerJson['notify']['like'] != prevLike:
_playNotificationSound(soundsDir + '/' +
likeSoundFilename, player)
elif shareSoundFilename:
if speakerJson['notify']['share'] != prevShare:
_playNotificationSound(soundsDir + '/' +
shareSoundFilename, player)
# get the speech parameters prevDM = speakerJson['notify']['dm']
pitch = getSpeakerPitch(nameStr, screenreader, gender) prevReply = speakerJson['notify']['reply']
rate = getSpeakerRate(nameStr, screenreader) prevCalendar = speakerJson['notify']['calendar']
srange = getSpeakerRange(nameStr) prevFollow = speakerJson['notify']['followRequests']
prevLike = speakerJson['notify']['like']
prevShare = speakerJson['notify']['share']
# say the speaker's name if speakerJson.get('say'):
if screenreader == 'espeak': if speakerJson['say'] != prevSay:
_speakerEspeak(espeak, pitch, rate, srange, nameStr) if speakerJson.get('name'):
elif screenreader == 'picospeaker': nameStr = speakerJson['name']
_speakerPicospeaker(pitch, rate, gender = 'They/Them'
systemLanguage, nameStr) if speakerJson.get('gender'):
time.sleep(2) gender = speakerJson['gender']
# append image description if needed # get the speech parameters
if not speakerJson.get('imageDescription'): pitch = getSpeakerPitch(nameStr, screenreader, gender)
sayStr = speakerJson['say'] rate = getSpeakerRate(nameStr, screenreader)
# echo spoken text to the screen srange = getSpeakerRange(nameStr)
print(html.unescape(nameStr) + ': ' +
html.unescape(speakerJson['say']) + '\n')
else:
sayStr = speakerJson['say'] + '. ' + \
speakerJson['imageDescription']
# echo spoken text to the screen
print(html.unescape(nameStr) + ': ' +
html.unescape(speakerJson['say']) + '\n' +
html.unescape(speakerJson['imageDescription']))
# speak the post content # say the speaker's name
if screenreader == 'espeak': if screenreader == 'espeak':
_speakerEspeak(espeak, pitch, rate, srange, sayStr) _speakerEspeak(espeak, pitch, rate, srange,
elif screenreader == 'picospeaker': nameStr)
_speakerPicospeaker(pitch, rate, elif screenreader == 'picospeaker':
systemLanguage, sayStr) _speakerPicospeaker(pitch, rate,
systemLanguage, nameStr)
time.sleep(2)
prevSay = speakerJson['say'] # append image description if needed
if not speakerJson.get('imageDescription'):
sayStr = speakerJson['say']
# echo spoken text to the screen
print(html.unescape(nameStr) + ': ' +
html.unescape(speakerJson['say']) + '\n')
else:
sayStr = speakerJson['say'] + '. ' + \
speakerJson['imageDescription']
# echo spoken text to the screen
imageDescription = \
html.unescape(speakerJson['imageDescription'])
print(html.unescape(nameStr) + ': ' +
html.unescape(speakerJson['say']) + '\n' +
imageDescription)
# speak the post content
if screenreader == 'espeak':
_speakerEspeak(espeak, pitch, rate, srange, sayStr)
elif screenreader == 'picospeaker':
_speakerPicospeaker(pitch, rate,
systemLanguage, sayStr)
prevSay = speakerJson['say']
# wait for a while, or until a key is pressed # wait for a while, or until a key is pressed
keyPress = _waitForKeypress(30, debug) keyPress = _waitForKeypress(30, debug)

View File

@ -2921,34 +2921,6 @@ def createModeration(baseDir: str, nickname: str, domain: str, port: int,
return boxItems return boxItems
def isDM(postJsonObject: {}) -> bool:
"""Returns true if the given post is a DM
"""
if postJsonObject['type'] != 'Create':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if postJsonObject['object']['type'] != 'Note' and \
postJsonObject['object']['type'] != 'Patch' and \
postJsonObject['object']['type'] != 'EncryptedMessage' and \
postJsonObject['object']['type'] != 'Article':
return False
if postJsonObject['object'].get('moderationStatus'):
return False
fields = ('to', 'cc')
for f in fields:
if not postJsonObject['object'].get(f):
continue
for toAddress in postJsonObject['object'][f]:
if toAddress.endswith('#Public'):
return False
if toAddress.endswith('followers'):
return False
return True
def isImageMedia(session, baseDir: str, httpPrefix: str, def isImageMedia(session, baseDir: str, httpPrefix: str,
nickname: str, domain: str, nickname: str, domain: str,
postJsonObject: {}, translate: {}, postJsonObject: {}, translate: {},
@ -2992,40 +2964,6 @@ def isImageMedia(session, baseDir: str, httpPrefix: str,
return False return False
def isReply(postJsonObject: {}, actor: str) -> bool:
"""Returns true if the given post is a reply to the given actor
"""
if postJsonObject['type'] != 'Create':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if postJsonObject['object'].get('moderationStatus'):
return False
if postJsonObject['object']['type'] != 'Note' and \
postJsonObject['object']['type'] != 'EncryptedMessage' and \
postJsonObject['object']['type'] != 'Article':
return False
if postJsonObject['object'].get('inReplyTo'):
if isinstance(postJsonObject['object']['inReplyTo'], str):
if postJsonObject['object']['inReplyTo'].startswith(actor):
return True
if not postJsonObject['object'].get('tag'):
return False
if not isinstance(postJsonObject['object']['tag'], list):
return False
for tag in postJsonObject['object']['tag']:
if not tag.get('type'):
continue
if tag['type'] == 'Mention':
if not tag.get('href'):
continue
if actor in tag['href']:
return True
return False
def _addPostStringToTimeline(postStr: str, boxname: str, def _addPostStringToTimeline(postStr: str, boxname: str,
postsInBox: [], boxActor: str) -> bool: postsInBox: [], boxActor: str) -> bool:
""" is this a valid timeline post? """ is this a valid timeline post?

View File

@ -209,9 +209,9 @@ function notifications {
if [[ "$epicyonLikeFileContent" == *':'* ]]; then if [[ "$epicyonLikeFileContent" == *':'* ]]; then
epicyonLikeMessage="Epicyon: $epicyonLikeFileContent" epicyonLikeMessage="Epicyon: $epicyonLikeFileContent"
fi fi
"${PROJECT_NAME}-notification" -u "$USERNAME" -s "Epicyon" -m "$epicyonLikeMessage" --sensitive yes sendNotification "$USERNAME" "Epicyon" "$epicyonLikeMessage"
echo "##sent##" > "$epicyonLikeFile" echo "##sent##" > "$epicyonLikeFile"
chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonLkeFile" chown ${PROJECT_NAME}:${PROJECT_NAME} "$epicyonLikeFile"
fi fi
fi fi

View File

@ -12,6 +12,8 @@ import random
import urllib.parse import urllib.parse
from auth import createBasicAuthHeader from auth import createBasicAuthHeader
from session import getJson from session import getJson
from utils import isDM
from utils import isReply
from utils import camelCaseSplit from utils import camelCaseSplit
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import getNicknameFromActor from utils import getNicknameFromActor
@ -263,7 +265,11 @@ def getSpeakerFromServer(baseDir: str, session,
def _speakerEndpointJson(displayName: str, summary: str, def _speakerEndpointJson(displayName: str, summary: str,
content: str, imageDescription: str, content: str, imageDescription: str,
links: [], gender: str, postId: str) -> {}: links: [], gender: str, postId: str,
postDM: bool, postReply: bool,
followRequestsExist: bool,
likedBy: str, postCal: bool,
postShare: bool, themeName: str) -> {}:
"""Returns a json endpoint for the TTS speaker """Returns a json endpoint for the TTS speaker
""" """
speakerJson = { speakerJson = {
@ -272,7 +278,16 @@ def _speakerEndpointJson(displayName: str, summary: str,
"say": content, "say": content,
"imageDescription": imageDescription, "imageDescription": imageDescription,
"detectedLinks": links, "detectedLinks": links,
"id": postId "id": postId,
"notify": {
"theme": themeName,
"dm": postDM,
"reply": postReply,
"followRequests": followRequestsExist,
"likedBy": likedBy,
"calendar": postCal,
"share": postShare
}
} }
if gender: if gender:
speakerJson['gender'] = gender speakerJson['gender'] = gender
@ -360,9 +375,11 @@ def getSSMLbox(baseDir: str, path: str,
instanceTitle, gender) instanceTitle, gender)
def _postToSpeakerJson(baseDir: str, nickname: str, domain: str, def _postToSpeakerJson(baseDir: str, httpPrefix: str,
nickname: str, domain: str, domainFull: str,
postJsonObject: {}, personCache: {}, postJsonObject: {}, personCache: {},
translate: {}, announcingActor: str) -> {}: translate: {}, announcingActor: str,
themeName: str) -> {}:
"""Converts an ActivityPub post into some Json containing """Converts an ActivityPub post into some Json containing
speech synthesis parameters. speech synthesis parameters.
NOTE: There currently appears to be no standardized json NOTE: There currently appears to be no standardized json
@ -429,21 +446,54 @@ def _postToSpeakerJson(baseDir: str, nickname: str, domain: str,
postId = None postId = None
if postJsonObject['object'].get('id'): if postJsonObject['object'].get('id'):
postId = postJsonObject['object']['id'] postId = postJsonObject['object']['id']
actor = httpPrefix + '://' + domainFull + '/users/' + nickname
postDM = isDM(postJsonObject)
postReply = isReply(postJsonObject, actor)
followRequestsExist = False
accountsDir = baseDir + '/accounts/' + nickname + '@' + domainFull
approveFollowsFilename = accountsDir + '/followrequests.txt'
if os.path.isfile(approveFollowsFilename):
with open(approveFollowsFilename, 'r') as fp:
follows = fp.readlines()
if len(follows) > 0:
followRequestsExist = True
likedBy = ''
likeFilename = accountsDir + '/.newLike'
if os.path.isfile(likeFilename):
with open(likeFilename, 'r') as fp:
likedBy = fp.read()
if '##sent##' in likedBy:
likedBy = ''
calendarFilename = accountsDir + '/.newCalendar'
postCal = os.path.isfile(calendarFilename)
shareFilename = accountsDir + '/.newShare'
postShare = os.path.isfile(shareFilename)
return _speakerEndpointJson(speakerName, summary, return _speakerEndpointJson(speakerName, summary,
content, imageDescription, content, imageDescription,
detectedLinks, gender, postId) detectedLinks, gender, postId,
postDM, postReply,
followRequestsExist,
likedBy,
postCal, postShare, themeName)
def updateSpeaker(baseDir: str, nickname: str, domain: str, def updateSpeaker(baseDir: str, httpPrefix: str,
nickname: str, domain: str, domainFull: str,
postJsonObject: {}, personCache: {}, postJsonObject: {}, personCache: {},
translate: {}, announcingActor: str) -> None: translate: {}, announcingActor: str,
themeName: str) -> None:
""" Generates a json file which can be used for TTS announcement """ Generates a json file which can be used for TTS announcement
of incoming inbox posts of incoming inbox posts
""" """
speakerJson = \ speakerJson = \
_postToSpeakerJson(baseDir, nickname, domain, _postToSpeakerJson(baseDir, httpPrefix,
nickname, domain, domainFull,
postJsonObject, personCache, postJsonObject, personCache,
translate, announcingActor) translate, announcingActor,
themeName)
speakerFilename = \ speakerFilename = \
baseDir + '/accounts/' + nickname + '@' + domain + '/speaker.json' baseDir + '/accounts/' + nickname + '@' + domain + '/speaker.json'
saveJson(speakerJson, speakerFilename) saveJson(speakerJson, speakerFilename)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2068,3 +2068,65 @@ def rejectPostId(baseDir: str, nickname: str, domain: str,
if rejectFile: if rejectFile:
rejectFile.write('\n') rejectFile.write('\n')
rejectFile.close() rejectFile.close()
def isDM(postJsonObject: {}) -> bool:
"""Returns true if the given post is a DM
"""
if postJsonObject['type'] != 'Create':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if postJsonObject['object']['type'] != 'Note' and \
postJsonObject['object']['type'] != 'Patch' and \
postJsonObject['object']['type'] != 'EncryptedMessage' and \
postJsonObject['object']['type'] != 'Article':
return False
if postJsonObject['object'].get('moderationStatus'):
return False
fields = ('to', 'cc')
for f in fields:
if not postJsonObject['object'].get(f):
continue
for toAddress in postJsonObject['object'][f]:
if toAddress.endswith('#Public'):
return False
if toAddress.endswith('followers'):
return False
return True
def isReply(postJsonObject: {}, actor: str) -> bool:
"""Returns true if the given post is a reply to the given actor
"""
if postJsonObject['type'] != 'Create':
return False
if not postJsonObject.get('object'):
return False
if not isinstance(postJsonObject['object'], dict):
return False
if postJsonObject['object'].get('moderationStatus'):
return False
if postJsonObject['object']['type'] != 'Note' and \
postJsonObject['object']['type'] != 'EncryptedMessage' and \
postJsonObject['object']['type'] != 'Article':
return False
if postJsonObject['object'].get('inReplyTo'):
if isinstance(postJsonObject['object']['inReplyTo'], str):
if postJsonObject['object']['inReplyTo'].startswith(actor):
return True
if not postJsonObject['object'].get('tag'):
return False
if not isinstance(postJsonObject['object']['tag'], list):
return False
for tag in postJsonObject['object']['tag']:
if not tag.get('type'):
continue
if tag['type'] == 'Mention':
if not tag.get('href'):
continue
if actor in tag['href']:
return True
return False

View File

@ -31,7 +31,8 @@ def htmlConfirmDelete(cssCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Shows a screen asking to confirm the deletion of a post """Shows a screen asking to confirm the deletion of a post
""" """
if '/statuses/' not in messageId: if '/statuses/' not in messageId:
@ -72,6 +73,7 @@ def htmlConfirmDelete(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, allowLocalNetworkAccess, peertubeInstances, allowLocalNetworkAccess,
themeName,
False, False, False, False, False) False, False, False, False, False)
deletePostStr += '<center>' deletePostStr += '<center>'
deletePostStr += \ deletePostStr += \

View File

@ -30,7 +30,8 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Shows posts on the front screen of a news instance """Shows posts on the front screen of a news instance
These should only be public blog posts from the features timeline These should only be public blog posts from the features timeline
which is the blog timeline of the news actor which is the blog timeline of the news actor
@ -71,6 +72,7 @@ def _htmlFrontScreenPosts(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
False, False, False, True, False) False, False, False, True, False)
if postStr: if postStr:
profileStr += postStr + separatorStr profileStr += postStr + separatorStr
@ -159,7 +161,8 @@ def htmlFrontScreen(rssIconAtTop: bool,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess) + licenseStr allowLocalNetworkAccess,
theme) + licenseStr
# Footer which is only used for system accounts # Footer which is only used for system accounts
profileFooterStr = ' </td>\n' profileFooterStr = ' </td>\n'

View File

@ -19,9 +19,9 @@ from like import noOfLikes
from follow import isFollowingActor from follow import isFollowingActor
from posts import postIsMuted from posts import postIsMuted
from posts import getPersonBox from posts import getPersonBox
from posts import isDM
from posts import downloadAnnounce from posts import downloadAnnounce
from posts import populateRepliesJson from posts import populateRepliesJson
from utils import isDM
from utils import rejectPostId from utils import rejectPostId
from utils import isRecentPost from utils import isRecentPost
from utils import getConfigParam from utils import getConfigParam
@ -1139,6 +1139,7 @@ def individualPostAsHtml(allowDownloads: bool,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool, allowLocalNetworkAccess: bool,
themeName: str,
showRepeats=True, showRepeats=True,
showIcons=False, showIcons=False,
manuallyApprovesFollowers=False, manuallyApprovesFollowers=False,
@ -1304,9 +1305,11 @@ def individualPostAsHtml(allowDownloads: bool,
postJsonObject['id']) postJsonObject['id'])
if announceFilename and postJsonObject.get('actor'): if announceFilename and postJsonObject.get('actor'):
if not os.path.isfile(announceFilename + '.tts'): if not os.path.isfile(announceFilename + '.tts'):
updateSpeaker(baseDir, nickname, domain, updateSpeaker(baseDir, httpPrefix,
nickname, domain, domainFull,
postJsonObject, personCache, postJsonObject, personCache,
translate, postJsonObject['actor']) translate, postJsonObject['actor'],
themeName)
ttsFile = open(announceFilename + '.tts', "w+") ttsFile = open(announceFilename + '.tts', "w+")
if ttsFile: if ttsFile:
ttsFile.write('\n') ttsFile.write('\n')
@ -1683,7 +1686,8 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Show an individual post as html """Show an individual post as html
""" """
postStr = '' postStr = ''
@ -1724,7 +1728,7 @@ def htmlIndividualPost(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess, themeName,
False, authorized, False, False, False) False, authorized, False, False, False)
messageId = removeIdEnding(postJsonObject['id']) messageId = removeIdEnding(postJsonObject['id'])
@ -1752,6 +1756,7 @@ def htmlIndividualPost(cssCache: {},
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
False, authorized, False, authorized,
False, False, False) + postStr False, False, False) + postStr
@ -1782,6 +1787,7 @@ def htmlIndividualPost(cssCache: {},
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
False, authorized, False, authorized,
False, False, False) False, False, False)
cssFilename = baseDir + '/epicyon-profile.css' cssFilename = baseDir + '/epicyon-profile.css'
@ -1803,7 +1809,8 @@ def htmlPostReplies(cssCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Show the replies to an individual post as html """Show the replies to an individual post as html
""" """
repliesStr = '' repliesStr = ''
@ -1822,6 +1829,7 @@ def htmlPostReplies(cssCache: {},
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
False, False, False, False, False) False, False, False, False, False)
cssFilename = baseDir + '/epicyon-profile.css' cssFilename = baseDir + '/epicyon-profile.css'

View File

@ -66,7 +66,8 @@ def htmlProfileAfterSearch(cssCache: {},
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
defaultTimeline: str, defaultTimeline: str,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Show a profile page after a search for a fediverse address """Show a profile page after a search for a fediverse address
""" """
if hasUsersPath(profileHandle) or '/@' in profileHandle: if hasUsersPath(profileHandle) or '/@' in profileHandle:
@ -300,6 +301,7 @@ def htmlProfileAfterSearch(cssCache: {},
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, allowLocalNetworkAccess, peertubeInstances, allowLocalNetworkAccess,
themeName,
False, False, False, False, False) False, False, False, False, False)
i += 1 i += 1
if i >= 20: if i >= 20:
@ -804,7 +806,8 @@ def htmlProfile(rssIconAtTop: bool,
YTReplacementDomain, YTReplacementDomain,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess) + licenseStr allowLocalNetworkAccess,
theme) + licenseStr
elif selected == 'following': elif selected == 'following':
profileStr += \ profileStr += \
_htmlProfileFollowing(translate, baseDir, httpPrefix, _htmlProfileFollowing(translate, baseDir, httpPrefix,
@ -856,7 +859,8 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Shows posts on the profile screen """Shows posts on the profile screen
These should only be public posts These should only be public posts
""" """
@ -896,6 +900,7 @@ def _htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
False, False, False, True, False) False, False, False, True, False)
if postStr: if postStr:
profileStr += postStr + separatorStr profileStr += postStr + separatorStr

View File

@ -527,7 +527,8 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Show a page containing search results for your post history """Show a page containing search results for your post history
""" """
if historysearch.startswith('!'): if historysearch.startswith('!'):
@ -604,6 +605,7 @@ def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str,
showPublishedDateOnly, showPublishedDateOnly,
peertubeInstances, peertubeInstances,
allowLocalNetworkAccess, allowLocalNetworkAccess,
themeName,
showIndividualPostIcons, showIndividualPostIcons,
showIndividualPostIcons, showIndividualPostIcons,
False, False, False) False, False, False)
@ -626,7 +628,8 @@ def htmlHashtagSearch(cssCache: {},
YTReplacementDomain: str, YTReplacementDomain: str,
showPublishedDateOnly: bool, showPublishedDateOnly: bool,
peertubeInstances: [], peertubeInstances: [],
allowLocalNetworkAccess: bool) -> str: allowLocalNetworkAccess: bool,
themeName: str) -> str:
"""Show a page containing search results for a hashtag """Show a page containing search results for a hashtag
""" """
if hashtag.startswith('#'): if hashtag.startswith('#'):
@ -778,6 +781,7 @@ def htmlHashtagSearch(cssCache: {},
allowLocalNetworkAccess, allowLocalNetworkAccess,
showRepeats, showIcons, showRepeats, showIcons,
manuallyApprovesFollowers, manuallyApprovesFollowers,
themeName,
showPublicOnly, showPublicOnly,
storeToCache) storeToCache)
if postStr: if postStr:

View File

@ -723,6 +723,7 @@ def htmlTimeline(cssCache: {}, defaultTimeline: str,
boxName != 'dm', boxName != 'dm',
showIndividualPostIcons, showIndividualPostIcons,
manuallyApproveFollowers, manuallyApproveFollowers,
theme,
False, True) False, True)
_logTimelineTiming(enableTimingLog, _logTimelineTiming(enableTimingLog,
timelineStartTime, boxName, '12') timelineStartTime, boxName, '12')