| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | __filename__ = "webapp_search.py" | 
					
						
							|  |  |  | __author__ = "Bob Mottram" | 
					
						
							|  |  |  | __license__ = "AGPL3+" | 
					
						
							| 
									
										
										
										
											2021-01-26 10:07:42 +00:00
										 |  |  | __version__ = "1.2.0" | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | __maintainer__ = "Bob Mottram" | 
					
						
							|  |  |  | __email__ = "bob@freedombone.net" | 
					
						
							|  |  |  | __status__ = "Production" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | from shutil import copyfile | 
					
						
							|  |  |  | import urllib.parse | 
					
						
							|  |  |  | from datetime import datetime | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  | from utils import getConfigParam | 
					
						
							| 
									
										
										
										
											2020-12-16 11:19:16 +00:00
										 |  |  | from utils import getFullDomain | 
					
						
							| 
									
										
										
										
											2020-12-01 21:44:27 +00:00
										 |  |  | from utils import isEditor | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | from utils import loadJson | 
					
						
							|  |  |  | from utils import getDomainFromActor | 
					
						
							|  |  |  | from utils import getNicknameFromActor | 
					
						
							|  |  |  | from utils import locatePost | 
					
						
							|  |  |  | from utils import isPublicPost | 
					
						
							|  |  |  | from utils import firstParagraphFromString | 
					
						
							|  |  |  | from utils import searchBoxPosts | 
					
						
							| 
									
										
										
										
											2020-12-22 10:30:52 +00:00
										 |  |  | from categories import getHashtagCategory | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | from feeds import rss2TagHeader | 
					
						
							|  |  |  | from feeds import rss2TagFooter | 
					
						
							| 
									
										
										
										
											2021-02-07 13:33:30 +00:00
										 |  |  | from webapp_utils import htmlKeyboardNavigation | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | from webapp_utils import getAltPath | 
					
						
							| 
									
										
										
										
											2020-11-12 15:32:49 +00:00
										 |  |  | from webapp_utils import htmlHeaderWithExternalStyle | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | from webapp_utils import htmlFooter | 
					
						
							|  |  |  | from webapp_utils import getSearchBannerFile | 
					
						
							|  |  |  | from webapp_utils import htmlPostSeparator | 
					
						
							|  |  |  | from webapp_post import individualPostAsHtml | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | from webapp_hashtagswarm import htmlHashTagSwarm | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def htmlSearchEmoji(cssCache: {}, translate: {}, | 
					
						
							|  |  |  |                     baseDir: str, httpPrefix: str, | 
					
						
							|  |  |  |                     searchStr: str) -> str: | 
					
						
							|  |  |  |     """Search results for emoji
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # emoji.json is generated so that it can be customized and the changes | 
					
						
							|  |  |  |     # will be retained even if default_emoji.json is subsequently updated | 
					
						
							|  |  |  |     if not os.path.isfile(baseDir + '/emoji/emoji.json'): | 
					
						
							|  |  |  |         copyfile(baseDir + '/emoji/default_emoji.json', | 
					
						
							|  |  |  |                  baseDir + '/emoji/emoji.json') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     searchStr = searchStr.lower().replace(':', '').strip('\n').strip('\r') | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-profile.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/epicyon.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/epicyon.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     emojiLookupFilename = baseDir + '/emoji/emoji.json' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # create header | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							|  |  |  |     emojiForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     emojiForm += '<center><h1>' + \ | 
					
						
							|  |  |  |         translate['Emoji Search'] + \ | 
					
						
							|  |  |  |         '</h1></center>' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     # does the lookup file exist? | 
					
						
							|  |  |  |     if not os.path.isfile(emojiLookupFilename): | 
					
						
							|  |  |  |         emojiForm += '<center><h5>' + \ | 
					
						
							|  |  |  |             translate['No results'] + '</h5></center>' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |         emojiForm += htmlFooter() | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |         return emojiForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     emojiJson = loadJson(emojiLookupFilename) | 
					
						
							|  |  |  |     if emojiJson: | 
					
						
							|  |  |  |         results = {} | 
					
						
							|  |  |  |         for emojiName, filename in emojiJson.items(): | 
					
						
							|  |  |  |             if searchStr in emojiName: | 
					
						
							|  |  |  |                 results[emojiName] = filename + '.png' | 
					
						
							|  |  |  |         for emojiName, filename in emojiJson.items(): | 
					
						
							|  |  |  |             if emojiName in searchStr: | 
					
						
							|  |  |  |                 results[emojiName] = filename + '.png' | 
					
						
							|  |  |  |         headingShown = False | 
					
						
							|  |  |  |         emojiForm += '<center>' | 
					
						
							|  |  |  |         msgStr1 = translate['Copy the text then paste it into your post'] | 
					
						
							|  |  |  |         msgStr2 = ':<img loading="lazy" class="searchEmoji" src="/emoji/' | 
					
						
							|  |  |  |         for emojiName, filename in results.items(): | 
					
						
							|  |  |  |             if os.path.isfile(baseDir + '/emoji/' + filename): | 
					
						
							|  |  |  |                 if not headingShown: | 
					
						
							|  |  |  |                     emojiForm += \ | 
					
						
							|  |  |  |                         '<center><h5>' + msgStr1 + \ | 
					
						
							|  |  |  |                         '</h5></center>' | 
					
						
							|  |  |  |                     headingShown = True | 
					
						
							|  |  |  |                 emojiForm += \ | 
					
						
							|  |  |  |                     '<h3>:' + emojiName + msgStr2 + \ | 
					
						
							|  |  |  |                     filename + '"/></h3>' | 
					
						
							|  |  |  |         emojiForm += '</center>' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     emojiForm += htmlFooter() | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     return emojiForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def htmlSearchSharedItems(cssCache: {}, translate: {}, | 
					
						
							|  |  |  |                           baseDir: str, searchStr: str, | 
					
						
							|  |  |  |                           pageNumber: int, | 
					
						
							|  |  |  |                           resultsPerPage: int, | 
					
						
							|  |  |  |                           httpPrefix: str, | 
					
						
							|  |  |  |                           domainFull: str, actor: str, | 
					
						
							|  |  |  |                           callingDomain: str) -> str: | 
					
						
							|  |  |  |     """Search results for shared items
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     currPage = 1 | 
					
						
							|  |  |  |     ctr = 0 | 
					
						
							|  |  |  |     sharedItemsForm = '' | 
					
						
							|  |  |  |     searchStrLower = urllib.parse.unquote(searchStr) | 
					
						
							|  |  |  |     searchStrLower = searchStrLower.lower().strip('\n').strip('\r') | 
					
						
							|  |  |  |     searchStrLowerList = searchStrLower.split('+') | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-profile.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/epicyon.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/epicyon.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     sharedItemsForm = \ | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |         htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-12-07 12:06:30 +00:00
										 |  |  |         '<center><h1>' + \ | 
					
						
							|  |  |  |         '<a href="' + actor + '/search">' + \ | 
					
						
							|  |  |  |         translate['Shared Items Search'] + \ | 
					
						
							|  |  |  |         '</a></h1></center>' | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     resultsExist = False | 
					
						
							|  |  |  |     for subdir, dirs, files in os.walk(baseDir + '/accounts'): | 
					
						
							|  |  |  |         for handle in dirs: | 
					
						
							|  |  |  |             if '@' not in handle: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             contactNickname = handle.split('@')[0] | 
					
						
							|  |  |  |             sharesFilename = baseDir + '/accounts/' + handle + \ | 
					
						
							|  |  |  |                 '/shares.json' | 
					
						
							|  |  |  |             if not os.path.isfile(sharesFilename): | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |             sharesJson = loadJson(sharesFilename) | 
					
						
							|  |  |  |             if not sharesJson: | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |             for name, sharedItem in sharesJson.items(): | 
					
						
							|  |  |  |                 matched = True | 
					
						
							|  |  |  |                 for searchSubstr in searchStrLowerList: | 
					
						
							|  |  |  |                     subStrMatched = False | 
					
						
							|  |  |  |                     searchSubstr = searchSubstr.strip() | 
					
						
							|  |  |  |                     if searchSubstr in sharedItem['location'].lower(): | 
					
						
							|  |  |  |                         subStrMatched = True | 
					
						
							|  |  |  |                     elif searchSubstr in sharedItem['summary'].lower(): | 
					
						
							|  |  |  |                         subStrMatched = True | 
					
						
							|  |  |  |                     elif searchSubstr in sharedItem['displayName'].lower(): | 
					
						
							|  |  |  |                         subStrMatched = True | 
					
						
							|  |  |  |                     elif searchSubstr in sharedItem['category'].lower(): | 
					
						
							|  |  |  |                         subStrMatched = True | 
					
						
							|  |  |  |                     if not subStrMatched: | 
					
						
							|  |  |  |                         matched = False | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  |                 if matched: | 
					
						
							|  |  |  |                     if currPage == pageNumber: | 
					
						
							|  |  |  |                         sharedItemsForm += '<div class="container">\n' | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<p class="share-title">' + \ | 
					
						
							|  |  |  |                             sharedItem['displayName'] + '</p>\n' | 
					
						
							|  |  |  |                         if sharedItem.get('imageUrl'): | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '<a href="' + \ | 
					
						
							|  |  |  |                                 sharedItem['imageUrl'] + '">\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '<img loading="lazy" src="' + \ | 
					
						
							|  |  |  |                                 sharedItem['imageUrl'] + \ | 
					
						
							|  |  |  |                                 '" alt="Item image"></a>\n' | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<p>' + sharedItem['summary'] + '</p>\n' | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<p><b>' + translate['Type'] + \ | 
					
						
							|  |  |  |                             ':</b> ' + sharedItem['itemType'] + ' ' | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<b>' + translate['Category'] + \ | 
					
						
							|  |  |  |                             ':</b> ' + sharedItem['category'] + ' ' | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<b>' + translate['Location'] + \ | 
					
						
							|  |  |  |                             ':</b> ' + sharedItem['location'] + '</p>\n' | 
					
						
							|  |  |  |                         contactActor = \ | 
					
						
							|  |  |  |                             httpPrefix + '://' + domainFull + \ | 
					
						
							|  |  |  |                             '/users/' + contactNickname | 
					
						
							|  |  |  |                         sharedItemsForm += \ | 
					
						
							|  |  |  |                             '<p><a href="' + actor + \ | 
					
						
							|  |  |  |                             '?replydm=sharedesc:' + \ | 
					
						
							|  |  |  |                             sharedItem['displayName'] + \ | 
					
						
							|  |  |  |                             '?mention=' + contactActor + \ | 
					
						
							|  |  |  |                             '"><button class="button">' + \ | 
					
						
							|  |  |  |                             translate['Contact'] + '</button></a>\n' | 
					
						
							|  |  |  |                         if actor.endswith('/users/' + contactNickname): | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 ' <a href="' + actor + '?rmshare=' + \ | 
					
						
							|  |  |  |                                 name + '"><button class="button">' + \ | 
					
						
							|  |  |  |                                 translate['Remove'] + '</button></a>\n' | 
					
						
							|  |  |  |                         sharedItemsForm += '</p></div>\n' | 
					
						
							|  |  |  |                         if not resultsExist and currPage > 1: | 
					
						
							|  |  |  |                             postActor = \ | 
					
						
							|  |  |  |                                 getAltPath(actor, domainFull, | 
					
						
							|  |  |  |                                            callingDomain) | 
					
						
							|  |  |  |                             # previous page link, needs to be a POST | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '<form method="POST" action="' + \ | 
					
						
							|  |  |  |                                 postActor + \ | 
					
						
							|  |  |  |                                 '/searchhandle?page=' + \ | 
					
						
							|  |  |  |                                 str(pageNumber - 1) + '">\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '  <input type="hidden" ' + \ | 
					
						
							|  |  |  |                                 'name="actor" value="' + actor + '">\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '  <input type="hidden" ' + \ | 
					
						
							|  |  |  |                                 'name="searchtext" value="' + \ | 
					
						
							|  |  |  |                                 searchStrLower + '"><br>\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                             sharedItemsForm += \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '  <center>\n' + \ | 
					
						
							|  |  |  |                                 '    <a href="' + actor + \ | 
					
						
							|  |  |  |                                 '" type="submit" name="submitSearch">\n' | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '    <img loading="lazy" ' + \ | 
					
						
							| 
									
										
										
										
											2020-12-09 13:08:26 +00:00
										 |  |  |                                 'class="pageicon" src="/icons' + \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '/pageup.png" title="' + \ | 
					
						
							|  |  |  |                                 translate['Page up'] + \ | 
					
						
							|  |  |  |                                 '" alt="' + translate['Page up'] + \ | 
					
						
							|  |  |  |                                 '"/></a>\n' | 
					
						
							|  |  |  |                             sharedItemsForm += '  </center>\n' | 
					
						
							|  |  |  |                             sharedItemsForm += '</form>\n' | 
					
						
							| 
									
										
										
										
											2020-12-07 11:53:23 +00:00
										 |  |  |                         resultsExist = True | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                     ctr += 1 | 
					
						
							|  |  |  |                     if ctr >= resultsPerPage: | 
					
						
							|  |  |  |                         currPage += 1 | 
					
						
							|  |  |  |                         if currPage > pageNumber: | 
					
						
							|  |  |  |                             postActor = \ | 
					
						
							|  |  |  |                                 getAltPath(actor, domainFull, | 
					
						
							|  |  |  |                                            callingDomain) | 
					
						
							|  |  |  |                             # next page link, needs to be a POST | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '<form method="POST" action="' + \ | 
					
						
							|  |  |  |                                 postActor + \ | 
					
						
							|  |  |  |                                 '/searchhandle?page=' + \ | 
					
						
							|  |  |  |                                 str(pageNumber + 1) + '">\n' | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '  <input type="hidden" ' + \ | 
					
						
							|  |  |  |                                 'name="actor" value="' + actor + '">\n' | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '  <input type="hidden" ' + \ | 
					
						
							|  |  |  |                                 'name="searchtext" value="' + \ | 
					
						
							|  |  |  |                                 searchStrLower + '"><br>\n' | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '  <center>\n' + \ | 
					
						
							|  |  |  |                                 '    <a href="' + actor + \ | 
					
						
							|  |  |  |                                 '" type="submit" name="submitSearch">\n' | 
					
						
							|  |  |  |                             sharedItemsForm += \ | 
					
						
							|  |  |  |                                 '    <img loading="lazy" ' + \ | 
					
						
							| 
									
										
										
										
											2020-12-09 13:08:26 +00:00
										 |  |  |                                 'class="pageicon" src="/icons' + \ | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |                                 '/pagedown.png" title="' + \ | 
					
						
							|  |  |  |                                 translate['Page down'] + \ | 
					
						
							|  |  |  |                                 '" alt="' + translate['Page down'] + \ | 
					
						
							|  |  |  |                                 '"/></a>\n' | 
					
						
							|  |  |  |                             sharedItemsForm += '  </center>\n' | 
					
						
							|  |  |  |                             sharedItemsForm += '</form>\n' | 
					
						
							|  |  |  |                             break | 
					
						
							|  |  |  |                         ctr = 0 | 
					
						
							| 
									
										
										
										
											2020-12-13 22:13:45 +00:00
										 |  |  |         break | 
					
						
							| 
									
										
										
										
											2020-11-12 15:55:08 +00:00
										 |  |  |     if not resultsExist: | 
					
						
							|  |  |  |         sharedItemsForm += \ | 
					
						
							|  |  |  |             '<center><h5>' + translate['No results'] + '</h5></center>\n' | 
					
						
							|  |  |  |     sharedItemsForm += htmlFooter() | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     return sharedItemsForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def htmlSearchEmojiTextEntry(cssCache: {}, translate: {}, | 
					
						
							|  |  |  |                              baseDir: str, path: str) -> str: | 
					
						
							|  |  |  |     """Search for an emoji by name
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # emoji.json is generated so that it can be customized and the changes | 
					
						
							|  |  |  |     # will be retained even if default_emoji.json is subsequently updated | 
					
						
							|  |  |  |     if not os.path.isfile(baseDir + '/emoji/emoji.json'): | 
					
						
							|  |  |  |         copyfile(baseDir + '/emoji/default_emoji.json', | 
					
						
							|  |  |  |                  baseDir + '/emoji/emoji.json') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     actor = path.replace('/search', '') | 
					
						
							|  |  |  |     domain, port = getDomainFromActor(actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/img/search-background.png'): | 
					
						
							|  |  |  |         if not os.path.isfile(baseDir + '/accounts/search-background.png'): | 
					
						
							|  |  |  |             copyfile(baseDir + '/img/search-background.png', | 
					
						
							|  |  |  |                      baseDir + '/accounts/search-background.png') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-follow.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/follow.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/follow.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							|  |  |  |     emojiStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     emojiStr += '<div class="follow">\n' | 
					
						
							|  |  |  |     emojiStr += '  <div class="followAvatar">\n' | 
					
						
							|  |  |  |     emojiStr += '  <center>\n' | 
					
						
							|  |  |  |     emojiStr += \ | 
					
						
							|  |  |  |         '  <p class="followText">' + \ | 
					
						
							|  |  |  |         translate['Enter an emoji name to search for'] + '</p>\n' | 
					
						
							| 
									
										
										
										
											2020-12-27 17:17:48 +00:00
										 |  |  |     emojiStr += '  <form role="search" method="POST" action="' + \ | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |         actor + '/searchhandleemoji">\n' | 
					
						
							|  |  |  |     emojiStr += '    <input type="hidden" name="actor" value="' + \ | 
					
						
							|  |  |  |         actor + '">\n' | 
					
						
							|  |  |  |     emojiStr += '    <input type="text" name="searchtext" autofocus><br>\n' | 
					
						
							|  |  |  |     emojiStr += \ | 
					
						
							|  |  |  |         '    <button type="submit" class="button" name="submitSearch">' + \ | 
					
						
							|  |  |  |         translate['Submit'] + '</button>\n' | 
					
						
							|  |  |  |     emojiStr += '  </form>\n' | 
					
						
							|  |  |  |     emojiStr += '  </center>\n' | 
					
						
							|  |  |  |     emojiStr += '  </div>\n' | 
					
						
							|  |  |  |     emojiStr += '</div>\n' | 
					
						
							|  |  |  |     emojiStr += htmlFooter() | 
					
						
							|  |  |  |     return emojiStr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def htmlSearch(cssCache: {}, translate: {}, | 
					
						
							|  |  |  |                baseDir: str, path: str, domain: str, | 
					
						
							| 
									
										
										
										
											2021-02-07 13:33:30 +00:00
										 |  |  |                defaultTimeline: str, theme: str, | 
					
						
							|  |  |  |                textModeBanner: str) -> str: | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     """Search called from the timeline icon
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     actor = path.replace('/search', '') | 
					
						
							|  |  |  |     searchNickname = getNicknameFromActor(actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/img/search-background.png'): | 
					
						
							|  |  |  |         if not os.path.isfile(baseDir + '/accounts/search-background.png'): | 
					
						
							|  |  |  |             copyfile(baseDir + '/img/search-background.png', | 
					
						
							|  |  |  |                      baseDir + '/accounts/search-background.png') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-search.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/search.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/search.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							|  |  |  |     followStr = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # show a banner above the search box | 
					
						
							|  |  |  |     searchBannerFile, searchBannerFilename = \ | 
					
						
							| 
									
										
										
										
											2020-12-20 17:26:38 +00:00
										 |  |  |         getSearchBannerFile(baseDir, searchNickname, domain, theme) | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-07 13:33:30 +00:00
										 |  |  |     textModeBannerStr = htmlKeyboardNavigation(textModeBanner, {}) | 
					
						
							|  |  |  |     if textModeBannerStr is None: | 
					
						
							|  |  |  |         textModeBannerStr = '' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     if os.path.isfile(searchBannerFilename): | 
					
						
							|  |  |  |         usersPath = '/users/' + searchNickname | 
					
						
							|  |  |  |         followStr += \ | 
					
						
							| 
									
										
										
										
											2021-02-07 13:33:30 +00:00
										 |  |  |             '<header>\n' + textModeBannerStr + \ | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |             '<a href="' + usersPath + '/' + defaultTimeline + '" title="' + \ | 
					
						
							|  |  |  |             translate['Switch to timeline view'] + '" alt="' + \ | 
					
						
							|  |  |  |             translate['Switch to timeline view'] + '">\n' | 
					
						
							|  |  |  |         followStr += '<img loading="lazy" class="timeline-banner" src="' + \ | 
					
						
							| 
									
										
										
										
											2021-02-01 20:20:13 +00:00
										 |  |  |             usersPath + '/' + searchBannerFile + '" alt="" /></a>\n' + \ | 
					
						
							| 
									
										
										
										
											2020-12-27 16:57:15 +00:00
										 |  |  |             '</header>\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # show the search box | 
					
						
							|  |  |  |     followStr += '<div class="follow">\n' | 
					
						
							|  |  |  |     followStr += '  <div class="followAvatar">\n' | 
					
						
							|  |  |  |     followStr += '  <center>\n' | 
					
						
							|  |  |  |     idx = 'Enter an address, shared item, !history, #hashtag, ' + \ | 
					
						
							|  |  |  |         '*skill or :emoji: to search for' | 
					
						
							|  |  |  |     followStr += \ | 
					
						
							|  |  |  |         '  <p class="followText">' + translate[idx] + '</p>\n' | 
					
						
							| 
									
										
										
										
											2020-12-27 16:59:03 +00:00
										 |  |  |     followStr += '  <form role="search" method="POST" ' + \ | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |         'accept-charset="UTF-8" action="' + actor + '/searchhandle">\n' | 
					
						
							|  |  |  |     followStr += \ | 
					
						
							|  |  |  |         '    <input type="hidden" name="actor" value="' + actor + '">\n' | 
					
						
							|  |  |  |     followStr += '    <input type="text" name="searchtext" autofocus><br>\n' | 
					
						
							|  |  |  |     followStr += '    <button type="submit" class="button" ' + \ | 
					
						
							|  |  |  |         'name="submitSearch">' + translate['Submit'] + '</button>\n' | 
					
						
							|  |  |  |     followStr += '  </form>\n' | 
					
						
							|  |  |  |     followStr += '  <p class="hashtagswarm">' + \ | 
					
						
							| 
									
										
										
										
											2020-11-19 17:09:50 +00:00
										 |  |  |         htmlHashTagSwarm(baseDir, actor, translate) + '</p>\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     followStr += '  </center>\n' | 
					
						
							|  |  |  |     followStr += '  </div>\n' | 
					
						
							|  |  |  |     followStr += '</div>\n' | 
					
						
							|  |  |  |     followStr += htmlFooter() | 
					
						
							|  |  |  |     return followStr | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 12:25:32 +00:00
										 |  |  | def htmlSkillsSearch(actor: str, | 
					
						
							|  |  |  |                      cssCache: {}, translate: {}, baseDir: str, | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |                      httpPrefix: str, | 
					
						
							|  |  |  |                      skillsearch: str, instanceOnly: bool, | 
					
						
							|  |  |  |                      postsPerPage: int) -> str: | 
					
						
							|  |  |  |     """Show a page containing search results for a skill
 | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     if skillsearch.startswith('*'): | 
					
						
							|  |  |  |         skillsearch = skillsearch[1:].strip() | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     skillsearch = skillsearch.lower().strip('\n').strip('\r') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     results = [] | 
					
						
							|  |  |  |     # search instance accounts | 
					
						
							|  |  |  |     for subdir, dirs, files in os.walk(baseDir + '/accounts/'): | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |         for f in files: | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |             if not f.endswith('.json'): | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |             if '@' not in f: | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |             if f.startswith('inbox@'): | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |             actorFilename = os.path.join(subdir, f) | 
					
						
							|  |  |  |             actorJson = loadJson(actorFilename) | 
					
						
							|  |  |  |             if actorJson: | 
					
						
							|  |  |  |                 if actorJson.get('id') and \ | 
					
						
							|  |  |  |                    actorJson.get('skills') and \ | 
					
						
							|  |  |  |                    actorJson.get('name') and \ | 
					
						
							|  |  |  |                    actorJson.get('icon'): | 
					
						
							|  |  |  |                     actor = actorJson['id'] | 
					
						
							|  |  |  |                     for skillName, skillLevel in actorJson['skills'].items(): | 
					
						
							|  |  |  |                         skillName = skillName.lower() | 
					
						
							|  |  |  |                         if not (skillName in skillsearch or | 
					
						
							|  |  |  |                                 skillsearch in skillName): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         skillLevelStr = str(skillLevel) | 
					
						
							|  |  |  |                         if skillLevel < 100: | 
					
						
							|  |  |  |                             skillLevelStr = '0' + skillLevelStr | 
					
						
							|  |  |  |                         if skillLevel < 10: | 
					
						
							|  |  |  |                             skillLevelStr = '0' + skillLevelStr | 
					
						
							|  |  |  |                         indexStr = \ | 
					
						
							|  |  |  |                             skillLevelStr + ';' + actor + ';' + \ | 
					
						
							|  |  |  |                             actorJson['name'] + \ | 
					
						
							|  |  |  |                             ';' + actorJson['icon']['url'] | 
					
						
							|  |  |  |                         if indexStr not in results: | 
					
						
							|  |  |  |                             results.append(indexStr) | 
					
						
							| 
									
										
										
										
											2020-12-13 22:13:45 +00:00
										 |  |  |         break | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     if not instanceOnly: | 
					
						
							|  |  |  |         # search actor cache | 
					
						
							|  |  |  |         for subdir, dirs, files in os.walk(baseDir + '/cache/actors/'): | 
					
						
							|  |  |  |             for f in files: | 
					
						
							|  |  |  |                 if not f.endswith('.json'): | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if '@' not in f: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if f.startswith('inbox@'): | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 actorFilename = os.path.join(subdir, f) | 
					
						
							|  |  |  |                 cachedActorJson = loadJson(actorFilename) | 
					
						
							|  |  |  |                 if cachedActorJson: | 
					
						
							|  |  |  |                     if cachedActorJson.get('actor'): | 
					
						
							|  |  |  |                         actorJson = cachedActorJson['actor'] | 
					
						
							|  |  |  |                         if actorJson.get('id') and \ | 
					
						
							|  |  |  |                            actorJson.get('skills') and \ | 
					
						
							|  |  |  |                            actorJson.get('name') and \ | 
					
						
							|  |  |  |                            actorJson.get('icon'): | 
					
						
							|  |  |  |                             actor = actorJson['id'] | 
					
						
							|  |  |  |                             for skillName, skillLevel in \ | 
					
						
							|  |  |  |                                     actorJson['skills'].items(): | 
					
						
							|  |  |  |                                 skillName = skillName.lower() | 
					
						
							|  |  |  |                                 if not (skillName in skillsearch or | 
					
						
							|  |  |  |                                         skillsearch in skillName): | 
					
						
							|  |  |  |                                     continue | 
					
						
							|  |  |  |                                 skillLevelStr = str(skillLevel) | 
					
						
							|  |  |  |                                 if skillLevel < 100: | 
					
						
							|  |  |  |                                     skillLevelStr = '0' + skillLevelStr | 
					
						
							|  |  |  |                                 if skillLevel < 10: | 
					
						
							|  |  |  |                                     skillLevelStr = '0' + skillLevelStr | 
					
						
							|  |  |  |                                 indexStr = \ | 
					
						
							|  |  |  |                                     skillLevelStr + ';' + actor + ';' + \ | 
					
						
							|  |  |  |                                     actorJson['name'] + \ | 
					
						
							|  |  |  |                                     ';' + actorJson['icon']['url'] | 
					
						
							|  |  |  |                                 if indexStr not in results: | 
					
						
							|  |  |  |                                     results.append(indexStr) | 
					
						
							| 
									
										
										
										
											2020-12-13 22:13:45 +00:00
										 |  |  |             break | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     results.sort(reverse=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-profile.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/epicyon.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/epicyon.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							|  |  |  |     skillSearchForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     skillSearchForm += \ | 
					
						
							| 
									
										
										
										
											2020-12-07 12:28:13 +00:00
										 |  |  |         '<center><h1><a href = "' + actor + '/search">' + \ | 
					
						
							| 
									
										
										
										
											2020-12-07 12:25:32 +00:00
										 |  |  |         translate['Skills search'] + ': ' + \ | 
					
						
							|  |  |  |         skillsearch + \ | 
					
						
							|  |  |  |         '</a></h1></center>' | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if len(results) == 0: | 
					
						
							|  |  |  |         skillSearchForm += \ | 
					
						
							|  |  |  |             '<center><h5>' + translate['No results'] + \ | 
					
						
							|  |  |  |             '</h5></center>' | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         skillSearchForm += '<center>' | 
					
						
							|  |  |  |         ctr = 0 | 
					
						
							|  |  |  |         for skillMatch in results: | 
					
						
							|  |  |  |             skillMatchFields = skillMatch.split(';') | 
					
						
							|  |  |  |             if len(skillMatchFields) != 4: | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |             actor = skillMatchFields[1] | 
					
						
							|  |  |  |             actorName = skillMatchFields[2] | 
					
						
							|  |  |  |             avatarUrl = skillMatchFields[3] | 
					
						
							|  |  |  |             skillSearchForm += \ | 
					
						
							|  |  |  |                 '<div class="search-result""><a href="' + \ | 
					
						
							|  |  |  |                 actor + '/skills">' | 
					
						
							|  |  |  |             skillSearchForm += \ | 
					
						
							|  |  |  |                 '<img loading="lazy" src="' + avatarUrl + \ | 
					
						
							| 
									
										
										
										
											2021-02-01 20:20:13 +00:00
										 |  |  |                 '" alt="" /><span class="search-result-text">' + actorName + \ | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |                 '</span></a></div>' | 
					
						
							|  |  |  |             ctr += 1 | 
					
						
							|  |  |  |             if ctr >= postsPerPage: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |         skillSearchForm += '</center>' | 
					
						
							|  |  |  |     skillSearchForm += htmlFooter() | 
					
						
							|  |  |  |     return skillSearchForm | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def htmlHistorySearch(cssCache: {}, translate: {}, baseDir: str, | 
					
						
							|  |  |  |                       httpPrefix: str, | 
					
						
							|  |  |  |                       nickname: str, domain: str, | 
					
						
							|  |  |  |                       historysearch: str, | 
					
						
							|  |  |  |                       postsPerPage: int, pageNumber: int, | 
					
						
							|  |  |  |                       projectVersion: str, | 
					
						
							|  |  |  |                       recentPostsCache: {}, | 
					
						
							|  |  |  |                       maxRecentPosts: int, | 
					
						
							|  |  |  |                       session, | 
					
						
							| 
									
										
										
										
											2020-12-31 12:46:35 +00:00
										 |  |  |                       cachedWebfingers, | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |                       personCache: {}, | 
					
						
							|  |  |  |                       port: int, | 
					
						
							|  |  |  |                       YTReplacementDomain: str, | 
					
						
							| 
									
										
										
										
											2020-12-23 23:59:49 +00:00
										 |  |  |                       showPublishedDateOnly: bool, | 
					
						
							| 
									
										
										
										
											2021-01-30 11:47:09 +00:00
										 |  |  |                       peertubeInstances: [], | 
					
						
							|  |  |  |                       allowLocalNetworkAccess: bool) -> str: | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     """Show a page containing search results for your post history
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if historysearch.startswith('!'): | 
					
						
							|  |  |  |         historysearch = historysearch[1:].strip() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     historysearch = historysearch.lower().strip('\n').strip('\r') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     boxFilenames = \ | 
					
						
							|  |  |  |         searchBoxPosts(baseDir, nickname, domain, | 
					
						
							|  |  |  |                        historysearch, postsPerPage) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-profile.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/epicyon.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/epicyon.css' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     historySearchForm = \ | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |         htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # add the page title | 
					
						
							| 
									
										
										
										
											2020-12-16 11:19:16 +00:00
										 |  |  |     domainFull = getFullDomain(domain, port) | 
					
						
							| 
									
										
										
										
											2020-12-07 12:16:30 +00:00
										 |  |  |     actor = httpPrefix + '://' + domainFull + '/users/' + nickname | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |     historySearchForm += \ | 
					
						
							| 
									
										
										
										
											2020-12-07 12:16:30 +00:00
										 |  |  |         '<center><h1><a href="' + actor + '/search">' + \ | 
					
						
							|  |  |  |         translate['Your Posts'] + \ | 
					
						
							|  |  |  |         '</a></h1></center>' | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if len(boxFilenames) == 0: | 
					
						
							|  |  |  |         historySearchForm += \ | 
					
						
							|  |  |  |             '<center><h5>' + translate['No results'] + \ | 
					
						
							|  |  |  |             '</h5></center>' | 
					
						
							|  |  |  |         return historySearchForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     separatorStr = htmlPostSeparator(baseDir, None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # ensure that the page number is in bounds | 
					
						
							|  |  |  |     if not pageNumber: | 
					
						
							|  |  |  |         pageNumber = 1 | 
					
						
							|  |  |  |     elif pageNumber < 1: | 
					
						
							|  |  |  |         pageNumber = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # get the start end end within the index file | 
					
						
							|  |  |  |     startIndex = int((pageNumber - 1) * postsPerPage) | 
					
						
							|  |  |  |     endIndex = startIndex + postsPerPage | 
					
						
							|  |  |  |     noOfBoxFilenames = len(boxFilenames) | 
					
						
							|  |  |  |     if endIndex >= noOfBoxFilenames and noOfBoxFilenames > 0: | 
					
						
							|  |  |  |         endIndex = noOfBoxFilenames - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     index = startIndex | 
					
						
							|  |  |  |     while index <= endIndex: | 
					
						
							|  |  |  |         postFilename = boxFilenames[index] | 
					
						
							|  |  |  |         if not postFilename: | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         postJsonObject = loadJson(postFilename) | 
					
						
							|  |  |  |         if not postJsonObject: | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         showIndividualPostIcons = True | 
					
						
							|  |  |  |         allowDeletion = False | 
					
						
							|  |  |  |         postStr = \ | 
					
						
							|  |  |  |             individualPostAsHtml(True, recentPostsCache, | 
					
						
							|  |  |  |                                  maxRecentPosts, | 
					
						
							| 
									
										
										
										
											2020-12-09 13:31:54 +00:00
										 |  |  |                                  translate, None, | 
					
						
							| 
									
										
										
										
											2020-12-31 12:46:35 +00:00
										 |  |  |                                  baseDir, session, cachedWebfingers, | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |                                  personCache, | 
					
						
							|  |  |  |                                  nickname, domain, port, | 
					
						
							|  |  |  |                                  postJsonObject, | 
					
						
							|  |  |  |                                  None, True, allowDeletion, | 
					
						
							|  |  |  |                                  httpPrefix, projectVersion, | 
					
						
							|  |  |  |                                  'search', | 
					
						
							|  |  |  |                                  YTReplacementDomain, | 
					
						
							|  |  |  |                                  showPublishedDateOnly, | 
					
						
							| 
									
										
										
										
											2020-12-23 23:59:49 +00:00
										 |  |  |                                  peertubeInstances, | 
					
						
							| 
									
										
										
										
											2021-01-30 11:47:09 +00:00
										 |  |  |                                  allowLocalNetworkAccess, | 
					
						
							| 
									
										
										
										
											2020-11-19 14:02:16 +00:00
										 |  |  |                                  showIndividualPostIcons, | 
					
						
							|  |  |  |                                  showIndividualPostIcons, | 
					
						
							|  |  |  |                                  False, False, False) | 
					
						
							|  |  |  |         if postStr: | 
					
						
							|  |  |  |             historySearchForm += separatorStr + postStr | 
					
						
							|  |  |  |         index += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     historySearchForm += htmlFooter() | 
					
						
							|  |  |  |     return historySearchForm | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def htmlHashtagSearch(cssCache: {}, | 
					
						
							|  |  |  |                       nickname: str, domain: str, port: int, | 
					
						
							|  |  |  |                       recentPostsCache: {}, maxRecentPosts: int, | 
					
						
							|  |  |  |                       translate: {}, | 
					
						
							|  |  |  |                       baseDir: str, hashtag: str, pageNumber: int, | 
					
						
							|  |  |  |                       postsPerPage: int, | 
					
						
							| 
									
										
										
										
											2020-12-31 12:46:35 +00:00
										 |  |  |                       session, cachedWebfingers: {}, personCache: {}, | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                       httpPrefix: str, projectVersion: str, | 
					
						
							|  |  |  |                       YTReplacementDomain: str, | 
					
						
							| 
									
										
										
										
											2020-12-23 23:59:49 +00:00
										 |  |  |                       showPublishedDateOnly: bool, | 
					
						
							| 
									
										
										
										
											2021-01-30 11:47:09 +00:00
										 |  |  |                       peertubeInstances: [], | 
					
						
							|  |  |  |                       allowLocalNetworkAccess: bool) -> str: | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     """Show a page containing search results for a hashtag
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if hashtag.startswith('#'): | 
					
						
							|  |  |  |         hashtag = hashtag[1:] | 
					
						
							|  |  |  |     hashtag = urllib.parse.unquote(hashtag) | 
					
						
							|  |  |  |     hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' | 
					
						
							|  |  |  |     if not os.path.isfile(hashtagIndexFile): | 
					
						
							|  |  |  |         if hashtag != hashtag.lower(): | 
					
						
							|  |  |  |             hashtag = hashtag.lower() | 
					
						
							|  |  |  |             hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' | 
					
						
							|  |  |  |     if not os.path.isfile(hashtagIndexFile): | 
					
						
							|  |  |  |         print('WARN: hashtag file not found ' + hashtagIndexFile) | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     separatorStr = htmlPostSeparator(baseDir, None) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # check that the directory for the nickname exists | 
					
						
							|  |  |  |     if nickname: | 
					
						
							|  |  |  |         if not os.path.isdir(baseDir + '/accounts/' + | 
					
						
							|  |  |  |                              nickname + '@' + domain): | 
					
						
							|  |  |  |             nickname = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # read the index | 
					
						
							|  |  |  |     with open(hashtagIndexFile, "r") as f: | 
					
						
							|  |  |  |         lines = f.readlines() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # read the css | 
					
						
							|  |  |  |     cssFilename = baseDir + '/epicyon-profile.css' | 
					
						
							|  |  |  |     if os.path.isfile(baseDir + '/epicyon.css'): | 
					
						
							|  |  |  |         cssFilename = baseDir + '/epicyon.css' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # ensure that the page number is in bounds | 
					
						
							|  |  |  |     if not pageNumber: | 
					
						
							|  |  |  |         pageNumber = 1 | 
					
						
							|  |  |  |     elif pageNumber < 1: | 
					
						
							|  |  |  |         pageNumber = 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # get the start end end within the index file | 
					
						
							|  |  |  |     startIndex = int((pageNumber - 1) * postsPerPage) | 
					
						
							|  |  |  |     endIndex = startIndex + postsPerPage | 
					
						
							|  |  |  |     noOfLines = len(lines) | 
					
						
							|  |  |  |     if endIndex >= noOfLines and noOfLines > 0: | 
					
						
							|  |  |  |         endIndex = noOfLines - 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # add the page title | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |     instanceTitle = \ | 
					
						
							|  |  |  |         getConfigParam(baseDir, 'instanceTitle') | 
					
						
							| 
									
										
										
										
											2020-11-12 15:32:49 +00:00
										 |  |  |     hashtagSearchForm = \ | 
					
						
							| 
									
										
										
										
											2021-01-11 19:46:21 +00:00
										 |  |  |         htmlHeaderWithExternalStyle(cssFilename, instanceTitle) | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     if nickname: | 
					
						
							|  |  |  |         hashtagSearchForm += '<center>\n' + \ | 
					
						
							|  |  |  |             '<h1><a href="/users/' + nickname + '/search">#' + \ | 
					
						
							| 
									
										
										
										
											2020-12-01 22:21:33 +00:00
										 |  |  |             hashtag + '</a></h1>\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     else: | 
					
						
							|  |  |  |         hashtagSearchForm += '<center>\n' + \ | 
					
						
							| 
									
										
										
										
											2020-12-01 22:21:33 +00:00
										 |  |  |             '<h1>#' + hashtag + '</h1>\n' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # RSS link for hashtag feed | 
					
						
							|  |  |  |     hashtagSearchForm += '<a href="/tags/rss2/' + hashtag + '">' | 
					
						
							|  |  |  |     hashtagSearchForm += \ | 
					
						
							|  |  |  |         '<img style="width:3%;min-width:50px" ' + \ | 
					
						
							|  |  |  |         'loading="lazy" alt="RSS 2.0" title="RSS 2.0" src="/' + \ | 
					
						
							| 
									
										
										
										
											2020-12-09 13:08:26 +00:00
										 |  |  |         'icons/logorss.png" /></a></center>\n' | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 21:44:27 +00:00
										 |  |  |     # edit the category for this hashtag | 
					
						
							|  |  |  |     if isEditor(baseDir, nickname): | 
					
						
							|  |  |  |         category = getHashtagCategory(baseDir, hashtag) | 
					
						
							| 
									
										
										
										
											2020-12-01 21:53:35 +00:00
										 |  |  |         hashtagSearchForm += '<div class="hashtagCategoryContainer">\n' | 
					
						
							| 
									
										
										
										
											2020-12-02 11:55:40 +00:00
										 |  |  |         hashtagSearchForm += '  <form enctype="multipart/form-data" ' + \ | 
					
						
							|  |  |  |             'method="POST" accept-charset="UTF-8" action="' + \ | 
					
						
							| 
									
										
										
										
											2020-12-02 11:12:56 +00:00
										 |  |  |             '/users/' + nickname + '/tags/' + hashtag + \ | 
					
						
							|  |  |  |             '/sethashtagcategory">\n' | 
					
						
							| 
									
										
										
										
											2020-12-01 21:44:27 +00:00
										 |  |  |         hashtagSearchForm += '    <center>\n' | 
					
						
							| 
									
										
										
										
											2020-12-01 21:48:12 +00:00
										 |  |  |         hashtagSearchForm += translate['Category'] | 
					
						
							| 
									
										
										
										
											2020-12-01 21:44:27 +00:00
										 |  |  |         hashtagSearchForm += \ | 
					
						
							| 
									
										
										
										
											2020-12-01 22:56:26 +00:00
										 |  |  |             '      <input type="text" style="width: 20ch" ' + \ | 
					
						
							| 
									
										
										
										
											2020-12-01 22:54:09 +00:00
										 |  |  |             'name="hashtagCategory" value="' + category + '">\n' | 
					
						
							| 
									
										
										
										
											2020-12-01 21:44:27 +00:00
										 |  |  |         hashtagSearchForm += \ | 
					
						
							|  |  |  |             '      <button type="submit" class="button" name="submitYes">' + \ | 
					
						
							|  |  |  |             translate['Submit'] + '</button>\n' | 
					
						
							|  |  |  |         hashtagSearchForm += '    </center>\n' | 
					
						
							|  |  |  |         hashtagSearchForm += '  </form>\n' | 
					
						
							| 
									
										
										
										
											2020-12-01 21:53:35 +00:00
										 |  |  |         hashtagSearchForm += '</div>\n' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |     if startIndex > 0: | 
					
						
							|  |  |  |         # previous page link | 
					
						
							|  |  |  |         hashtagSearchForm += \ | 
					
						
							|  |  |  |             '  <center>\n' + \ | 
					
						
							|  |  |  |             '    <a href="/tags/' + hashtag + '?page=' + \ | 
					
						
							|  |  |  |             str(pageNumber - 1) + \ | 
					
						
							|  |  |  |             '"><img loading="lazy" class="pageicon" src="/' + \ | 
					
						
							| 
									
										
										
										
											2020-12-09 13:08:26 +00:00
										 |  |  |             'icons/pageup.png" title="' + \ | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |             translate['Page up'] + \ | 
					
						
							|  |  |  |             '" alt="' + translate['Page up'] + \ | 
					
						
							|  |  |  |             '"></a>\n  </center>\n' | 
					
						
							|  |  |  |     index = startIndex | 
					
						
							|  |  |  |     while index <= endIndex: | 
					
						
							|  |  |  |         postId = lines[index].strip('\n').strip('\r') | 
					
						
							|  |  |  |         if '  ' not in postId: | 
					
						
							|  |  |  |             nickname = getNicknameFromActor(postId) | 
					
						
							|  |  |  |             if not nickname: | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             postFields = postId.split('  ') | 
					
						
							|  |  |  |             if len(postFields) != 3: | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             nickname = postFields[1] | 
					
						
							|  |  |  |             postId = postFields[2] | 
					
						
							|  |  |  |         postFilename = locatePost(baseDir, nickname, domain, postId) | 
					
						
							|  |  |  |         if not postFilename: | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         postJsonObject = loadJson(postFilename) | 
					
						
							| 
									
										
										
										
											2020-11-30 10:44:37 +00:00
										 |  |  |         if not postJsonObject: | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         if not isPublicPost(postJsonObject): | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         showIndividualPostIcons = False | 
					
						
							|  |  |  |         if nickname: | 
					
						
							|  |  |  |             showIndividualPostIcons = True | 
					
						
							|  |  |  |         allowDeletion = False | 
					
						
							|  |  |  |         showRepeats = showIndividualPostIcons | 
					
						
							|  |  |  |         showIcons = showIndividualPostIcons | 
					
						
							|  |  |  |         manuallyApprovesFollowers = False | 
					
						
							|  |  |  |         showPublicOnly = False | 
					
						
							|  |  |  |         storeToCache = False | 
					
						
							|  |  |  |         allowDownloads = True | 
					
						
							|  |  |  |         avatarUrl = None | 
					
						
							|  |  |  |         showAvatarOptions = True | 
					
						
							|  |  |  |         postStr = \ | 
					
						
							|  |  |  |             individualPostAsHtml(allowDownloads, recentPostsCache, | 
					
						
							|  |  |  |                                  maxRecentPosts, | 
					
						
							| 
									
										
										
										
											2020-12-09 13:31:54 +00:00
										 |  |  |                                  translate, None, | 
					
						
							| 
									
										
										
										
											2020-12-31 12:46:35 +00:00
										 |  |  |                                  baseDir, session, cachedWebfingers, | 
					
						
							| 
									
										
										
										
											2020-11-30 10:44:37 +00:00
										 |  |  |                                  personCache, | 
					
						
							|  |  |  |                                  nickname, domain, port, | 
					
						
							|  |  |  |                                  postJsonObject, | 
					
						
							|  |  |  |                                  avatarUrl, showAvatarOptions, | 
					
						
							|  |  |  |                                  allowDeletion, | 
					
						
							|  |  |  |                                  httpPrefix, projectVersion, | 
					
						
							|  |  |  |                                  'search', | 
					
						
							|  |  |  |                                  YTReplacementDomain, | 
					
						
							|  |  |  |                                  showPublishedDateOnly, | 
					
						
							| 
									
										
										
										
											2020-12-23 23:59:49 +00:00
										 |  |  |                                  peertubeInstances, | 
					
						
							| 
									
										
										
										
											2021-01-30 11:47:09 +00:00
										 |  |  |                                  allowLocalNetworkAccess, | 
					
						
							| 
									
										
										
										
											2020-11-30 10:44:37 +00:00
										 |  |  |                                  showRepeats, showIcons, | 
					
						
							|  |  |  |                                  manuallyApprovesFollowers, | 
					
						
							|  |  |  |                                  showPublicOnly, | 
					
						
							|  |  |  |                                  storeToCache) | 
					
						
							|  |  |  |         if postStr: | 
					
						
							|  |  |  |             hashtagSearchForm += separatorStr + postStr | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |         index += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if endIndex < noOfLines - 1: | 
					
						
							|  |  |  |         # next page link | 
					
						
							|  |  |  |         hashtagSearchForm += \ | 
					
						
							|  |  |  |             '  <center>\n' + \ | 
					
						
							|  |  |  |             '    <a href="/tags/' + hashtag + \ | 
					
						
							|  |  |  |             '?page=' + str(pageNumber + 1) + \ | 
					
						
							| 
									
										
										
										
											2020-12-09 13:08:26 +00:00
										 |  |  |             '"><img loading="lazy" class="pageicon" src="/icons' + \ | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |             '/pagedown.png" title="' + translate['Page down'] + \ | 
					
						
							|  |  |  |             '" alt="' + translate['Page down'] + '"></a>' + \ | 
					
						
							|  |  |  |             '  </center>' | 
					
						
							|  |  |  |     hashtagSearchForm += htmlFooter() | 
					
						
							|  |  |  |     return hashtagSearchForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def rssHashtagSearch(nickname: str, domain: str, port: int, | 
					
						
							|  |  |  |                      recentPostsCache: {}, maxRecentPosts: int, | 
					
						
							|  |  |  |                      translate: {}, | 
					
						
							|  |  |  |                      baseDir: str, hashtag: str, | 
					
						
							|  |  |  |                      postsPerPage: int, | 
					
						
							| 
									
										
										
										
											2020-12-31 12:46:35 +00:00
										 |  |  |                      session, cachedWebfingers: {}, personCache: {}, | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  |                      httpPrefix: str, projectVersion: str, | 
					
						
							|  |  |  |                      YTReplacementDomain: str) -> str: | 
					
						
							|  |  |  |     """Show an rss feed for a hashtag
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if hashtag.startswith('#'): | 
					
						
							|  |  |  |         hashtag = hashtag[1:] | 
					
						
							|  |  |  |     hashtag = urllib.parse.unquote(hashtag) | 
					
						
							|  |  |  |     hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' | 
					
						
							|  |  |  |     if not os.path.isfile(hashtagIndexFile): | 
					
						
							|  |  |  |         if hashtag != hashtag.lower(): | 
					
						
							|  |  |  |             hashtag = hashtag.lower() | 
					
						
							|  |  |  |             hashtagIndexFile = baseDir + '/tags/' + hashtag + '.txt' | 
					
						
							|  |  |  |     if not os.path.isfile(hashtagIndexFile): | 
					
						
							|  |  |  |         print('WARN: hashtag file not found ' + hashtagIndexFile) | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # check that the directory for the nickname exists | 
					
						
							|  |  |  |     if nickname: | 
					
						
							|  |  |  |         if not os.path.isdir(baseDir + '/accounts/' + | 
					
						
							|  |  |  |                              nickname + '@' + domain): | 
					
						
							|  |  |  |             nickname = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # read the index | 
					
						
							|  |  |  |     lines = [] | 
					
						
							|  |  |  |     with open(hashtagIndexFile, "r") as f: | 
					
						
							|  |  |  |         lines = f.readlines() | 
					
						
							|  |  |  |     if not lines: | 
					
						
							|  |  |  |         return None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-16 11:19:16 +00:00
										 |  |  |     domainFull = getFullDomain(domain, port) | 
					
						
							| 
									
										
										
										
											2020-11-09 19:41:01 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     maxFeedLength = 10 | 
					
						
							|  |  |  |     hashtagFeed = \ | 
					
						
							|  |  |  |         rss2TagHeader(hashtag, httpPrefix, domainFull) | 
					
						
							|  |  |  |     for index in range(len(lines)): | 
					
						
							|  |  |  |         postId = lines[index].strip('\n').strip('\r') | 
					
						
							|  |  |  |         if '  ' not in postId: | 
					
						
							|  |  |  |             nickname = getNicknameFromActor(postId) | 
					
						
							|  |  |  |             if not nickname: | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 if index >= maxFeedLength: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             postFields = postId.split('  ') | 
					
						
							|  |  |  |             if len(postFields) != 3: | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 if index >= maxFeedLength: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             nickname = postFields[1] | 
					
						
							|  |  |  |             postId = postFields[2] | 
					
						
							|  |  |  |         postFilename = locatePost(baseDir, nickname, domain, postId) | 
					
						
							|  |  |  |         if not postFilename: | 
					
						
							|  |  |  |             index += 1 | 
					
						
							|  |  |  |             if index >= maxFeedLength: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |             continue | 
					
						
							|  |  |  |         postJsonObject = loadJson(postFilename) | 
					
						
							|  |  |  |         if postJsonObject: | 
					
						
							|  |  |  |             if not isPublicPost(postJsonObject): | 
					
						
							|  |  |  |                 index += 1 | 
					
						
							|  |  |  |                 if index >= maxFeedLength: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             # add to feed | 
					
						
							|  |  |  |             if postJsonObject['object'].get('content') and \ | 
					
						
							|  |  |  |                postJsonObject['object'].get('attributedTo') and \ | 
					
						
							|  |  |  |                postJsonObject['object'].get('published'): | 
					
						
							|  |  |  |                 published = postJsonObject['object']['published'] | 
					
						
							|  |  |  |                 pubDate = datetime.strptime(published, "%Y-%m-%dT%H:%M:%SZ") | 
					
						
							|  |  |  |                 rssDateStr = pubDate.strftime("%a, %d %b %Y %H:%M:%S UT") | 
					
						
							|  |  |  |                 hashtagFeed += '     <item>' | 
					
						
							|  |  |  |                 hashtagFeed += \ | 
					
						
							|  |  |  |                     '         <author>' + \ | 
					
						
							|  |  |  |                     postJsonObject['object']['attributedTo'] + \ | 
					
						
							|  |  |  |                     '</author>' | 
					
						
							|  |  |  |                 if postJsonObject['object'].get('summary'): | 
					
						
							|  |  |  |                     hashtagFeed += \ | 
					
						
							|  |  |  |                         '         <title>' + \ | 
					
						
							|  |  |  |                         postJsonObject['object']['summary'] + \ | 
					
						
							|  |  |  |                         '</title>' | 
					
						
							|  |  |  |                 description = postJsonObject['object']['content'] | 
					
						
							|  |  |  |                 description = firstParagraphFromString(description) | 
					
						
							|  |  |  |                 hashtagFeed += \ | 
					
						
							|  |  |  |                     '         <description>' + description + '</description>' | 
					
						
							|  |  |  |                 hashtagFeed += \ | 
					
						
							|  |  |  |                     '         <pubDate>' + rssDateStr + '</pubDate>' | 
					
						
							|  |  |  |                 if postJsonObject['object'].get('attachment'): | 
					
						
							|  |  |  |                     for attach in postJsonObject['object']['attachment']: | 
					
						
							|  |  |  |                         if not attach.get('url'): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         hashtagFeed += \ | 
					
						
							|  |  |  |                             '         <link>' + attach['url'] + '</link>' | 
					
						
							|  |  |  |                 hashtagFeed += '     </item>' | 
					
						
							|  |  |  |         index += 1 | 
					
						
							|  |  |  |         if index >= maxFeedLength: | 
					
						
							|  |  |  |             break | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return hashtagFeed + rss2TagFooter() |