From 5af36bf8175339ae8ea712d1b267982b64d94aa8 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 3 Mar 2021 09:52:38 +0000 Subject: [PATCH 1/5] Add tts endpoint to actor --- person.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/person.py b/person.py index b6bf9baf4..323cb11d1 100644 --- a/person.py +++ b/person.py @@ -276,12 +276,13 @@ def _createPersonBase(baseDir: str, nickname: str, domain: str, port: int, 'devices': personId + '/collections/devices', 'endpoints': { 'id': personId + '/endpoints', - 'sharedInbox': httpPrefix+'://' + domain + '/inbox', + 'sharedInbox': httpPrefix + '://' + domain + '/inbox', }, 'featured': personId + '/collections/featured', 'featuredTags': personId + '/collections/tags', 'followers': personId + '/followers', 'following': personId + '/following', + 'tts': personId + '/speaker', 'shares': personId + '/shares', 'orgSchema': None, 'skills': {}, @@ -556,6 +557,10 @@ def personUpgradeActor(baseDir: str, personJson: {}, personJson = loadJson(filename) if updateActor: + # add a speaker endpoint + if not personJson.get('tts'): + personJson['tts'] = personJson['id'] + '/speaker' + saveJson(personJson, filename) # also update the actor within the cache From 7c20406d3feee08f30f4e7b2df59a536efab20be Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 3 Mar 2021 12:34:46 +0000 Subject: [PATCH 2/5] SSML inbox endpoint --- daemon.py | 46 ++++++++++++++------ inbox.py | 11 ++--- speaker.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 22 deletions(-) diff --git a/daemon.py b/daemon.py index a288e8c87..2ad5ff24e 100644 --- a/daemon.py +++ b/daemon.py @@ -269,6 +269,7 @@ from filters import isFiltered from filters import addGlobalFilter from filters import removeGlobalFilter from context import hasValidContext +from speaker import getSSMLbox import os @@ -487,24 +488,28 @@ class PubServer(BaseHTTPRequestHandler): """ if not self.headers.get('Accept'): return False + acceptStr = self.headers['Accept'] if self.server.debug: - print('ACCEPT: ' + self.headers['Accept']) - if 'image/' in self.headers['Accept']: - if 'text/html' not in self.headers['Accept']: + print('ACCEPT: ' + acceptStr) + if 'application/ssml' in acceptStr: + if 'text/html' not in acceptStr: return False - if 'video/' in self.headers['Accept']: - if 'text/html' not in self.headers['Accept']: + if 'image/' in acceptStr: + if 'text/html' not in acceptStr: return False - if 'audio/' in self.headers['Accept']: - if 'text/html' not in self.headers['Accept']: + if 'video/' in acceptStr: + if 'text/html' not in acceptStr: return False - if self.headers['Accept'].startswith('*'): + if 'audio/' in acceptStr: + if 'text/html' not in acceptStr: + return False + if acceptStr.startswith('*'): if self.headers.get('User-Agent'): if 'ELinks' in self.headers['User-Agent'] or \ 'Lynx' in self.headers['User-Agent']: return True return False - if 'json' in self.headers['Accept']: + if 'json' in acceptStr: return False return True @@ -10480,10 +10485,25 @@ class PubServer(BaseHTTPRequestHandler): # arriving in your inbox if authorized and usersInPath and \ self.path.endswith('/speaker'): - self._getSpeaker(callingDomain, self.path, - self.server.baseDir, - self.server.domain, - self.server.debug) + if 'application/ssml' not in self.headers['Accept']: + # json endpoint + self._getSpeaker(callingDomain, self.path, + self.server.baseDir, + self.server.domain, + self.server.debug) + else: + xmlStr = \ + getSSMLbox(self.server.baseDir, + self.path, self.server.domain, + self.server.systemLanguage, + self.server.instanceTitle, + 'inbox') + if xmlStr: + msg = xmlStr.encode('utf-8') + msglen = len(msg) + self._set_headers('application/xrd+xml', msglen, + None, callingDomain) + self._write(msg) return # redirect to the welcome screen diff --git a/inbox.py b/inbox.py index cf8bae050..49ccf0c44 100644 --- a/inbox.py +++ b/inbox.py @@ -84,6 +84,7 @@ from context import hasValidContext from content import htmlReplaceQuoteMarks from speaker import speakerReplaceLinks from speaker import speakerPronounce +from speaker import speakerEndpointJson def storeHashTags(baseDir: str, nickname: str, postJsonObject: {}) -> None: @@ -2201,13 +2202,9 @@ def _updateSpeaker(baseDir: str, nickname: str, domain: str, announcedHandle = announcedNickname + '@' + announcedDomain content = \ translate['announces'] + ' ' + announcedHandle + '. ' + content - speakerJson = { - "name": speakerName, - "summary": summary, - "say": content, - "imageDescription": imageDescription, - "detectedLinks": detectedLinks - } + speakerJson = speakerEndpointJson(speakerName, summary, + content, imageDescription, + detectedLinks) saveJson(speakerJson, speakerFilename) diff --git a/speaker.py b/speaker.py index 916e0d364..13ed60365 100644 --- a/speaker.py +++ b/speaker.py @@ -10,8 +10,11 @@ import os import random from auth import createBasicAuthHeader from session import getJson +from utils import loadJson from utils import getFullDomain +speakerRemoveChars = ('.\n', '. ', ',', ';', '?', '!') + def getSpeakerPitch(displayName: str, screenreader: str) -> int: """Returns the speech synthesis pitch for the given name @@ -96,9 +99,8 @@ def speakerReplaceLinks(sayText: str, translate: {}, """Replaces any links in the given text with "link to [domain]". Instead of reading out potentially very long and meaningless links """ - removeChars = ('.\n', '. ', ',', ';', '?', '!') text = sayText - for ch in removeChars: + for ch in speakerRemoveChars: text = text.replace(ch, ' ') replacements = {} wordsList = text.split(' ') @@ -136,6 +138,28 @@ def speakerReplaceLinks(sayText: str, translate: {}, return sayText.replace('..', '.') +def _addSSMLemphasis(sayText: str) -> str: + """Adds emphasis to *emphasised* text + """ + if '*' not in sayText: + return sayText + text = sayText + for ch in speakerRemoveChars: + text = text.replace(ch, ' ') + wordsList = text.split(' ') + replacements = {} + for word in wordsList: + if word.startswith('*'): + if word.endswith('*'): + replacements[word] = \ + '' + \ + word.replace('*', '') + \ + '' + for replaceStr, newStr in replacements.items(): + sayText = sayText.replace(replaceStr, newStr) + return sayText + + def getSpeakerFromServer(baseDir: str, session, nickname: str, password: str, domain: str, port: int, @@ -166,3 +190,95 @@ def getSpeakerFromServer(baseDir: str, session, getJson(session, url, headers, None, __version__, httpPrefix, domain) return speakerJson + + +def speakerEndpointJson(displayName: str, summary: str, + content: str, imageDescription: str, + links: []) -> {}: + """Returns a json endpoint for the TTS speaker + """ + return { + "name": displayName, + "summary": summary, + "say": content, + "imageDescription": imageDescription, + "detectedLinks": links + } + + +def _speakerEndpointSSML(displayName: str, summary: str, + content: str, imageDescription: str, + links: [], language: str, + instanceTitle: str, + gender: str) -> str: + """Returns an SSML endpoint for the TTS speaker + https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language + https://www.w3.org/TR/speech-synthesis/ + """ + langShort = 'en' + if language: + langShort = language[:2] + if not gender: + gender = 'neutral' + else: + if langShort == 'en': + gender = gender.lower() + if 'him' in gender or 'male' in gender: + gender = 'male' + elif 'her' in gender or 'she' in gender or \ + 'fem' in gender or 'woman' in gender: + gender = 'female' + elif 'man' in gender: + gender = 'male' + else: + gender = 'neutral' + + content = _addSSMLemphasis(content) + voiceParams = 'name="' + displayName + '" gender="' + gender + '"' + return '\n' + \ + '\n' + \ + ' \n' + \ + ' ' + \ + instanceTitle + ' inbox\n' + \ + ' \n' + \ + '

\n' + \ + ' \n' + \ + ' \n' + \ + ' ' + content + '\n' + \ + ' \n' + \ + ' \n' + \ + '

\n' + \ + '
\n' + + +def getSSMLbox(baseDir: str, path: str, + domain: str, + systemLanguage: str, + instanceTitle: str, + boxName: str) -> str: + """Returns SSML for the given timeline + """ + nickname = path.split('/users/')[1] + if '/' in nickname: + nickname = nickname.split('/')[0] + speakerFilename = \ + baseDir + '/accounts/' + nickname + '@' + domain + '/speaker.json' + if not os.path.isfile(speakerFilename): + return None + speakerJson = loadJson(speakerFilename) + if not speakerJson: + return None + gender = None + if speakerJson.get('gender'): + gender = speakerJson['gender'] + return _speakerEndpointSSML(speakerJson['name'], + speakerJson['summary'], + speakerJson['say'], + speakerJson['imageDescription'], + speakerJson['detectedLinks'], + systemLanguage, + instanceTitle, gender) From d96cdd6c857f12643e4deff0b4840f529476a7ca Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 3 Mar 2021 13:02:47 +0000 Subject: [PATCH 3/5] Gender detaction for SSML --- inbox.py | 4 +++- speaker.py | 14 +++++++------- utils.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/inbox.py b/inbox.py index 49ccf0c44..b7c7b3e1b 100644 --- a/inbox.py +++ b/inbox.py @@ -14,6 +14,7 @@ import html import urllib.parse from linked_data_sig import verifyJsonSignature from utils import getDisplayName +from utils import getGenderFromBio from utils import removeHtml from utils import getConfigParam from utils import hasUsersPath @@ -2194,6 +2195,7 @@ def _updateSpeaker(baseDir: str, nickname: str, domain: str, speakerName = \ getDisplayName(baseDir, postJsonObject['actor'], personCache) + gender = getGenderFromBio(baseDir, postJsonObject['actor'], personCache) if not speakerName: return if announcingActor: @@ -2204,7 +2206,7 @@ def _updateSpeaker(baseDir: str, nickname: str, domain: str, translate['announces'] + ' ' + announcedHandle + '. ' + content speakerJson = speakerEndpointJson(speakerName, summary, content, imageDescription, - detectedLinks) + detectedLinks, gender) saveJson(speakerJson, speakerFilename) diff --git a/speaker.py b/speaker.py index 13ed60365..cdccc1b3c 100644 --- a/speaker.py +++ b/speaker.py @@ -194,16 +194,19 @@ def getSpeakerFromServer(baseDir: str, session, def speakerEndpointJson(displayName: str, summary: str, content: str, imageDescription: str, - links: []) -> {}: + links: [], gender: str) -> {}: """Returns a json endpoint for the TTS speaker """ - return { + speakerJson = { "name": displayName, "summary": summary, "say": content, "imageDescription": imageDescription, "detectedLinks": links } + if gender: + speakerJson['gender'] = gender + return speakerJson def _speakerEndpointSSML(displayName: str, summary: str, @@ -223,13 +226,10 @@ def _speakerEndpointSSML(displayName: str, summary: str, else: if langShort == 'en': gender = gender.lower() - if 'him' in gender or 'male' in gender: + if 'he/him' in gender: gender = 'male' - elif 'her' in gender or 'she' in gender or \ - 'fem' in gender or 'woman' in gender: + elif 'she/her' in gender: gender = 'female' - elif 'man' in gender: - gender = 'male' else: gender = 'neutral' diff --git a/utils.py b/utils.py index b0594c7ae..9d467467d 100644 --- a/utils.py +++ b/utils.py @@ -669,6 +669,41 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str: return nameFound +def getGenderFromBio(baseDir: str, actor: str, personCache: {}) -> str: + """Tries to ascertain gender from bio description + """ + if '/statuses/' in actor: + actor = actor.split('/statuses/')[0] + if not personCache.get(actor): + return None + bioFound = None + if personCache[actor].get('actor'): + if personCache[actor]['actor'].get('summary'): + bioFound = personCache[actor]['actor']['summary'] + else: + # Try to obtain from the cached actors + cachedActorFilename = \ + baseDir + '/cache/actors/' + (actor.replace('/', '#')) + '.json' + if os.path.isfile(cachedActorFilename): + actorJson = loadJson(cachedActorFilename, 1) + if actorJson: + if actorJson.get('summary'): + bioFound = actorJson['summary'] + if not bioFound: + return None + gender = 'They/Them' + bioFoundOrig = bioFound + bioFound = bioFound.lower() + if 'him' in bioFound or 'male' in bioFound: + gender = 'He/Him' + elif 'her' in bioFound or 'she' in bioFound or \ + 'fem' in bioFound or 'woman' in bioFound: + gender = 'She/Her' + elif 'man' in bioFound or 'He' in bioFoundOrig: + gender = 'He/Him' + return gender + + def getNicknameFromActor(actor: str) -> str: """Returns the nickname from an actor url """ From 5b2986d68505e8059f2a9867a77d6465baffa9b8 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 3 Mar 2021 13:37:18 +0000 Subject: [PATCH 4/5] Gender detection from profile tag --- inbox.py | 3 ++- translations/ar.json | 5 ++++- translations/ca.json | 5 ++++- translations/cy.json | 5 ++++- translations/de.json | 5 ++++- translations/en.json | 5 ++++- translations/es.json | 5 ++++- translations/fr.json | 5 ++++- translations/ga.json | 5 ++++- translations/hi.json | 5 ++++- translations/it.json | 5 ++++- translations/ja.json | 5 ++++- translations/oc.json | 5 ++++- translations/pt.json | 5 ++++- translations/ru.json | 5 ++++- translations/zh.json | 5 ++++- utils.py | 41 +++++++++++++++++++++++++++++++++++++---- 17 files changed, 99 insertions(+), 20 deletions(-) diff --git a/inbox.py b/inbox.py index b7c7b3e1b..80481c66f 100644 --- a/inbox.py +++ b/inbox.py @@ -2195,7 +2195,8 @@ def _updateSpeaker(baseDir: str, nickname: str, domain: str, speakerName = \ getDisplayName(baseDir, postJsonObject['actor'], personCache) - gender = getGenderFromBio(baseDir, postJsonObject['actor'], personCache) + gender = getGenderFromBio(baseDir, postJsonObject['actor'], + personCache, translate) if not speakerName: return if announcingActor: diff --git a/translations/ar.json b/translations/ar.json index 54f4adbac..0027e6346 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -381,5 +381,8 @@ "mentioning": "ذكر", "sad face": "وجه حزين", "thinking emoji": "التفكير الرموز التعبيرية", - "laughing": "يضحك" + "laughing": "يضحك", + "gender": "جنس تذكير أو تأنيث", + "He/Him": "هو", + "She/Her": "هي" } diff --git a/translations/ca.json b/translations/ca.json index 5112f57ab..5ad1772f0 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -381,5 +381,8 @@ "mentioning": "esmentant", "sad face": "cara trista", "thinking emoji": "emoji pensant", - "laughing": "rient" + "laughing": "rient", + "gender": "gènere", + "He/Him": "Ell", + "She/Her": "Ella" } diff --git a/translations/cy.json b/translations/cy.json index 8decbe17b..c8c2dfff5 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -381,5 +381,8 @@ "mentioning": "sôn", "sad face": "wyneb trist", "thinking emoji": "meddwl emoji", - "laughing": "chwerthin" + "laughing": "chwerthin", + "gender": "rhyw", + "He/Him": "Ef", + "She/Her": "Hi/Ei" } diff --git a/translations/de.json b/translations/de.json index 46bb450d8..aef2f428a 100644 --- a/translations/de.json +++ b/translations/de.json @@ -381,5 +381,8 @@ "mentioning": "Erwähnen", "sad face": "trauriges Gesicht", "thinking emoji": "Emowji denken", - "laughing": "Lachen" + "laughing": "Lachen", + "gender": "geschlecht", + "He/Him": "Er/ihm", + "She/Her": "Sie" } diff --git a/translations/en.json b/translations/en.json index 47997a169..eb8743f40 100644 --- a/translations/en.json +++ b/translations/en.json @@ -381,5 +381,8 @@ "mentioning": "mentioning", "sad face": "sad face", "thinking emoji": "thinking emowji", - "laughing": "laughing" + "laughing": "laughing", + "gender": "gender", + "He/Him": "He/Him", + "She/Her": "She/Her" } diff --git a/translations/es.json b/translations/es.json index bb12926f7..10c2729ea 100644 --- a/translations/es.json +++ b/translations/es.json @@ -381,5 +381,8 @@ "mentioning": "mencionar", "sad face": "cara triste", "thinking emoji": "pensando emowji", - "laughing": "risa" + "laughing": "risa", + "gender": "género", + "He/Him": "El", + "She/Her": "Ella" } diff --git a/translations/fr.json b/translations/fr.json index 84cd8b145..f959f284e 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -381,5 +381,8 @@ "mentioning": "mentionnant", "sad face": "visage triste", "thinking emoji": "penser emowji", - "laughing": "en riant" + "laughing": "en riant", + "gender": "le genre", + "He/Him": "Il/Lui", + "She/Her": "Elle" } diff --git a/translations/ga.json b/translations/ga.json index 123ef8c52..315448826 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -381,5 +381,8 @@ "mentioning": "ag lua", "sad face": "aghaidh brónach", "thinking emoji": "ag smaoineamh emowji", - "laughing": "ag gáire" + "laughing": "ag gáire", + "gender": "inscne", + "He/Him": "Sé/Eisean", + "She/Her": "Sí" } diff --git a/translations/hi.json b/translations/hi.json index ecb575420..f7ed05f0d 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -381,5 +381,8 @@ "mentioning": "उल्लेख", "sad face": "उदास चेहरा", "thinking emoji": "सोच रहे हैं इमोजी", - "laughing": "हस रहा" + "laughing": "हस रहा", + "gender": "लिंग", + "He/Him": "वह/उसे", + "She/Her": "वह/उसकी" } diff --git a/translations/it.json b/translations/it.json index e5a999fe6..52bb3e58f 100644 --- a/translations/it.json +++ b/translations/it.json @@ -381,5 +381,8 @@ "mentioning": "menzionando", "sad face": "faccia triste", "thinking emoji": "pensiero emoji", - "laughing": "ridendo" + "laughing": "ridendo", + "gender": "genere", + "He/Him": "Lui", + "She/Her": "Lei" } diff --git a/translations/ja.json b/translations/ja.json index efdc45a58..967673668 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -381,5 +381,8 @@ "mentioning": "言及する", "sad face": "悲しい顔", "thinking emoji": "絵文字を考える", - "laughing": "笑い" + "laughing": "笑い", + "gender": "性別", + "He/Him": "彼", + "She/Her": "彼女" } diff --git a/translations/oc.json b/translations/oc.json index 522ac2cdf..3d008db27 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -377,5 +377,8 @@ "mentioning": "mentioning", "sad face": "sad face", "thinking emoji": "thinking emowji", - "laughing": "laughing" + "laughing": "laughing", + "gender": "gender", + "He/Him": "He/Him", + "She/Her": "She/Her" } diff --git a/translations/pt.json b/translations/pt.json index 42414a9fc..e62387eec 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -381,5 +381,8 @@ "mentioning": "mencionando", "sad face": "rosto triste", "thinking emoji": "pensando emowji", - "laughing": "rindo" + "laughing": "rindo", + "gender": "gênero", + "He/Him": "Ele", + "She/Her": "Ela" } diff --git a/translations/ru.json b/translations/ru.json index a9e14625f..806da91da 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -381,5 +381,8 @@ "mentioning": "упоминание", "sad face": "грустное лицо", "thinking emoji": "думающий смайлик", - "laughing": "смеющийся" + "laughing": "смеющийся", + "gender": "Пол", + "He/Him": "Он/Его", + "She/Her": "Она/Ее" } diff --git a/translations/zh.json b/translations/zh.json index d7af314c0..5f149d144 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -381,5 +381,8 @@ "mentioning": "提及", "sad face": "悲伤的脸", "thinking emoji": "思维表情符号", - "laughing": "笑" + "laughing": "笑", + "gender": "", + "He/Him": "", + "She/Her": "" } diff --git a/utils.py b/utils.py index 9d467467d..9ccef742a 100644 --- a/utils.py +++ b/utils.py @@ -669,7 +669,8 @@ def getDisplayName(baseDir: str, actor: str, personCache: {}) -> str: return nameFound -def getGenderFromBio(baseDir: str, actor: str, personCache: {}) -> str: +def getGenderFromBio(baseDir: str, actor: str, personCache: {}, + translate: {}) -> str: """Tries to ascertain gender from bio description """ if '/statuses/' in actor: @@ -678,7 +679,21 @@ def getGenderFromBio(baseDir: str, actor: str, personCache: {}) -> str: return None bioFound = None if personCache[actor].get('actor'): - if personCache[actor]['actor'].get('summary'): + # is gender defined as a profile tag? + if personCache[actor]['actor'].get('attachment'): + tagsList = personCache[actor]['actor']['attachment'] + if isinstance(tagsList, list): + for tag in tagsList: + if not isinstance(tag, dict): + continue + if not tag.get('name') or not tag.get('value'): + continue + if tag['name'].lower() == \ + translate['gender'].lower(): + bioFound = tag['value'] + break + # if not then use the bio + if not bioFound and personCache[actor]['actor'].get('summary'): bioFound = personCache[actor]['actor']['summary'] else: # Try to obtain from the cached actors @@ -687,14 +702,32 @@ def getGenderFromBio(baseDir: str, actor: str, personCache: {}) -> str: if os.path.isfile(cachedActorFilename): actorJson = loadJson(cachedActorFilename, 1) if actorJson: - if actorJson.get('summary'): + # is gender defined as a profile tag? + if actorJson.get('attachment'): + tagsList = actorJson['attachment'] + if isinstance(tagsList, list): + for tag in tagsList: + if not isinstance(tag, dict): + continue + if not tag.get('name') or not tag.get('value'): + continue + if tag['name'].lower() == \ + translate['gender'].lower(): + bioFound = tag['value'] + break + # if not then use the bio + if not bioFound and actorJson.get('summary'): bioFound = actorJson['summary'] if not bioFound: return None gender = 'They/Them' bioFoundOrig = bioFound bioFound = bioFound.lower() - if 'him' in bioFound or 'male' in bioFound: + if translate['He/Him'] in bioFound: + gender = 'He/Him' + elif translate['She/Her'] in bioFound: + gender = 'She/Her' + elif 'him' in bioFound or 'male' in bioFound: gender = 'He/Him' elif 'her' in bioFound or 'she' in bioFound or \ 'fem' in bioFound or 'woman' in bioFound: From d9ee131302443dda5813a02ad1f2f6b2b7c1f83f Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 3 Mar 2021 13:39:39 +0000 Subject: [PATCH 5/5] Check that directory exists --- theme.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/theme.py b/theme.py index de444a90f..3a8d9c897 100644 --- a/theme.py +++ b/theme.py @@ -68,8 +68,9 @@ def _copyThemeHelpFiles(baseDir: str, themeName: str, if destHelpMarkdownFile == 'profile.md' or \ destHelpMarkdownFile == 'final.md': destHelpMarkdownFile = 'welcome_' + destHelpMarkdownFile - copyfile(themeDir + '/' + helpMarkdownFile, - baseDir + '/accounts/' + destHelpMarkdownFile) + if os.path.isdir(baseDir + '/accounts'): + copyfile(themeDir + '/' + helpMarkdownFile, + baseDir + '/accounts/' + destHelpMarkdownFile) break @@ -659,6 +660,8 @@ def _setClearCacheFlag(baseDir: str) -> None: """Sets a flag which can be used by an external system (eg. a script in a cron job) to clear the browser cache """ + if not os.path.isdir(baseDir + '/accounts'): + return flagFilename = baseDir + '/accounts/.clear_cache' with open(flagFilename, 'w+') as flagFile: flagFile.write('\n')