__filename__ = "webapp_create_post.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
__version__ = "1.2.0"
__maintainer__ = "Bob Mottram"
__email__ = "bob@libreserver.org"
__status__ = "Production"
__module_group__ = "Web Interface"
import os
from utils import getNewPostEndpoints
from utils import isPublicPostFromUrl
from utils import getNicknameFromActor
from utils import getDomainFromActor
from utils import getMediaFormats
from utils import getConfigParam
from utils import acctDir
from utils import getCurrencies
from utils import getCategoryTypes
from webapp_utils import getBannerFile
from webapp_utils import htmlHeaderWithExternalStyle
from webapp_utils import htmlFooter
from webapp_utils import editTextField
from webapp_utils import editNumberField
from webapp_utils import editCurrencyField
from webapp_post import individualPostAsHtml
def _htmlFollowingDataList(base_dir: str, nickname: str,
domain: str, domainFull: str) -> str:
"""Returns a datalist of handles being followed
"""
listStr = '\n'
followingFilename = \
acctDir(base_dir, nickname, domain) + '/following.txt'
msg = None
if os.path.isfile(followingFilename):
with open(followingFilename, 'r') as followingFile:
msg = followingFile.read()
# add your own handle, so that you can send DMs
# to yourself as reminders
msg += nickname + '@' + domainFull + '\n'
if msg:
# include petnames
petnamesFilename = \
acctDir(base_dir, nickname, domain) + '/petnames.txt'
if os.path.isfile(petnamesFilename):
followingList = []
with open(petnamesFilename, 'r') as petnamesFile:
petStr = petnamesFile.read()
# extract each petname and append it
petnamesList = petStr.split('\n')
for pet in petnamesList:
followingList.append(pet.split(' ')[0])
# add the following.txt entries
followingList += msg.split('\n')
else:
# no petnames list exists - just use following.txt
followingList = msg.split('\n')
followingList.sort()
if followingList:
for followingAddress in followingList:
if followingAddress:
listStr += '@' + followingAddress + ' \n'
listStr += ' \n'
return listStr
def _htmlNewPostDropDown(scopeIcon: str, scopeDescription: str,
replyStr: str,
translate: {},
showPublicOnDropdown: bool,
defaultTimeline: str,
pathBase: str,
dropdownNewPostSuffix: str,
dropdownNewBlogSuffix: str,
dropdownUnlistedSuffix: str,
dropdownFollowersSuffix: str,
dropdownDMSuffix: str,
dropdownReminderSuffix: str,
dropdownReportSuffix: str,
noDropDown: bool,
accessKeys: {}) -> str:
"""Returns the html for a drop down list of new post types
"""
dropDownContent = '\n'
if not noDropDown:
dropDownContent += '
\n'
dropDownContent += '
\n'
dropDownContent += ' ' + scopeDescription + ' \n'
if noDropDown:
dropDownContent += '
\n'
return dropDownContent
dropDownContent += '
\n'
dropDownContent += '\n'
return dropDownContent
def htmlNewPost(cssCache: {}, mediaInstance: bool, translate: {},
base_dir: str, http_prefix: str,
path: str, inReplyTo: str,
mentions: [],
shareDescription: str,
reportUrl: str, pageNumber: int,
category: str,
nickname: str, domain: str,
domainFull: str,
defaultTimeline: str, newswire: {},
theme: str, noDropDown: bool,
accessKeys: {}, customSubmitText: str,
conversationId: str,
recentPostsCache: {}, maxRecentPosts: int,
session, cachedWebfingers: {},
personCache: {}, port: int,
postJsonObject: {},
projectVersion: str,
yt_replace_domain: str,
twitterReplacementDomain: str,
show_published_date_only: bool,
peertubeInstances: [],
allow_local_network_access: bool,
systemLanguage: str,
max_like_count: int, signingPrivateKeyPem: str,
CWlists: {}, lists_enabled: str,
boxName: str) -> str:
"""New post screen
"""
replyStr = ''
isNewReminder = False
if path.endswith('/newreminder'):
isNewReminder = True
# the date and time
dateAndTimeStr = '\n'
if not isNewReminder:
dateAndTimeStr += \
' \n'
# select a date and time for this post
dateAndTimeStr += '' + \
translate['Date'] + ': \n'
dateAndTimeStr += ' \n'
dateAndTimeStr += '' + \
translate['Time'] + ': '
dateAndTimeStr += \
' \n
\n'
showPublicOnDropdown = True
messageBoxHeight = 400
# filename of the banner shown at the top
bannerFile, bannerFilename = \
getBannerFile(base_dir, nickname, domain, theme)
if not path.endswith('/newshare') and not path.endswith('/newwanted'):
if not path.endswith('/newreport'):
if not inReplyTo or isNewReminder:
newPostText = '' + \
translate['Write your post text below.'] + ' \n'
else:
newPostText = ''
if category != 'accommodation':
newPostText = \
'' + \
translate['Write your reply to'] + \
' ' + \
translate['this post'] + '
\n'
if postJsonObject:
newPostText += \
individualPostAsHtml(signingPrivateKeyPem,
True, recentPostsCache,
maxRecentPosts,
translate, None,
base_dir, session,
cachedWebfingers,
personCache,
nickname, domain, port,
postJsonObject,
None, True, False,
http_prefix, projectVersion,
boxName,
yt_replace_domain,
twitterReplacementDomain,
show_published_date_only,
peertubeInstances,
allow_local_network_access,
theme, systemLanguage,
max_like_count,
False, False, False,
False, False, False,
CWlists, lists_enabled)
replyStr = ' \n'
# if replying to a non-public post then also make
# this post non-public
if not isPublicPostFromUrl(base_dir, nickname, domain,
inReplyTo):
newPostPath = path
if '?' in newPostPath:
newPostPath = newPostPath.split('?')[0]
if newPostPath.endswith('/newpost'):
path = path.replace('/newpost', '/newfollowers')
elif newPostPath.endswith('/newunlisted'):
path = path.replace('/newunlisted', '/newfollowers')
showPublicOnDropdown = False
else:
newPostText = \
'' + translate['Write your report below.'] + ' \n'
# custom report header with any additional instructions
if os.path.isfile(base_dir + '/accounts/report.txt'):
with open(base_dir + '/accounts/report.txt', 'r') as file:
customReportText = file.read()
if '' not in customReportText:
customReportText = \
'' + \
customReportText + '
\n'
repStr = ''
customReportText = \
customReportText.replace('
', repStr)
newPostText += customReportText
idx = 'This message only goes to moderators, even if it ' + \
'mentions other fediverse addresses.'
newPostText += \
'
' + translate[idx] + '
\n' + \
'' + translate['Also see'] + \
' ' + \
translate['Terms of Service'] + '
\n'
else:
if path.endswith('/newshare'):
newPostText = \
'' + \
translate['Enter the details for your shared item below.'] + \
' \n'
else:
newPostText = \
'' + \
translate['Enter the details for your wanted item below.'] + \
' \n'
if path.endswith('/newquestion'):
newPostText = \
'' + \
translate['Enter the choices for your question below.'] + \
' \n'
if os.path.isfile(base_dir + '/accounts/newpost.txt'):
with open(base_dir + '/accounts/newpost.txt', 'r') as file:
newPostText = \
'' + file.read() + '
\n'
cssFilename = base_dir + '/epicyon-profile.css'
if os.path.isfile(base_dir + '/epicyon.css'):
cssFilename = base_dir + '/epicyon.css'
if '?' in path:
path = path.split('?')[0]
newPostEndpoints = getNewPostEndpoints()
pathBase = path
for currPostType in newPostEndpoints:
pathBase = pathBase.replace('/' + currPostType, '')
newPostImageSection = ' \n'
newPostImageSection += \
editTextField(translate['Image description'], 'imageDescription', '')
newPostImageSection += \
' \n'
newPostImageSection += '
\n'
scopeIcon = 'scope_public.png'
scopeDescription = translate['Public']
if shareDescription:
if category == 'accommodation':
placeholderSubject = translate['Request to stay']
else:
placeholderSubject = translate['Ask about a shared item.'] + '..'
else:
placeholderSubject = \
translate['Subject or Content Warning (optional)'] + '...'
placeholderMentions = ''
if inReplyTo:
placeholderMentions = \
translate['Replying to'] + '...'
placeholderMessage = ''
if category != 'accommodation':
placeholderMessage = translate['Write something'] + '...'
else:
idx = 'Introduce yourself and specify the date ' + \
'and time when you wish to stay'
placeholderMessage = translate[idx]
extraFields = ''
endpoint = 'newpost'
if path.endswith('/newblog'):
placeholderSubject = translate['Title']
scopeIcon = 'scope_blog.png'
if defaultTimeline != 'tlfeatures':
scopeDescription = translate['Blog']
else:
scopeDescription = translate['Article']
endpoint = 'newblog'
elif path.endswith('/newunlisted'):
scopeIcon = 'scope_unlisted.png'
scopeDescription = translate['Unlisted']
endpoint = 'newunlisted'
elif path.endswith('/newfollowers'):
scopeIcon = 'scope_followers.png'
scopeDescription = translate['Followers']
endpoint = 'newfollowers'
elif path.endswith('/newdm'):
scopeIcon = 'scope_dm.png'
scopeDescription = translate['DM']
endpoint = 'newdm'
elif isNewReminder:
scopeIcon = 'scope_reminder.png'
scopeDescription = translate['Reminder']
endpoint = 'newreminder'
elif path.endswith('/newreport'):
scopeIcon = 'scope_report.png'
scopeDescription = translate['Report']
endpoint = 'newreport'
elif path.endswith('/newquestion'):
scopeIcon = 'scope_question.png'
scopeDescription = translate['Question']
placeholderMessage = translate['Enter your question'] + '...'
endpoint = 'newquestion'
extraFields = '\n'
extraFields += ' ' + \
translate['Possible answers'] + ': \n'
for questionCtr in range(8):
extraFields += \
' \n'
extraFields += \
' ' + \
translate['Duration of listing in days'] + \
': \n'
extraFields += '
'
elif path.endswith('/newshare'):
scopeIcon = 'scope_share.png'
scopeDescription = translate['Shared Item']
placeholderSubject = translate['Name of the shared item'] + '...'
placeholderMessage = \
translate['Description of the item being shared'] + '...'
endpoint = 'newshare'
extraFields = '\n'
extraFields += \
editNumberField(translate['Quantity'],
'itemQty', 1, 1, 999999, 1)
extraFields += ' ' + \
editTextField(translate['Type of shared item. eg. hat'] + ':',
'itemType', '', '', True)
categoryTypes = getCategoryTypes(base_dir)
catStr = translate['Category of shared item. eg. clothing']
extraFields += '' + catStr + ' \n'
extraFields += ' \n'
for category in categoryTypes:
translatedCategory = "food"
if translate.get(category):
translatedCategory = translate[category]
extraFields += ' ' + \
translatedCategory + ' \n'
extraFields += ' \n'
extraFields += \
editNumberField(translate['Duration of listing in days'],
'duration', 14, 1, 365, 1)
extraFields += '
\n'
extraFields += '\n'
cityOrLocStr = translate['City or location of the shared item']
extraFields += editTextField(cityOrLocStr + ':', 'location', '')
extraFields += '
\n'
extraFields += '\n'
extraFields += \
editCurrencyField(translate['Price'] + ':', 'itemPrice', '0.00',
'0.00', True)
extraFields += ' '
extraFields += \
'' + translate['Currency'] + ' \n'
currencies = getCurrencies()
extraFields += ' \n'
currencyList = []
for symbol, currName in currencies.items():
currencyList.append(currName + ' ' + symbol)
currencyList.sort()
defaultCurrency = getConfigParam(base_dir, 'defaultCurrency')
if not defaultCurrency:
defaultCurrency = "EUR"
for currName in currencyList:
if defaultCurrency not in currName:
extraFields += ' ' + currName + ' \n'
else:
extraFields += ' ' + \
currName + ' \n'
extraFields += ' \n'
extraFields += '
\n'
elif path.endswith('/newwanted'):
scopeIcon = 'scope_wanted.png'
scopeDescription = translate['Wanted']
placeholderSubject = translate['Name of the wanted item'] + '...'
placeholderMessage = \
translate['Description of the item wanted'] + '...'
endpoint = 'newwanted'
extraFields = '\n'
extraFields += \
editNumberField(translate['Quantity'],
'itemQty', 1, 1, 999999, 1)
extraFields += ' ' + \
editTextField(translate['Type of wanted item. eg. hat'] + ':',
'itemType', '', '', True)
categoryTypes = getCategoryTypes(base_dir)
catStr = translate['Category of wanted item. eg. clothes']
extraFields += '' + catStr + ' \n'
extraFields += ' \n'
for category in categoryTypes:
translatedCategory = "food"
if translate.get(category):
translatedCategory = translate[category]
extraFields += ' ' + \
translatedCategory + ' \n'
extraFields += ' \n'
extraFields += \
editNumberField(translate['Duration of listing in days'],
'duration', 14, 1, 365, 1)
extraFields += '
\n'
extraFields += '\n'
cityOrLocStr = translate['City or location of the wanted item']
extraFields += editTextField(cityOrLocStr + ':', 'location', '')
extraFields += '
\n'
extraFields += '\n'
extraFields += \
editCurrencyField(translate['Maximum Price'] + ':',
'itemPrice', '0.00', '0.00', True)
extraFields += ' '
extraFields += \
'' + translate['Currency'] + ' \n'
currencies = getCurrencies()
extraFields += ' \n'
currencyList = []
for symbol, currName in currencies.items():
currencyList.append(currName + ' ' + symbol)
currencyList.sort()
defaultCurrency = getConfigParam(base_dir, 'defaultCurrency')
if not defaultCurrency:
defaultCurrency = "EUR"
for currName in currencyList:
if defaultCurrency not in currName:
extraFields += ' ' + currName + ' \n'
else:
extraFields += ' ' + \
currName + ' \n'
extraFields += ' \n'
extraFields += '
\n'
citationsStr = ''
if endpoint == 'newblog':
citationsFilename = \
acctDir(base_dir, nickname, domain) + '/.citations.txt'
if os.path.isfile(citationsFilename):
citationsStr = '\n'
citationsStr += '
' + \
translate['Citations'] + ':
\n'
citationsStr += '
\n'
citationsSeparator = '#####'
with open(citationsFilename, 'r') as f:
citations = f.readlines()
for line in citations:
if citationsSeparator not in line:
continue
sections = line.strip().split(citationsSeparator)
if len(sections) != 3:
continue
title = sections[1]
link = sections[2]
citationsStr += \
' ' + \
title + ' '
citationsStr += ' \n'
citationsStr += '
\n'
dateAndLocation = ''
if endpoint != 'newshare' and \
endpoint != 'newwanted' and \
endpoint != 'newreport' and \
endpoint != 'newquestion':
if not isNewReminder:
dateAndLocation = \
'\n'
dateAndLocation += '\n'
dateAndLocation += \
editTextField(translate['Location'], 'location', '')
dateAndLocation += '
\n'
instanceTitle = getConfigParam(base_dir, 'instanceTitle')
newPostForm = htmlHeaderWithExternalStyle(cssFilename, instanceTitle, None)
newPostForm += \
'\n'
mentionsStr = ''
for m in mentions:
mentionNickname = getNicknameFromActor(m)
if not mentionNickname:
continue
mentionDomain, mentionPort = getDomainFromActor(m)
if not mentionDomain:
continue
if mentionPort:
mentionsHandle = \
'@' + mentionNickname + '@' + \
mentionDomain + ':' + str(mentionPort)
else:
mentionsHandle = '@' + mentionNickname + '@' + mentionDomain
if mentionsHandle not in mentionsStr:
mentionsStr += mentionsHandle + ' '
# build suffixes so that any replies or mentions are
# preserved when switching between scopes
dropdownNewPostSuffix = '/newpost'
dropdownNewBlogSuffix = '/newblog'
dropdownUnlistedSuffix = '/newunlisted'
dropdownFollowersSuffix = '/newfollowers'
dropdownDMSuffix = '/newdm'
dropdownReminderSuffix = '/newreminder'
dropdownReportSuffix = '/newreport'
if inReplyTo or mentions:
dropdownNewPostSuffix = ''
dropdownNewBlogSuffix = ''
dropdownUnlistedSuffix = ''
dropdownFollowersSuffix = ''
dropdownDMSuffix = ''
dropdownReminderSuffix = ''
dropdownReportSuffix = ''
if inReplyTo:
dropdownNewPostSuffix += '?replyto=' + inReplyTo
dropdownNewBlogSuffix += '?replyto=' + inReplyTo
dropdownUnlistedSuffix += '?replyto=' + inReplyTo
dropdownFollowersSuffix += '?replyfollowers=' + inReplyTo
dropdownDMSuffix += '?replydm=' + inReplyTo
for mentionedActor in mentions:
dropdownNewPostSuffix += '?mention=' + mentionedActor
dropdownNewBlogSuffix += '?mention=' + mentionedActor
dropdownUnlistedSuffix += '?mention=' + mentionedActor
dropdownFollowersSuffix += '?mention=' + mentionedActor
dropdownDMSuffix += '?mention=' + mentionedActor
dropdownReportSuffix += '?mention=' + mentionedActor
if conversationId and inReplyTo:
dropdownNewPostSuffix += '?conversationId=' + conversationId
dropdownNewBlogSuffix += '?conversationId=' + conversationId
dropdownUnlistedSuffix += '?conversationId=' + conversationId
dropdownFollowersSuffix += '?conversationId=' + conversationId
dropdownDMSuffix += '?conversationId=' + conversationId
dropDownContent = ''
if not reportUrl and not shareDescription:
dropDownContent = \
_htmlNewPostDropDown(scopeIcon, scopeDescription,
replyStr,
translate,
showPublicOnDropdown,
defaultTimeline,
pathBase,
dropdownNewPostSuffix,
dropdownNewBlogSuffix,
dropdownUnlistedSuffix,
dropdownFollowersSuffix,
dropdownDMSuffix,
dropdownReminderSuffix,
dropdownReportSuffix,
noDropDown, accessKeys)
else:
if not shareDescription:
# reporting a post to moderator
mentionsStr = 'Re: ' + reportUrl + '\n\n' + mentionsStr
newPostForm += \
'\n'
if not reportUrl:
newPostForm = \
newPostForm.replace('', '')
newPostForm += htmlFooter()
return newPostForm