diff --git a/content.py b/content.py index bc3c9af4a..2d2bc7d88 100644 --- a/content.py +++ b/content.py @@ -14,6 +14,54 @@ from utils import fileLastModified from utils import getLinkPrefixes +def htmlReplaceQuoteMarks(content: str) -> str: + """Replaces quotes with html formatting + "hello" becomes hello + """ + if '"' not in content: + if '"' not in content: + return content + + newContent = content + if '"' in content: + sections = content.split('"') + if len(sections) > 1: + newContent = '' + openQuote = True + markup = False + for ch in content: + currChar = ch + if ch == '<': + markup = True + elif ch == '>': + markup = False + elif ch == '"' and not markup: + if openQuote: + currChar = '“' + else: + currChar = '”' + openQuote = not openQuote + newContent += currChar + + if '"' in newContent: + openQuote = True + content = newContent + newContent = '' + ctr = 0 + sections = content.split('"') + noOfSections = len(sections) + for s in sections: + newContent += s + if ctr < noOfSections - 1: + if openQuote: + newContent += '“' + else: + newContent += '”' + openQuote = not openQuote + ctr += 1 + return newContent + + def dangerousMarkup(content: str) -> bool: """Returns true if the given content contains dangerous html markup """ @@ -433,6 +481,7 @@ def removeHtml(content: str) -> str: if '<' not in content: return content removing = False + content = content.replace('', '"').replace('', '"') result = '' for ch in content: if ch == '<': @@ -525,7 +574,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str, by matching against known following accounts """ if content.startswith('

'): - return content + return htmlReplaceQuoteMarks(content) maxWordLength = 40 content = content.replace('\r', '') content = content.replace('\n', ' --linebreak-- ') @@ -608,7 +657,7 @@ def addHtmlTags(baseDir: str, httpPrefix: str, if longWordsList: content = removeLongWords(content, maxWordLength, longWordsList) content = content.replace(' --linebreak-- ', '

') - return '

' + content + '

' + return '

' + htmlReplaceQuoteMarks(content) + '

' def getMentionsFromHtml(htmlText: str, diff --git a/daemon.py b/daemon.py index 9d21dfd7b..864f69f17 100644 --- a/daemon.py +++ b/daemon.py @@ -928,7 +928,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.personCache, self.server.allowDeletion, self.server.proxyType, version, - self.server.debug) + self.server.debug, + self.server.YTReplacementDomain) def _postToOutboxThread(self, messageJson: {}) -> bool: """Creates a thread to send a post @@ -2266,7 +2267,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.cachedWebfingers, self.server.personCache, self.server.httpPrefix, - self.server.projectVersion) + self.server.projectVersion, + self.server.YTReplacementDomain) if hashtagStr: msg = hashtagStr.encode('utf-8') self._set_headers('text/html', len(msg), @@ -3145,7 +3147,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.baseDir, deleteUrl, self.server.httpPrefix, __version__, self.server.cachedWebfingers, - self.server.personCache, callingDomain) + self.server.personCache, callingDomain, + self.server.TYReplacementDomain) if deleteStr: self._set_headers('text/html', len(deleteStr), cookie, callingDomain) @@ -3478,6 +3481,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix projectVersion = \ self.server.projectVersion + ytDomain = \ + self.server.YTReplacementDomain msg = \ htmlIndividualPost(recentPostsCache, maxRecentPosts, @@ -3492,7 +3497,8 @@ class PubServer(BaseHTTPRequestHandler): postJsonObject, httpPrefix, projectVersion, - likedBy) + likedBy, + ytDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), cookie, callingDomain) @@ -3606,6 +3612,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix projectVersion = \ self.server.projectVersion + ytDomain = \ + self.server.YTReplacementDomain msg = \ htmlPostReplies(recentPostsCache, maxRecentPosts, @@ -3619,7 +3627,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.port, repliesJson, httpPrefix, - projectVersion) + projectVersion, + ytDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -3707,6 +3716,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix projectVersion = \ self.server.projectVersion + ytDomain = \ + self.server.YTReplacementDomain msg = \ htmlPostReplies(recentPostsCache, maxRecentPosts, @@ -3720,7 +3731,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.port, repliesJson, httpPrefix, - projectVersion) + projectVersion, + ytDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -3771,6 +3783,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.recentPostsCache cachedWebfingers = \ self.server.cachedWebfingers + YTReplacementDomain = \ + self.server.YTReplacementDomain msg = \ htmlProfile(defaultTimeline, recentPostsCache, @@ -3785,6 +3799,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, cachedWebfingers, self.server.personCache, + YTReplacementDomain, actorJson['roles'], None, None) msg = msg.encode('utf-8') @@ -3831,6 +3846,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.recentPostsCache cachedWebfingers = \ self.server.cachedWebfingers + YTReplacementDomain = \ + self.server.YTReplacementDomain msg = \ htmlProfile(defaultTimeline, recentPostsCache, @@ -3845,6 +3862,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, cachedWebfingers, self.server.personCache, + YTReplacementDomain, actorJson['skills'], None, None) msg = msg.encode('utf-8') @@ -3937,6 +3955,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.httpPrefix projectVersion = \ self.server.projectVersion + ytDomain = \ + self.server.YTReplacementDomain msg = \ htmlIndividualPost(recentPostsCache, maxRecentPosts, @@ -3952,7 +3972,8 @@ class PubServer(BaseHTTPRequestHandler): postJsonObject, httpPrefix, projectVersion, - likedBy) + likedBy, + ytDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4035,7 +4056,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4126,7 +4148,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4216,7 +4239,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4307,7 +4331,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4396,7 +4421,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4461,7 +4487,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.port, self.server.allowDeletion, self.server.httpPrefix, - self.server.projectVersion) + self.server.projectVersion, + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4538,7 +4565,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4624,7 +4652,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.allowDeletion, self.server.httpPrefix, self.server.projectVersion, - self._isMinimal(nickname)) + self._isMinimal(nickname), + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4701,7 +4730,8 @@ class PubServer(BaseHTTPRequestHandler): moderationFeed, True, self.server.httpPrefix, - self.server.projectVersion) + self.server.projectVersion, + self.server.YTReplacementDomain) msg = msg.encode('utf-8') self._set_headers('text/html', len(msg), @@ -4789,6 +4819,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.cachedWebfingers, self.server.personCache, + self.server.YTReplacementDomain, shares, pageNumber, sharesPerPage) msg = msg.encode('utf-8') @@ -4869,6 +4900,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.cachedWebfingers, self.server.personCache, + self.server.YTReplacementDomain, following, pageNumber, followsPerPage).encode('utf-8') @@ -4947,6 +4979,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.cachedWebfingers, self.server.personCache, + self.server.YTReplacementDomain, followers, pageNumber, followsPerPage).encode('utf-8') @@ -5001,6 +5034,7 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.cachedWebfingers, self.server.personCache, + self.server.YTReplacementDomain, None, None).encode('utf-8') self._set_headers('text/html', len(msg), @@ -5379,7 +5413,8 @@ class PubServer(BaseHTTPRequestHandler): imgDescription, self.server.useBlurHash) - replaceYouTube(postJsonObject) + replaceYouTube(postJsonObject, + self.server.YTReplacementDomain) saveJson(postJsonObject, postFilename) print('Edited blog post, resaved ' + postFilename) return 1 @@ -6279,6 +6314,26 @@ class PubServer(BaseHTTPRequestHandler): setConfigParam(self.server.baseDir, 'instanceTitle', fields['instanceTitle']) + + if fields.get('ytdomain'): + currYTDomain = self.server.YTReplacementDomain + if fields['ytdomain'] != currYTDomain: + newYTDomain = fields['ytdomain'] + if '://' in newYTDomain: + newYTDomain = newYTDomain.split('://')[1] + if '/' in newYTDomain: + newYTDomain = newYTDomain.split('/')[0] + if '.' in newYTDomain: + setConfigParam(self.server.baseDir, + 'youtubedomain', + newYTDomain) + self.server.YTReplacementDomain = \ + newYTDomain + else: + setConfigParam(self.server.baseDir, + 'youtubedomain', '') + self.server.YTReplacementDomain = None + currInstanceDescriptionShort = \ getConfigParam(self.server.baseDir, 'instanceDescriptionShort') @@ -6933,7 +6988,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.cachedWebfingers, self.server.personCache, self.server.httpPrefix, - self.server.projectVersion) + self.server.projectVersion, + self.server.YTReplacementDomain) if hashtagStr: msg = hashtagStr.encode('utf-8') self._login_headers('text/html', @@ -6977,7 +7033,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.session, self.server.cachedWebfingers, self.server.personCache, - self.server.port) + self.server.port, + self.server.YTReplacementDomain) if historyStr: msg = historyStr.encode('utf-8') self._login_headers('text/html', @@ -7014,7 +7071,8 @@ class PubServer(BaseHTTPRequestHandler): self.server.cachedWebfingers, self.server.personCache, self.server.debug, - self.server.projectVersion) + self.server.projectVersion, + self.server.YTReplacementDomain) if profileStr: msg = profileStr.encode('utf-8') self._login_headers('text/html', @@ -7758,7 +7816,7 @@ class PubServer(BaseHTTPRequestHandler): msg = \ htmlUnblockConfirm(self.server.translate, self.server.baseDir, - originPathStr, + usersPath, optionsActor, optionsAvatarUrl).encode('utf-8') self._set_headers('text/html', len(msg), @@ -7772,7 +7830,7 @@ class PubServer(BaseHTTPRequestHandler): msg = \ htmlFollowConfirm(self.server.translate, self.server.baseDir, - originPathStr, + usersPath, optionsActor, optionsAvatarUrl).encode('utf-8') self._set_headers('text/html', len(msg), @@ -8312,6 +8370,7 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool, instanceId: str, clientToServer: bool, baseDir: str, domain: str, onionDomain: str, i2pDomain: str, + YTReplacementDomain: str, port=80, proxyPort=80, httpPrefix='https', fedList=[], maxMentions=10, maxEmoji=10, authenticatedFetch=False, @@ -8348,6 +8407,8 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool, print('ERROR: HTTP server failed to start. ' + str(e)) return False + httpd.YTReplacementDomain = YTReplacementDomain + # This counter is used to update the list of blocked domains in memory. # It helps to avoid touching the disk and so improves flooding resistance httpd.blocklistUpdateCtr = 0 @@ -8535,8 +8596,9 @@ def runDaemon(blogsInstance: bool, mediaInstance: bool, httpd.ocapAlways, maxReplies, domainMaxPostsPerDay, accountMaxPostsPerDay, allowDeletion, debug, maxMentions, maxEmoji, - httpd.translate, - unitTest, httpd.acceptedCaps), daemon=True) + httpd.translate, unitTest, + httpd.YTReplacementDomain, + httpd.acceptedCaps), daemon=True) print('Creating scheduled post thread') httpd.thrPostSchedule = \ threadWithTrace(target=runPostSchedule, diff --git a/epicyon-profile.css b/epicyon-profile.css index ef1796cc0..7cb709598 100644 --- a/epicyon-profile.css +++ b/epicyon-profile.css @@ -50,6 +50,7 @@ --button-corner-radius: 15px; --timeline-border-radius: 30px; --icons-side: right; + --title-color: #999; } @font-face { @@ -72,6 +73,10 @@ body, html { font-size: var(--font-size); } +h1 { + color: var(--title-color); +} + a, u { color: var(--main-fg-color); } diff --git a/epicyon.py b/epicyon.py index a6a8bd900..1ca654c63 100644 --- a/epicyon.py +++ b/epicyon.py @@ -115,6 +115,9 @@ parser.add_argument('--proxy', dest='proxyPort', type=int, default=None, parser.add_argument('--path', dest='baseDir', type=str, default=os.getcwd(), help='Directory in which to store posts') +parser.add_argument('--ytdomain', dest='YTReplacementDomain', + type=str, default=None, + help='Domain used to replace youtube.com') parser.add_argument('--language', dest='language', type=str, default=None, help='Language code, eg. en/fr/de/es') @@ -1791,6 +1794,15 @@ registration = getConfigParam(baseDir, 'registration') if not registration: registration = False +YTDomain = getConfigParam(baseDir, 'youtubedomain') +if YTDomain: + if '://' in YTDomain: + YTDomain = YTDomain.split('://')[1] + if '/' in YTDomain: + YTDomain = YTDomain.split('/')[0] + if '.' in YTDomain: + args.YTReplacementDomain = YTDomain + if setTheme(baseDir, themeName): print('Theme set to ' + themeName) @@ -1800,6 +1812,7 @@ runDaemon(args.blogsinstance, args.mediainstance, registration, args.language, __version__, instanceId, args.client, baseDir, domain, onionDomain, i2pDomain, + args.YTReplacementDomain, port, proxyPort, httpPrefix, federationList, args.maxMentions, args.maxEmoji, args.authenticatedFetch, diff --git a/fonts/LICENSES b/fonts/LICENSES index 804929c7f..93b7cd42a 100644 --- a/fonts/LICENSES +++ b/fonts/LICENSES @@ -14,6 +14,7 @@ LcdSolid is public domain. See https://www.fontspace.com/lcd-solid-font-f11346 MarginaliaRegular is public domain. See https://www.fontspace.com/marginalia-font-f32466 Octavius is created by Jack Oatley and described as "100% free to use, though credit is appreciated" https://www.dafont.com/octavius.font RailModel is GPL. See https://www.fontspace.com/rail-model-font-f10741 +Solidaric by Bob Mottram is under AGPL SubZER0 is under GPL. See http://www.free-fonts-download.com/Techno/subzer0-font SundownerRegular is public domain. See https://www.fontspace.com/sundowner-font-f40837 Warenhaus-Standard is public domain. See https://fontlibrary.org/en/font/warenhaus-typenhebel diff --git a/fonts/solidaric-italic.woff2 b/fonts/solidaric-italic.woff2 new file mode 100644 index 000000000..c70cdcb7e Binary files /dev/null and b/fonts/solidaric-italic.woff2 differ diff --git a/fonts/solidaric.woff2 b/fonts/solidaric.woff2 new file mode 100644 index 000000000..0fda1beef Binary files /dev/null and b/fonts/solidaric.woff2 differ diff --git a/img/banner_solidaric.png b/img/banner_solidaric.png new file mode 100644 index 000000000..dda908424 Binary files /dev/null and b/img/banner_solidaric.png differ diff --git a/img/icons/light/calendar_notify.png b/img/icons/light/calendar_notify.png index a8bb393b4..c364c3109 100644 Binary files a/img/icons/light/calendar_notify.png and b/img/icons/light/calendar_notify.png differ diff --git a/img/icons/light/person.png b/img/icons/light/person.png index d68b26f68..0241ae867 100644 Binary files a/img/icons/light/person.png and b/img/icons/light/person.png differ diff --git a/img/icons/light/repeat.png b/img/icons/light/repeat.png index ee4932be2..e2daf8082 100644 Binary files a/img/icons/light/repeat.png and b/img/icons/light/repeat.png differ diff --git a/img/icons/solidaric/add.png b/img/icons/solidaric/add.png new file mode 100644 index 000000000..c1949509e Binary files /dev/null and b/img/icons/solidaric/add.png differ diff --git a/img/icons/solidaric/bookmark.png b/img/icons/solidaric/bookmark.png new file mode 100644 index 000000000..c26d0af72 Binary files /dev/null and b/img/icons/solidaric/bookmark.png differ diff --git a/img/icons/solidaric/bookmark_inactive.png b/img/icons/solidaric/bookmark_inactive.png new file mode 100644 index 000000000..19139dca9 Binary files /dev/null and b/img/icons/solidaric/bookmark_inactive.png differ diff --git a/img/icons/solidaric/calendar.png b/img/icons/solidaric/calendar.png new file mode 100644 index 000000000..7d951c55e Binary files /dev/null and b/img/icons/solidaric/calendar.png differ diff --git a/img/icons/solidaric/calendar_notify.png b/img/icons/solidaric/calendar_notify.png new file mode 100644 index 000000000..c364c3109 Binary files /dev/null and b/img/icons/solidaric/calendar_notify.png differ diff --git a/img/icons/solidaric/delete.png b/img/icons/solidaric/delete.png new file mode 100644 index 000000000..51cd1539a Binary files /dev/null and b/img/icons/solidaric/delete.png differ diff --git a/img/icons/solidaric/dm.png b/img/icons/solidaric/dm.png new file mode 100644 index 000000000..369fffccd Binary files /dev/null and b/img/icons/solidaric/dm.png differ diff --git a/img/icons/solidaric/download.png b/img/icons/solidaric/download.png new file mode 100644 index 000000000..25820d8b7 Binary files /dev/null and b/img/icons/solidaric/download.png differ diff --git a/img/icons/solidaric/edit.png b/img/icons/solidaric/edit.png new file mode 100644 index 000000000..d29dd278f Binary files /dev/null and b/img/icons/solidaric/edit.png differ diff --git a/img/icons/solidaric/like.png b/img/icons/solidaric/like.png new file mode 100644 index 000000000..843103d05 Binary files /dev/null and b/img/icons/solidaric/like.png differ diff --git a/img/icons/solidaric/like_inactive.png b/img/icons/solidaric/like_inactive.png new file mode 100644 index 000000000..7dfca3224 Binary files /dev/null and b/img/icons/solidaric/like_inactive.png differ diff --git a/img/icons/solidaric/mute.png b/img/icons/solidaric/mute.png new file mode 100644 index 000000000..01baf4699 Binary files /dev/null and b/img/icons/solidaric/mute.png differ diff --git a/img/icons/solidaric/new.png b/img/icons/solidaric/new.png new file mode 100644 index 000000000..c6c834eb8 Binary files /dev/null and b/img/icons/solidaric/new.png differ diff --git a/img/icons/solidaric/newpost.png b/img/icons/solidaric/newpost.png new file mode 100644 index 000000000..1a4febb3c Binary files /dev/null and b/img/icons/solidaric/newpost.png differ diff --git a/img/icons/solidaric/pagedown.png b/img/icons/solidaric/pagedown.png new file mode 100644 index 000000000..ae671efd1 Binary files /dev/null and b/img/icons/solidaric/pagedown.png differ diff --git a/img/icons/solidaric/pageup.png b/img/icons/solidaric/pageup.png new file mode 100644 index 000000000..d32dbdc3f Binary files /dev/null and b/img/icons/solidaric/pageup.png differ diff --git a/img/icons/solidaric/person.png b/img/icons/solidaric/person.png new file mode 100644 index 000000000..07196aab5 Binary files /dev/null and b/img/icons/solidaric/person.png differ diff --git a/img/icons/solidaric/prev.png b/img/icons/solidaric/prev.png new file mode 100644 index 000000000..cbbbc2417 Binary files /dev/null and b/img/icons/solidaric/prev.png differ diff --git a/img/icons/solidaric/qrcode.png b/img/icons/solidaric/qrcode.png new file mode 100644 index 000000000..933a2671c Binary files /dev/null and b/img/icons/solidaric/qrcode.png differ diff --git a/img/icons/solidaric/repeat.png b/img/icons/solidaric/repeat.png new file mode 100644 index 000000000..e2daf8082 Binary files /dev/null and b/img/icons/solidaric/repeat.png differ diff --git a/img/icons/solidaric/repeat_inactive.png b/img/icons/solidaric/repeat_inactive.png new file mode 100644 index 000000000..3dc7f25e8 Binary files /dev/null and b/img/icons/solidaric/repeat_inactive.png differ diff --git a/img/icons/solidaric/reply.png b/img/icons/solidaric/reply.png new file mode 100644 index 000000000..325ae10ee Binary files /dev/null and b/img/icons/solidaric/reply.png differ diff --git a/img/icons/solidaric/rss.png b/img/icons/solidaric/rss.png new file mode 100644 index 000000000..533453fc7 Binary files /dev/null and b/img/icons/solidaric/rss.png differ diff --git a/img/icons/solidaric/rss3.png b/img/icons/solidaric/rss3.png new file mode 100644 index 000000000..83521cd1b Binary files /dev/null and b/img/icons/solidaric/rss3.png differ diff --git a/img/icons/solidaric/scope_blog.png b/img/icons/solidaric/scope_blog.png new file mode 100644 index 000000000..d98c0e894 Binary files /dev/null and b/img/icons/solidaric/scope_blog.png differ diff --git a/img/icons/solidaric/scope_dm.png b/img/icons/solidaric/scope_dm.png new file mode 100644 index 000000000..571e4042b Binary files /dev/null and b/img/icons/solidaric/scope_dm.png differ diff --git a/img/icons/solidaric/scope_followers.png b/img/icons/solidaric/scope_followers.png new file mode 100644 index 000000000..e09c45019 Binary files /dev/null and b/img/icons/solidaric/scope_followers.png differ diff --git a/img/icons/solidaric/scope_public.png b/img/icons/solidaric/scope_public.png new file mode 100644 index 000000000..e6e70adef Binary files /dev/null and b/img/icons/solidaric/scope_public.png differ diff --git a/img/icons/solidaric/scope_question.png b/img/icons/solidaric/scope_question.png new file mode 100644 index 000000000..846932693 Binary files /dev/null and b/img/icons/solidaric/scope_question.png differ diff --git a/img/icons/solidaric/scope_reminder.png b/img/icons/solidaric/scope_reminder.png new file mode 100644 index 000000000..62c6c06cf Binary files /dev/null and b/img/icons/solidaric/scope_reminder.png differ diff --git a/img/icons/solidaric/scope_report.png b/img/icons/solidaric/scope_report.png new file mode 100644 index 000000000..87deb9dcc Binary files /dev/null and b/img/icons/solidaric/scope_report.png differ diff --git a/img/icons/solidaric/scope_share.png b/img/icons/solidaric/scope_share.png new file mode 100644 index 000000000..616ceb42b Binary files /dev/null and b/img/icons/solidaric/scope_share.png differ diff --git a/img/icons/solidaric/scope_unlisted.png b/img/icons/solidaric/scope_unlisted.png new file mode 100644 index 000000000..cf6d97286 Binary files /dev/null and b/img/icons/solidaric/scope_unlisted.png differ diff --git a/img/icons/solidaric/search.png b/img/icons/solidaric/search.png new file mode 100644 index 000000000..18b7b05da Binary files /dev/null and b/img/icons/solidaric/search.png differ diff --git a/img/icons/solidaric/showhide.png b/img/icons/solidaric/showhide.png new file mode 100644 index 000000000..8dc2308de Binary files /dev/null and b/img/icons/solidaric/showhide.png differ diff --git a/img/icons/solidaric/unmute.png b/img/icons/solidaric/unmute.png new file mode 100644 index 000000000..eb9af71d2 Binary files /dev/null and b/img/icons/solidaric/unmute.png differ diff --git a/img/image_solidaric.png b/img/image_solidaric.png new file mode 100644 index 000000000..53e1ab442 Binary files /dev/null and b/img/image_solidaric.png differ diff --git a/img/login_background_purple.jpg b/img/login_background_purple.jpg new file mode 100644 index 000000000..8943113d2 Binary files /dev/null and b/img/login_background_purple.jpg differ diff --git a/img/login_background_solidaric.jpg b/img/login_background_solidaric.jpg new file mode 100644 index 000000000..39f8528d1 Binary files /dev/null and b/img/login_background_solidaric.jpg differ diff --git a/img/options_background_purple.jpg b/img/options_background_purple.jpg new file mode 100644 index 000000000..e6fa53ae1 Binary files /dev/null and b/img/options_background_purple.jpg differ diff --git a/img/options_background_solidaric.jpg b/img/options_background_solidaric.jpg new file mode 100644 index 000000000..2a9db09ab Binary files /dev/null and b/img/options_background_solidaric.jpg differ diff --git a/img/search_banner_solidaric.png b/img/search_banner_solidaric.png new file mode 100644 index 000000000..d9f5b9c41 Binary files /dev/null and b/img/search_banner_solidaric.png differ diff --git a/inbox.py b/inbox.py index 171206ce5..2a9dfae12 100644 --- a/inbox.py +++ b/inbox.py @@ -1328,7 +1328,8 @@ def receiveAnnounce(recentPostsCache: {}, httpPrefix: str, domain: str, onionDomain: str, port: int, sendThreads: [], postLog: [], cachedWebfingers: {}, personCache: {}, messageJson: {}, federationList: [], - debug: bool, translate: {}) -> bool: + debug: bool, translate: {}, + YTReplacementDomain: str) -> bool: """Receives an announce activity within the POST section of HTTPServer """ if messageJson['type'] != 'Announce': @@ -1410,7 +1411,8 @@ def receiveAnnounce(recentPostsCache: {}, ' -> ' + messageJson['object']) postJsonObject = downloadAnnounce(session, baseDir, httpPrefix, nickname, domain, messageJson, - __version__, translate) + __version__, translate, + YTReplacementDomain) if postJsonObject: if debug: print('DEBUG: Announce post downloaded for ' + @@ -2055,7 +2057,7 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int, queueFilename: str, destinationFilename: str, maxReplies: int, allowDeletion: bool, maxMentions: int, maxEmoji: int, translate: {}, - unitTest: bool) -> bool: + unitTest: bool, YTReplacementDomain: str) -> bool: """ Anything which needs to be done after capabilities checks have passed """ actor = keyId @@ -2132,7 +2134,8 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int, personCache, messageJson, federationList, - debug, translate): + debug, translate, + YTReplacementDomain): if debug: print('DEBUG: Announce accepted from ' + actor) @@ -2207,7 +2210,7 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int, return False # replace YouTube links, so they get less tracking data - replaceYouTube(postJsonObject) + replaceYouTube(postJsonObject, YTReplacementDomain) # list of indexes to be updated updateIndexList = ['inbox'] @@ -2289,7 +2292,7 @@ def inboxAfterCapabilities(recentPostsCache: {}, maxRecentPosts: int, if isImageMedia(session, baseDir, httpPrefix, nickname, domain, postJsonObject, - translate): + translate, YTReplacementDomain): # media index will be updated updateIndexList.append('tlmedia') if isBlogPost(postJsonObject): @@ -2411,9 +2414,10 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int, domainMaxPostsPerDay: int, accountMaxPostsPerDay: int, allowDeletion: bool, debug: bool, maxMentions: int, maxEmoji: int, translate: {}, unitTest: bool, + YTReplacementDomain: str, acceptedCaps=["inbox:write", "objects:read"]) -> None: - """Processes received items and moves them to - the appropriate directories + """Processes received items and moves them to the appropriate + directories """ currSessionTime = int(time.time()) sessionLastUpdate = currSessionTime @@ -2829,7 +2833,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int, queueFilename, destination, maxReplies, allowDeletion, maxMentions, maxEmoji, - translate, unitTest) + translate, unitTest, + YTReplacementDomain) else: print('Queue: object capabilities check has failed') if debug: @@ -2852,7 +2857,8 @@ def runInboxQueue(recentPostsCache: {}, maxRecentPosts: int, queueFilename, destination, maxReplies, allowDeletion, maxMentions, maxEmoji, - translate, unitTest) + translate, unitTest, + YTReplacementDomain) if debug: pprint(queueJson['post']) print('No capability list within post') diff --git a/media.py b/media.py index 7c6b87306..ec6626958 100644 --- a/media.py +++ b/media.py @@ -18,10 +18,12 @@ from shutil import rmtree from shutil import move -def replaceYouTube(postJsonObject: {}) -> None: - """Replace YouTube with invidio.us +def replaceYouTube(postJsonObject: {}, replacementDomain: str) -> None: + """Replace YouTube with a replacement domain This denies Google some, but not all, tracking data """ + if not replacementDomain: + return if not isinstance(postJsonObject['object'], dict): return if not postJsonObject['object'].get('content'): @@ -30,7 +32,7 @@ def replaceYouTube(postJsonObject: {}) -> None: return postJsonObject['object']['content'] = \ postJsonObject['object']['content'].replace('www.youtube.com', - 'invidio.us') + replacementDomain) def removeMetaData(imageFilename: str, outputFilename: str) -> None: diff --git a/outbox.py b/outbox.py index 32179dec7..c84e69dcd 100644 --- a/outbox.py +++ b/outbox.py @@ -44,7 +44,8 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str, federationList: [], sendThreads: [], postLog: [], cachedWebfingers: {}, personCache: {}, allowDeletion: bool, - proxyType: str, version: str, debug: bool) -> bool: + proxyType: str, version: str, debug: bool, + YTReplacementDomain: str) -> bool: """post is received by the outbox Client to server message post https://www.w3.org/TR/activitypub/#client-to-server-outbox-delivery @@ -104,7 +105,7 @@ def postMessageToOutbox(messageJson: {}, postToNickname: str, print('DEBUG: domain is blocked: ' + messageJson['actor']) return False # replace youtube, so that google gets less tracking data - replaceYouTube(messageJson) + replaceYouTube(messageJson, YTReplacementDomain) # https://www.w3.org/TR/activitypub/#create-activity-outbox messageJson['object']['attributedTo'] = messageJson['actor'] if messageJson['object'].get('attachment'): diff --git a/posts.py b/posts.py index fdca227b5..f2aa61f43 100644 --- a/posts.py +++ b/posts.py @@ -2418,14 +2418,16 @@ def isDM(postJsonObject: {}) -> bool: def isImageMedia(session, baseDir: str, httpPrefix: str, nickname: str, domain: str, - postJsonObject: {}, translate: {}) -> bool: + postJsonObject: {}, translate: {}, + YTReplacementDomain: str) -> bool: """Returns true if the given post has attached image media """ if postJsonObject['type'] == 'Announce': postJsonAnnounce = \ downloadAnnounce(session, baseDir, httpPrefix, nickname, domain, postJsonObject, - __version__, translate) + __version__, translate, + YTReplacementDomain) if postJsonAnnounce: postJsonObject = postJsonAnnounce if postJsonObject['type'] != 'Create': @@ -3153,7 +3155,7 @@ def rejectAnnounce(announceFilename: str): def downloadAnnounce(session, baseDir: str, httpPrefix: str, nickname: str, domain: str, postJsonObject: {}, projectVersion: str, - translate: {}) -> {}: + translate: {}, YTReplacementDomain: str) -> {}: """Download the post referenced by an announce """ if not postJsonObject.get('object'): @@ -3289,7 +3291,7 @@ def downloadAnnounce(session, baseDir: str, httpPrefix: str, rejectAnnounce(announceFilename) return None postJsonObject = announcedJson - replaceYouTube(postJsonObject) + replaceYouTube(postJsonObject, YTReplacementDomain) if saveJson(postJsonObject, announceFilename): return postJsonObject return None diff --git a/schedule.py b/schedule.py index 56c0ddd2f..919ab2b70 100644 --- a/schedule.py +++ b/schedule.py @@ -103,7 +103,8 @@ def updatePostSchedule(baseDir: str, handle: str, httpd, httpd.allowDeletion, httpd.proxyType, httpd.projectVersion, - httpd.debug): + httpd.debug, + httpd.YTReplacementDomain): indexLines.remove(line) os.remove(postFilename) continue diff --git a/tests.py b/tests.py index 0d47268dd..2f85cd218 100644 --- a/tests.py +++ b/tests.py @@ -64,6 +64,7 @@ from media import getAttachmentMediaType from delete import sendDeleteViaServer from inbox import validInbox from inbox import validInboxFilenames +from content import htmlReplaceQuoteMarks from content import dangerousMarkup from content import removeHtml from content import addWebLinks @@ -286,7 +287,7 @@ def createServerAlice(path: str, domain: str, port: int, print('Server running: Alice') runDaemon(False, False, 5, True, True, 'en', __version__, "instanceId", False, path, domain, - onionDomain, i2pDomain, port, port, + onionDomain, i2pDomain, None, port, port, httpPrefix, federationList, maxMentions, maxEmoji, False, noreply, nolike, nopics, noannounce, cw, ocapAlways, proxyType, maxReplies, @@ -351,7 +352,7 @@ def createServerBob(path: str, domain: str, port: int, print('Server running: Bob') runDaemon(False, False, 5, True, True, 'en', __version__, "instanceId", False, path, domain, - onionDomain, i2pDomain, port, port, + onionDomain, i2pDomain, None, port, port, httpPrefix, federationList, maxMentions, maxEmoji, False, noreply, nolike, nopics, noannounce, cw, ocapAlways, proxyType, maxReplies, @@ -393,7 +394,7 @@ def createServerEve(path: str, domain: str, port: int, federationList: [], print('Server running: Eve') runDaemon(False, False, 5, True, True, 'en', __version__, "instanceId", False, path, domain, - onionDomain, i2pDomain, port, port, + onionDomain, i2pDomain, None, port, port, httpPrefix, federationList, maxMentions, maxEmoji, False, noreply, nolike, nopics, noannounce, cw, ocapAlways, proxyType, maxReplies, allowDeletion, True, True, False, @@ -1923,8 +1924,28 @@ def testDangerousMarkup(): assert(not dangerousMarkup(content)) +def runHtmlReplaceQuoteMarks(): + print('htmlReplaceQuoteMarks') + testStr = 'The "cat" "sat" on the mat' + result = htmlReplaceQuoteMarks(testStr) + assert result == 'The “cat” “sat” on the mat' + + testStr = 'The cat sat on the mat' + result = htmlReplaceQuoteMarks(testStr) + assert result == 'The cat sat on the mat' + + testStr = '"hello"' + result = htmlReplaceQuoteMarks(testStr) + assert result == '“hello”' + + testStr = '"hello" "test" html' + result = htmlReplaceQuoteMarks(testStr) + assert result == '“hello” “test” html' + + def runAllTests(): print('Running tests...') + runHtmlReplaceQuoteMarks() testDangerousMarkup() testRemoveHtml() testSiteIsActive() diff --git a/theme.py b/theme.py index a1fc7908f..a8a85b5e6 100644 --- a/theme.py +++ b/theme.py @@ -25,7 +25,8 @@ def getThemesList() -> []: and to lookup function names """ return ('Default', 'Blue', 'Hacker', 'Henge', 'HighVis', - 'LCD', 'Light', 'Night', 'Purple', 'Starlight', 'Zen') + 'LCD', 'Light', 'Night', 'Purple', 'Solidaric', + 'Starlight', 'Zen') def setThemeInConfig(baseDir: str, name: str) -> bool: @@ -274,7 +275,7 @@ def setThemeNight(baseDir: str): removeTheme(baseDir) setThemeInConfig(baseDir, name) fontStr = \ - "url('./fonts/CheGuevaraTextSans-Regular.woff2') format('woff2')" + "url('./fonts/solidaric.woff2') format('woff2')" themeParams = { "font-size-button-mobile": "36px", "font-size": "32px", @@ -302,7 +303,7 @@ def setThemeNight(baseDir: str): "place-color": "#7961ab", "event-color": "#7961ab", "event-background": "#333", - "*font-family": "'CheGuevaraTextSans-Regular'", + "*font-family": "'solidaric'", "*src": fontStr } bgParams = { @@ -329,6 +330,7 @@ def setThemeStarlight(baseDir: str): "text-entry-background": "#0f0d10", "link-bg-color": "#0f0d10", "main-link-color": "#ffc4bc", + "title-color": "#ffc4bc", "main-visited-color": "#e1c4bc", "main-fg-color": "#ffc4bc", "main-bg-color-dm": "#0b0a0a", @@ -383,6 +385,7 @@ def setThemeHenge(baseDir: str): "text-entry-background": "#383335", "link-bg-color": "#383335", "main-link-color": "white", + "title-color": "white", "main-visited-color": "#e1c4bc", "main-fg-color": "white", "main-bg-color-dm": "#343335", @@ -433,6 +436,7 @@ def setThemeZen(baseDir: str): "border-color": "#463b35", "border-width": "7px", "main-link-color": "#dddddd", + "title-color": "#dddddd", "main-visited-color": "#dddddd", "button-background": "#463b35", "button-selected": "#26201d", @@ -492,6 +496,7 @@ def setThemeLCD(baseDir: str): "border-color": "#33390d", "border-width": "5px", "main-link-color": "#9fb42b", + "title-color": "#9fb42b", "main-visited-color": "#9fb42b", "button-selected": "black", "button-highlighted": "green", @@ -559,6 +564,7 @@ def setThemePurple(baseDir: str): "main-fg-color": "#f98bb0", "border-color": "#3f2145", "main-link-color": "#ff42a0", + "title-color": "white", "main-visited-color": "#f93bb0", "button-selected": "#c042a0", "button-background": "#ff42a0", @@ -602,6 +608,7 @@ def setThemeHacker(baseDir: str): "main-fg-color": "#00ff00", "border-color": "#035103", "main-link-color": "#2fff2f", + "title-color": "#2fff2f", "main-visited-color": "#3c8234", "button-selected": "#063200", "button-background": "#062200", @@ -655,6 +662,7 @@ def setThemeLight(baseDir: str): "main-fg-color": "#2d2c37", "border-color": "#c0cdd9", "main-link-color": "#2a2c37", + "title-color": "#2a2c37", "main-visited-color": "#232c37", "text-entry-foreground": "#111", "text-entry-background": "white", @@ -689,6 +697,60 @@ def setThemeLight(baseDir: str): setThemeFromDict(baseDir, name, themeParams, bgParams) +def setThemeSolidaric(baseDir: str): + name = 'solidaric' + themeParams = { + "font-size-button-mobile": "36px", + "font-size": "32px", + "font-size2": "26px", + "font-size3": "40px", + "font-size4": "24px", + "font-size5": "22px", + "rgba(0, 0, 0, 0.5)": "rgba(0, 0, 0, 0.0)", + "main-bg-color": "white", + "main-bg-color-dm": "white", + "link-bg-color": "white", + "main-bg-color-reply": "white", + "main-bg-color-report": "white", + "main-header-color-roles": "#ebebf0", + "main-fg-color": "#2d2c37", + "border-color": "#c0cdd9", + "main-link-color": "#2a2c37", + "title-color": "#2a2c37", + "main-visited-color": "#232c37", + "text-entry-foreground": "#111", + "text-entry-background": "white", + "font-color-header": "black", + "dropdown-fg-color": "#222", + "dropdown-fg-color-hover": "#222", + "dropdown-bg-color": "white", + "dropdown-bg-color-hover": "lightgrey", + "color: #FFFFFE;": "color: black;", + "calendar-bg-color": "white", + "lines-color": "black", + "day-number": "black", + "day-number2": "#282c37", + "place-color": "black", + "event-color": "#282c37", + "today-foreground": "white", + "today-circle": "red", + "event-background": "lightblue", + "event-foreground": "white", + "title-text": "#282c37", + "title-background": "#ccc", + "gallery-text-color": "black", + "*font-family": "'solidaric'", + "*src": "url('./fonts/solidaric.woff2') format('woff2')" + } + bgParams = { + "login": "jpg", + "follow": "jpg", + "options": "jpg", + "search": "jpg" + } + setThemeFromDict(baseDir, name, themeParams, bgParams) + + def setThemeImages(baseDir: str, name: str) -> None: """Changes the profile background image and banner to the defaults diff --git a/translations/ar.json b/translations/ar.json index 4bd93a6ce..b54e8e450 100644 --- a/translations/ar.json +++ b/translations/ar.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "الخادم مشغول. الرجاء معاودة المحاولة في وقت لاحق", "Receive calendar events from this account": "تلقي أحداث التقويم من هذا الحساب", "Grayscale": "درجات الرمادي", - "Liked by": "نال إعجاب" + "Liked by": "نال إعجاب", + "Solidaric": "تضامن", + "YouTube Replacement Domain": "استبدال نطاق يوتيوب" } diff --git a/translations/ca.json b/translations/ca.json index ccaebffa1..a9b786b2a 100644 --- a/translations/ca.json +++ b/translations/ca.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "El servidor està ocupat. Siusplau, intenta-ho més tard", "Receive calendar events from this account": "Rep esdeveniments del calendari des d’aquest compte", "Grayscale": "Escala de grisos", - "Liked by": "M'agrada" + "Liked by": "M'agrada", + "Solidaric": "Solidaritat", + "YouTube Replacement Domain": "Domini de substitució de YouTube" } diff --git a/translations/cy.json b/translations/cy.json index 4544c2de4..0edecbda1 100644 --- a/translations/cy.json +++ b/translations/cy.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Mae'r gweinydd yn brysur. Rho gynnig Arni eto'n hwyrach", "Receive calendar events from this account": "Derbyn digwyddiadau calendr o'r cyfrif hwn", "Grayscale": "Graddlwyd", - "Liked by": "Hoffi" + "Liked by": "Hoffi", + "Solidaric": "Undod", + "YouTube Replacement Domain": "Parth Amnewid YouTube" } diff --git a/translations/de.json b/translations/de.json index 25e70a00e..bc526301f 100644 --- a/translations/de.json +++ b/translations/de.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Der Server ist beschäftigt. Bitte versuchen Sie es später noch einmal", "Receive calendar events from this account": "Erhalten Sie Kalenderereignisse von diesem Konto", "Grayscale": "Graustufen", - "Liked by": "Gefallen von" + "Liked by": "Gefallen von", + "Solidaric": "Solidarität", + "YouTube Replacement Domain": "YouTube-Ersatzdomain" } diff --git a/translations/en.json b/translations/en.json index 44a323614..a472dffa7 100644 --- a/translations/en.json +++ b/translations/en.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "The server is busy. Please try again later", "Receive calendar events from this account": "Receive calendar events from this account", "Grayscale": "Grayscale", - "Liked by": "Liked by" + "Liked by": "Liked by", + "Solidaric": "Solidaric", + "YouTube Replacement Domain": "YouTube Replacement Domain" } diff --git a/translations/es.json b/translations/es.json index cedb08a47..04a95dfb4 100644 --- a/translations/es.json +++ b/translations/es.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "El servidor esta ocupado. Por favor, inténtelo de nuevo más tarde", "Receive calendar events from this account": "Recibe eventos de calendario de esta cuenta", "Grayscale": "Escala de grises", - "Liked by": "Apreciado por" + "Liked by": "Apreciado por", + "Solidaric": "Solidaridad", + "YouTube Replacement Domain": "Dominio de reemplazo de YouTube" } diff --git a/translations/fr.json b/translations/fr.json index 35413a30d..3f9a8d363 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Le serveur est occupé. Veuillez réessayer plus tard", "Receive calendar events from this account": "Recevoir des événements d'agenda de ce compte", "Grayscale": "Niveaux de gris", - "Liked by": "Aimé par" + "Liked by": "Aimé par", + "Solidaric": "Solidarité", + "YouTube Replacement Domain": "Domaine de remplacement YouTube" } diff --git a/translations/ga.json b/translations/ga.json index 89c3d8df4..cf69648d3 100644 --- a/translations/ga.json +++ b/translations/ga.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Tá an freastalaí gnóthach. Bain triail eile as níos déanaí", "Receive calendar events from this account": "Faigh imeachtaí féilire ón gcuntas seo", "Grayscale": "Liathscála", - "Liked by": "Thaitin" + "Liked by": "Thaitin", + "Solidaric": "Dlúthpháirtíocht", + "YouTube Replacement Domain": "Fearann Athsholáthair YouTube" } diff --git a/translations/hi.json b/translations/hi.json index 7222f66c6..b90225ecb 100644 --- a/translations/hi.json +++ b/translations/hi.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "सर्वर व्यस्त है। बाद में पुन: प्रयास करें", "Receive calendar events from this account": "इस खाते से कैलेंडर ईवेंट प्राप्त करें", "Grayscale": "ग्रेस्केल", - "Liked by": "द्वारा पसंद किया गया" + "Liked by": "द्वारा पसंद किया गया", + "Solidaric": "एकजुटता", + "YouTube Replacement Domain": "YouTube रिप्लेसमेंट डोमेन" } diff --git a/translations/it.json b/translations/it.json index fa88fb7d4..2cecaa66d 100644 --- a/translations/it.json +++ b/translations/it.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Il server è occupato. Per favore riprova più tardi", "Receive calendar events from this account": "Ricevi eventi di calendario da questo account", "Grayscale": "Scala di grigi", - "Liked by": "Mi è piaciuto" + "Liked by": "Mi è piaciuto", + "Solidaric": "Solidarietà", + "YouTube Replacement Domain": "Dominio sostitutivo di YouTube" } diff --git a/translations/ja.json b/translations/ja.json index 7952606c5..02a24543a 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "サーバーはビジーです。 後でもう一度やり直してください", "Receive calendar events from this account": "このアカウントからカレンダーイベントを受信します", "Grayscale": "グレースケール", - "Liked by": "好き" + "Liked by": "好き", + "Solidaric": "連帯", + "YouTube Replacement Domain": "YouTube交換ドメイン" } diff --git a/translations/oc.json b/translations/oc.json index 006a4f40a..6203b272a 100644 --- a/translations/oc.json +++ b/translations/oc.json @@ -248,5 +248,7 @@ "The server is busy. Please try again later": "The server is busy. Please try again later", "Receive calendar events from this account": "Receive calendar events from this account", "Grayscale": "Grayscale", - "Liked by": "Liked by" + "Liked by": "Liked by", + "Solidaric": "Solidaric", + "YouTube Replacement Domain": "YouTube Replacement Domain" } diff --git a/translations/pt.json b/translations/pt.json index 930e15d55..19c0f4149 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "O servidor está ocupado. Por favor, tente novamente mais tarde", "Receive calendar events from this account": "Receba eventos da agenda desta conta", "Grayscale": "Escala de cinza", - "Liked by": "Curtida por" + "Liked by": "Curtida por", + "Solidaric": "Solidariedade", + "YouTube Replacement Domain": "Domínio de substituição do YouTube" } diff --git a/translations/ru.json b/translations/ru.json index bf69ce736..bc322bf02 100644 --- a/translations/ru.json +++ b/translations/ru.json @@ -252,5 +252,7 @@ "The server is busy. Please try again later": "Сервер занят. Пожалуйста, попробуйте позже", "Receive calendar events from this account": "Получать события календаря от этого аккаунта", "Grayscale": "Оттенки серого", - "Liked by": "Понравилось" + "Liked by": "Понравилось", + "Solidaric": "солидарность", + "YouTube Replacement Domain": "Запасной домен YouTube" } diff --git a/translations/zh.json b/translations/zh.json index 8b8c89c38..16f29b92a 100644 --- a/translations/zh.json +++ b/translations/zh.json @@ -251,5 +251,7 @@ "The server is busy. Please try again later": "服务器忙。 请稍后再试", "Receive calendar events from this account": "从该帐户接收日历事件", "Grayscale": "灰阶", - "Liked by": "喜欢的人" + "Liked by": "喜欢的人", + "Solidaric": "团结互助", + "YouTube Replacement Domain": "YouTube替换域" } diff --git a/webinterface.py b/webinterface.py index 762b7f67a..aade59c81 100644 --- a/webinterface.py +++ b/webinterface.py @@ -57,6 +57,7 @@ from bookmarks import bookmarkedByPerson from announce import announcedByPerson from blocking import isBlocked from blocking import isBlockedHashtag +from content import htmlReplaceQuoteMarks from content import removeTextFormatting from content import switchWords from content import getMentionsFromHtml @@ -691,7 +692,8 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int, baseDir: str, hashtag: str, pageNumber: int, postsPerPage: int, session, wfRequest: {}, personCache: {}, - httpPrefix: str, projectVersion: str) -> str: + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str) -> str: """Show a page containing search results for a hashtag """ if hashtag.startswith('#'): @@ -795,6 +797,7 @@ def htmlHashtagSearch(nickname: str, domain: str, port: int, None, True, allowDeletion, httpPrefix, projectVersion, 'search', + YTReplacementDomain, showIndividualPostIcons, showIndividualPostIcons, False, False, False) @@ -951,7 +954,8 @@ def htmlHistorySearch(translate: {}, baseDir: str, session, wfRequest, personCache: {}, - port: int) -> str: + port: int, + YTReplacementDomain: str) -> str: """Show a page containing search results for your post history """ if historysearch.startswith('!'): @@ -1022,6 +1026,7 @@ def htmlHistorySearch(translate: {}, baseDir: str, None, True, allowDeletion, httpPrefix, projectVersion, 'search', + YTReplacementDomain, showIndividualPostIcons, showIndividualPostIcons, False, False, False) @@ -1501,6 +1506,16 @@ def htmlEditProfile(translate: {}, baseDir: str, path: str, ' ' + editProfileForm += \ + '
' + YTReplacementDomain = getConfigParam(baseDir, "youtubedomain") + if not YTReplacementDomain: + YTReplacementDomain = '' + editProfileForm += \ + ' ' + editProfileForm += ' ' editProfileForm += '
' editProfileForm += \ @@ -2290,7 +2305,8 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int, authorized: bool, ocapAlways: bool, nickname: str, domain: str, port: int, session, wfRequest: {}, personCache: {}, - projectVersion: str) -> str: + projectVersion: str, + YTReplacementDomain: str) -> str: """Shows posts on the profile screen These should only be public posts """ @@ -2323,6 +2339,7 @@ def htmlProfilePosts(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, item, None, True, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, False, False, True, False) if postStr: profileStr += postStr @@ -2566,6 +2583,7 @@ def htmlProfile(defaultTimeline: str, baseDir: str, httpPrefix: str, authorized: bool, ocapAlways: bool, profileJson: {}, selected: str, session, wfRequest: {}, personCache: {}, + YTReplacementDomain: str, extraJson=None, pageNumber=None, maxItemsPerPage=None) -> str: """Show the profile page as html @@ -2823,7 +2841,8 @@ def htmlProfile(defaultTimeline: str, baseDir, httpPrefix, authorized, ocapAlways, nickname, domain, port, session, wfRequest, personCache, - projectVersion) + licenseStr + projectVersion, + YTReplacementDomain) + licenseStr if selected == 'following': profileStr += \ htmlProfileFollowing(translate, baseDir, httpPrefix, @@ -3602,7 +3621,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int, avatarUrl: str, showAvatarOptions: bool, allowDeletion: bool, httpPrefix: str, projectVersion: str, - boxName: str, showRepeats=True, + boxName: str, YTReplacementDomain: str, + showRepeats=True, showIcons=False, manuallyApprovesFollowers=False, showPublicOnly=False, @@ -3729,7 +3749,8 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int, postJsonAnnounce = \ downloadAnnounce(session, baseDir, httpPrefix, nickname, domain, postJsonObject, - projectVersion, translate) + projectVersion, translate, + YTReplacementDomain) if not postJsonAnnounce: return '' postJsonObject = postJsonAnnounce @@ -4240,8 +4261,10 @@ def individualPostAsHtml(recentPostsCache: {}, maxRecentPosts: int, objectContent = removeTextFormatting(objectContent) objectContent = \ switchWords(baseDir, nickname, domain, objectContent) + objectContent = htmlReplaceQuoteMarks(objectContent) else: - objectContent = postJsonObject['object']['content'] + objectContent = \ + postJsonObject['object']['content'] if not postIsSensitive: contentStr = objectContent + attachmentStr @@ -4350,7 +4373,8 @@ def htmlTimeline(defaultTimeline: str, boxName: str, allowDeletion: bool, httpPrefix: str, projectVersion: str, manuallyApproveFollowers: bool, - minimal: bool) -> str: + minimal: bool, + YTReplacementDomain: str) -> str: """Show the timeline as html """ accountDir = baseDir + '/accounts/' + nickname + '@' + domain @@ -4793,6 +4817,7 @@ def htmlTimeline(defaultTimeline: str, allowDeletion, httpPrefix, projectVersion, boxName, + YTReplacementDomain, boxName != 'dm', showIndividualPostIcons, manuallyApproveFollowers, @@ -4823,7 +4848,8 @@ def htmlShares(defaultTimeline: str, session, baseDir: str, wfRequest: {}, personCache: {}, nickname: str, domain: str, port: int, allowDeletion: bool, - httpPrefix: str, projectVersion: str) -> str: + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str) -> str: """Show the shares timeline as html """ manuallyApproveFollowers = \ @@ -4835,7 +4861,7 @@ def htmlShares(defaultTimeline: str, nickname, domain, port, None, 'tlshares', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, - False) + False, YTReplacementDomain) def htmlInbox(defaultTimeline: str, @@ -4845,7 +4871,7 @@ def htmlInbox(defaultTimeline: str, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the inbox as html """ manuallyApproveFollowers = \ @@ -4857,7 +4883,7 @@ def htmlInbox(defaultTimeline: str, nickname, domain, port, inboxJson, 'inbox', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, - minimal) + minimal, YTReplacementDomain) def htmlBookmarks(defaultTimeline: str, @@ -4867,7 +4893,7 @@ def htmlBookmarks(defaultTimeline: str, nickname: str, domain: str, port: int, bookmarksJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the bookmarks as html """ manuallyApproveFollowers = \ @@ -4879,7 +4905,7 @@ def htmlBookmarks(defaultTimeline: str, nickname, domain, port, bookmarksJson, 'tlbookmarks', allowDeletion, httpPrefix, projectVersion, manuallyApproveFollowers, - minimal) + minimal, YTReplacementDomain) def htmlInboxDMs(defaultTimeline: str, @@ -4889,14 +4915,15 @@ def htmlInboxDMs(defaultTimeline: str, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the DM timeline as html """ return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, inboxJson, 'dm', allowDeletion, - httpPrefix, projectVersion, False, minimal) + httpPrefix, projectVersion, False, minimal, + YTReplacementDomain) def htmlInboxReplies(defaultTimeline: str, @@ -4906,7 +4933,7 @@ def htmlInboxReplies(defaultTimeline: str, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the replies timeline as html """ return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts, @@ -4914,7 +4941,7 @@ def htmlInboxReplies(defaultTimeline: str, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, inboxJson, 'tlreplies', allowDeletion, httpPrefix, projectVersion, False, - minimal) + minimal, YTReplacementDomain) def htmlInboxMedia(defaultTimeline: str, @@ -4924,7 +4951,7 @@ def htmlInboxMedia(defaultTimeline: str, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the media timeline as html """ return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts, @@ -4932,7 +4959,7 @@ def htmlInboxMedia(defaultTimeline: str, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, inboxJson, 'tlmedia', allowDeletion, httpPrefix, projectVersion, False, - minimal) + minimal, YTReplacementDomain) def htmlInboxBlogs(defaultTimeline: str, @@ -4942,7 +4969,7 @@ def htmlInboxBlogs(defaultTimeline: str, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the blogs timeline as html """ return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts, @@ -4950,7 +4977,7 @@ def htmlInboxBlogs(defaultTimeline: str, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, inboxJson, 'tlblogs', allowDeletion, httpPrefix, projectVersion, False, - minimal) + minimal, YTReplacementDomain) def htmlModeration(defaultTimeline: str, @@ -4959,14 +4986,16 @@ def htmlModeration(defaultTimeline: str, session, baseDir: str, wfRequest: {}, personCache: {}, nickname: str, domain: str, port: int, inboxJson: {}, allowDeletion: bool, - httpPrefix: str, projectVersion: str) -> str: + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str) -> str: """Show the moderation feed as html """ return htmlTimeline(defaultTimeline, recentPostsCache, maxRecentPosts, translate, pageNumber, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, inboxJson, 'moderation', - allowDeletion, httpPrefix, projectVersion, True, False) + allowDeletion, httpPrefix, projectVersion, True, False, + YTReplacementDomain) def htmlOutbox(defaultTimeline: str, @@ -4976,7 +5005,7 @@ def htmlOutbox(defaultTimeline: str, nickname: str, domain: str, port: int, outboxJson: {}, allowDeletion: bool, httpPrefix: str, projectVersion: str, - minimal: bool) -> str: + minimal: bool, YTReplacementDomain: str) -> str: """Show the Outbox as html """ manuallyApproveFollowers = \ @@ -4986,7 +5015,8 @@ def htmlOutbox(defaultTimeline: str, itemsPerPage, session, baseDir, wfRequest, personCache, nickname, domain, port, outboxJson, 'outbox', allowDeletion, httpPrefix, projectVersion, - manuallyApproveFollowers, minimal) + manuallyApproveFollowers, minimal, + YTReplacementDomain) def htmlIndividualPost(recentPostsCache: {}, maxRecentPosts: int, @@ -4994,7 +5024,8 @@ def htmlIndividualPost(recentPostsCache: {}, maxRecentPosts: int, baseDir: str, session, wfRequest: {}, personCache: {}, nickname: str, domain: str, port: int, authorized: bool, postJsonObject: {}, httpPrefix: str, - projectVersion: str, likedBy: str) -> str: + projectVersion: str, likedBy: str, + YTReplacementDomain: str) -> str: """Show an individual post as html """ iconsDir = getIconsDir(baseDir) @@ -5038,6 +5069,7 @@ def htmlIndividualPost(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, postJsonObject, None, True, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, authorized, False, False, False) messageId = postJsonObject['id'].replace('/activity', '') @@ -5060,6 +5092,7 @@ def htmlIndividualPost(recentPostsCache: {}, maxRecentPosts: int, postJsonObject, None, True, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, authorized, False, False, False) + postStr @@ -5085,6 +5118,7 @@ def htmlIndividualPost(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, item, None, True, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, authorized, False, False, False) cssFilename = baseDir + '/epicyon-profile.css' @@ -5102,7 +5136,8 @@ def htmlPostReplies(recentPostsCache: {}, maxRecentPosts: int, translate: {}, baseDir: str, session, wfRequest: {}, personCache: {}, nickname: str, domain: str, port: int, repliesJson: {}, - httpPrefix: str, projectVersion: str) -> str: + httpPrefix: str, projectVersion: str, + YTReplacementDomain: str) -> str: """Show the replies to an individual post as html """ iconsDir = getIconsDir(baseDir) @@ -5116,6 +5151,7 @@ def htmlPostReplies(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, item, None, True, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, False, False, False, False) cssFilename = baseDir + '/epicyon-profile.css' @@ -5203,7 +5239,8 @@ def htmlDeletePost(recentPostsCache: {}, maxRecentPosts: int, session, baseDir: str, messageId: str, httpPrefix: str, projectVersion: str, wfRequest: {}, personCache: {}, - callingDomain: str) -> str: + callingDomain: str, + YTReplacementDomain: str) -> str: """Shows a screen asking to confirm the deletion of a post """ if '/statuses/' not in messageId: @@ -5247,6 +5284,7 @@ def htmlDeletePost(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, postJsonObject, None, True, False, httpPrefix, projectVersion, 'outbox', + YTReplacementDomain, False, False, False, False, False) deletePostStr += '
' deletePostStr += \ @@ -6195,7 +6233,8 @@ def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int, nickname: str, domain: str, port: int, profileHandle: str, session, cachedWebfingers: {}, personCache: {}, - debug: bool, projectVersion: str) -> str: + debug: bool, projectVersion: str, + YTReplacementDomain: str) -> str: """Show a profile page after a search for a fediverse address """ if '/users/' in profileHandle or \ @@ -6400,6 +6439,7 @@ def htmlProfileAfterSearch(recentPostsCache: {}, maxRecentPosts: int, nickname, domain, port, item, avatarUrl, False, False, httpPrefix, projectVersion, 'inbox', + YTReplacementDomain, False, False, False, False, False) i += 1 if i >= 20: