__filename__ = "webapp_hashtagswarm.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.1.0" __maintainer__ = "Bob Mottram" __email__ = "bob@freedombone.net" __status__ = "Production" import os from shutil import copyfile from datetime import datetime from utils import getConfigParam from utils import getNicknameFromActor from utils import getHashtagCategories from utils import getHashtagCategory from webapp_utils import getSearchBannerFile from webapp_utils import getImageFile from webapp_utils import getContentWarningButton from webapp_utils import htmlHeaderWithExternalStyle from webapp_utils import htmlFooter def getHashtagCategoriesFeed(baseDir: str, hashtagCategories=None) -> str: """Returns an rss feed for hashtag categories """ if not hashtagCategories: hashtagCategories = getHashtagCategories(baseDir) if not hashtagCategories: return None rssStr = "\n" rssStr += "\n" rssStr += '\n' rssStr += ' #categories\n' rssDateStr = \ datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S UT") for categoryStr, hashtagList in hashtagCategories.items(): rssStr += '\n' rssStr += ' ' + categoryStr + '\n' listStr = '' for hashtag in hashtagList: listStr += hashtag + ' ' rssStr += ' ' + listStr.strip() + '\n' rssStr += ' \n' rssStr += ' ' + rssDateStr + '\n' rssStr += '\n' rssStr += '\n' rssStr += '\n' return rssStr def getHashtagDomainMax(domainHistogram: {}) -> str: """Returns the domain with the maximum number of hashtags """ maxCount = 1 maxDomain = None for domain, count in domainHistogram.items(): if count > maxCount: maxDomain = domain maxCount = count return maxDomain def getHashtagDomainHistogram(domainHistogram: {}, translate: {}) -> str: """Returns the html for a histogram of domains from which hashtags are coming """ totalCount = 0 for domain, count in domainHistogram.items(): totalCount += count if totalCount == 0: return '' htmlStr = '' histogramHeaderStr = '

\n' histogramHeaderStr += '

' + translate['Hashtag origins'] + '

\n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' histogramHeaderStr += ' \n' leftColStr = '' rightColStr = '' for i in range(len(domainHistogram)): domain = getHashtagDomainMax(domainHistogram) if not domain: break percent = int(domainHistogram[domain] * 100 / totalCount) if histogramHeaderStr: htmlStr += histogramHeaderStr histogramHeaderStr = None leftColStr += str(percent) + '%
' rightColStr += domain + '
' del domainHistogram[domain] if htmlStr: htmlStr += ' \n' htmlStr += ' \n' htmlStr += ' \n' htmlStr += ' \n' htmlStr += '
' + leftColStr + '' + rightColStr + '
\n' htmlStr += '
\n' return htmlStr def htmlHashTagSwarm(baseDir: str, actor: str, translate: {}) -> str: """Returns a tag swarm of today's hashtags """ currTime = datetime.utcnow() daysSinceEpoch = (currTime - datetime(1970, 1, 1)).days daysSinceEpochStr = str(daysSinceEpoch) + ' ' daysSinceEpochStr2 = str(daysSinceEpoch - 1) + ' ' recently = daysSinceEpoch - 1 tagSwarm = [] categorySwarm = [] domainHistogram = {} # Load the blocked hashtags into memory. # This avoids needing to repeatedly load the blocked file for each hashtag blockedStr = '' globalBlockingFilename = baseDir + '/accounts/blocking.txt' if os.path.isfile(globalBlockingFilename): with open(globalBlockingFilename, 'r') as fp: blockedStr = fp.read() for subdir, dirs, files in os.walk(baseDir + '/tags'): for f in files: if not f.endswith('.txt'): continue tagsFilename = os.path.join(baseDir + '/tags', f) if not os.path.isfile(tagsFilename): continue # get last modified datetime modTimesinceEpoc = os.path.getmtime(tagsFilename) lastModifiedDate = datetime.fromtimestamp(modTimesinceEpoc) fileDaysSinceEpoch = (lastModifiedDate - datetime(1970, 1, 1)).days # check if the file was last modified within the previous # two days if fileDaysSinceEpoch < recently: continue hashTagName = f.split('.')[0] if '#' + hashTagName + '\n' in blockedStr: continue with open(tagsFilename, 'r') as fp: # only read one line, which saves time and memory lastTag = fp.readline() if not lastTag.startswith(daysSinceEpochStr): if not lastTag.startswith(daysSinceEpochStr2): continue with open(tagsFilename, 'r') as tagsFile: while True: line = tagsFile.readline() if not line: break elif ' ' not in line: break sections = line.split(' ') if len(sections) != 3: break postDaysSinceEpochStr = sections[0] if not postDaysSinceEpochStr.isdigit(): break postDaysSinceEpoch = int(postDaysSinceEpochStr) if postDaysSinceEpoch < recently: break else: postUrl = sections[2] if '##' not in postUrl: break postDomain = postUrl.split('##')[1] if '#' in postDomain: postDomain = postDomain.split('#')[0] if domainHistogram.get(postDomain): domainHistogram[postDomain] = \ domainHistogram[postDomain] + 1 else: domainHistogram[postDomain] = 1 tagSwarm.append(hashTagName) categoryFilename = \ tagsFilename.replace('.txt', '.category') if os.path.isfile(categoryFilename): categoryStr = \ getHashtagCategory(baseDir, hashTagName) if categoryStr not in categorySwarm: categorySwarm.append(categoryStr) break if not tagSwarm: return '' tagSwarm.sort() # swarm of categories categorySwarmStr = '' if categorySwarm: if len(categorySwarm) > 3: categorySwarm.sort() for categoryStr in categorySwarm: categorySwarmStr += \ '' + categoryStr + '\n' categorySwarmStr += '
\n' # swarm of tags tagSwarmStr = '' for tagName in tagSwarm: tagSwarmStr += \ '' + tagName + '\n' if categorySwarmStr: tagSwarmStr = \ getContentWarningButton('alltags', translate, tagSwarmStr) tagSwarmHtml = categorySwarmStr + tagSwarmStr.strip() + '\n' # tagSwarmHtml += getHashtagDomainHistogram(domainHistogram, translate) return tagSwarmHtml def htmlSearchHashtagCategory(cssCache: {}, translate: {}, baseDir: str, path: str, domain: str) -> str: """Show hashtags after selecting a category on the main search screen """ actor = path.split('/category/')[0] categoryStr = path.split('/category/')[1].strip() 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' htmlStr = htmlHeaderWithExternalStyle(cssFilename) # show a banner above the search box searchBannerFile, searchBannerFilename = \ getSearchBannerFile(baseDir, searchNickname, domain) if not os.path.isfile(searchBannerFilename): # get the default search banner for the theme theme = getConfigParam(baseDir, 'theme').lower() if theme == 'default': theme = '' else: theme = '_' + theme themeSearchImageFile, themeSearchBannerFilename = \ getImageFile(baseDir, 'search_banner', baseDir + '/img', searchNickname, domain) if os.path.isfile(themeSearchBannerFilename): searchBannerFilename = \ baseDir + '/accounts/' + \ searchNickname + '@' + domain + '/' + themeSearchImageFile copyfile(themeSearchBannerFilename, searchBannerFilename) searchBannerFile = themeSearchImageFile if os.path.isfile(searchBannerFilename): htmlStr += '\n' htmlStr += '\n' htmlStr += '
' htmlStr += '



' htmlStr += '

' htmlStr += translate['Category'] + ': ' + categoryStr + '

' hashtagsDict = getHashtagCategories(baseDir, True, categoryStr) if hashtagsDict: for categoryStr2, hashtagList in hashtagsDict.items(): hashtagList.sort() for tagName in hashtagList: htmlStr += \ '' + tagName + '\n' htmlStr += '
' htmlStr += '
' htmlStr += htmlFooter() return htmlStr