forked from indymedia/epicyon
Editing news posts
parent
fa945479b5
commit
5085587ad4
174
daemon.py
174
daemon.py
|
@ -62,6 +62,7 @@ from person import canRemovePost
|
|||
from person import personSnooze
|
||||
from person import personUnsnooze
|
||||
from posts import isModerator
|
||||
from posts import isEditor
|
||||
from posts import mutePost
|
||||
from posts import unmutePost
|
||||
from posts import createQuestionPost
|
||||
|
@ -146,6 +147,7 @@ from webinterface import htmlProfileAfterSearch
|
|||
from webinterface import htmlEditProfile
|
||||
from webinterface import htmlEditLinks
|
||||
from webinterface import htmlEditNewswire
|
||||
from webinterface import htmlEditNewsPost
|
||||
from webinterface import htmlTermsOfService
|
||||
from webinterface import htmlSkillsSearch
|
||||
from webinterface import htmlHistorySearch
|
||||
|
@ -1096,7 +1098,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return True
|
||||
elif '/' + nickname + '?' in self.path:
|
||||
return True
|
||||
elif self.path.endswith('/'+nickname):
|
||||
elif self.path.endswith('/' + nickname):
|
||||
return True
|
||||
print('AUTH: nickname ' + nickname +
|
||||
' was not found in path ' + self.path)
|
||||
|
@ -2929,6 +2931,133 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _newsPostEdit(self, callingDomain: str, cookie: str,
|
||||
authorized: bool, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
domain: str, domainFull: str,
|
||||
onionDomain: str, i2pDomain: str, debug: bool,
|
||||
defaultTimeline: str):
|
||||
"""edits a news post
|
||||
"""
|
||||
usersPath = path.replace('/newseditdata', '')
|
||||
usersPath = usersPath.replace('/editnewspost', '')
|
||||
actorStr = httpPrefix + '://' + domainFull + usersPath
|
||||
if ' boundary=' in self.headers['Content-type']:
|
||||
boundary = self.headers['Content-type'].split('boundary=')[1]
|
||||
if ';' in boundary:
|
||||
boundary = boundary.split(';')[0]
|
||||
|
||||
# get the nickname
|
||||
nickname = getNicknameFromActor(actorStr)
|
||||
editorRole = None
|
||||
if nickname:
|
||||
editorRole = isEditor(baseDir, nickname)
|
||||
if not nickname or not editorRole:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
if not nickname:
|
||||
print('WARN: nickname not found in ' + actorStr)
|
||||
else:
|
||||
print('WARN: nickname is not an editor' + actorStr)
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
length = int(self.headers['Content-length'])
|
||||
|
||||
# check that the POST isn't too large
|
||||
if length > self.server.maxPostLength:
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
print('Maximum news data length exceeded ' + str(length))
|
||||
self._redirect_headers(actorStr, cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
try:
|
||||
# read the bytes of the http form POST
|
||||
postBytes = self.rfile.read(length)
|
||||
except SocketError as e:
|
||||
if e.errno == errno.ECONNRESET:
|
||||
print('WARN: connection was reset while ' +
|
||||
'reading bytes from http form POST')
|
||||
else:
|
||||
print('WARN: error while reading bytes ' +
|
||||
'from http form POST')
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
except ValueError as e:
|
||||
print('ERROR: failed to read bytes for POST')
|
||||
print(e)
|
||||
self.send_response(400)
|
||||
self.end_headers()
|
||||
self.server.POSTbusy = False
|
||||
return
|
||||
|
||||
# extract all of the text fields into a dict
|
||||
fields = \
|
||||
extractTextFieldsInPOST(postBytes, boundary, debug)
|
||||
newsPostUrl = None
|
||||
newsPostTitle = None
|
||||
newsPostContent = None
|
||||
if fields.get('newsPostUrl'):
|
||||
newsPostUrl = fields['newsPostUrl']
|
||||
if fields.get('newsPostTitle'):
|
||||
newsPostTitle = fields['newsPostTitle']
|
||||
if fields.get('editedNewsPost'):
|
||||
newsPostContent = fields['editedNewsPost']
|
||||
|
||||
if newsPostUrl and newsPostContent and newsPostTitle:
|
||||
# load the post
|
||||
postFilename = \
|
||||
locatePost(baseDir, nickname, domain,
|
||||
newsPostUrl)
|
||||
if postFilename:
|
||||
postJsonObject = loadJson(postFilename)
|
||||
# update the content and title
|
||||
postJsonObject['object']['summary'] = \
|
||||
newsPostTitle
|
||||
postJsonObject['object']['content'] = \
|
||||
newsPostContent
|
||||
postUrl = postJsonObject['object']['id']
|
||||
postUrl = postUrl.replace('/', '#')
|
||||
# remove the html from post cache
|
||||
cachedPost = \
|
||||
baseDir + '/accounts/' + \
|
||||
nickname + '@' + domain + \
|
||||
'/postcache/' + postUrl + '.html'
|
||||
if os.path.isfile(cachedPost):
|
||||
os.remove(cachedPost)
|
||||
# save the news post
|
||||
saveJson(postJsonObject, postFilename)
|
||||
|
||||
# redirect back to the default timeline
|
||||
if callingDomain.endswith('.onion') and \
|
||||
onionDomain:
|
||||
actorStr = \
|
||||
'http://' + onionDomain + usersPath
|
||||
elif (callingDomain.endswith('.i2p') and
|
||||
i2pDomain):
|
||||
actorStr = \
|
||||
'http://' + i2pDomain + usersPath
|
||||
self._redirect_headers(actorStr + '/' + defaultTimeline,
|
||||
cookie, callingDomain)
|
||||
self.server.POSTbusy = False
|
||||
|
||||
def _profileUpdate(self, callingDomain: str, cookie: str,
|
||||
authorized: bool, path: str,
|
||||
baseDir: str, httpPrefix: str,
|
||||
|
@ -7976,6 +8105,29 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _editNewsPost(self, callingDomain: str, path: str,
|
||||
translate: {}, baseDir: str,
|
||||
httpPrefix: str, domain: str, port: int,
|
||||
cookie: str) -> bool:
|
||||
"""Show the edit screen for a news post
|
||||
"""
|
||||
if '/users/' in path and '/editnewspost=' in path:
|
||||
postUrl = path.split('/editnewspost=')[1]
|
||||
if '?' in postUrl:
|
||||
postUrl = postUrl.split('?')[0]
|
||||
msg = htmlEditNewsPost(translate, baseDir,
|
||||
path, domain, port,
|
||||
httpPrefix, postUrl).encode('utf-8')
|
||||
if msg:
|
||||
self._set_headers('text/html', len(msg),
|
||||
cookie, callingDomain)
|
||||
self._write(msg)
|
||||
else:
|
||||
self._404()
|
||||
self.server.GETbusy = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _editEvent(self, callingDomain: str, path: str,
|
||||
httpPrefix: str, domain: str, domainFull: str,
|
||||
baseDir: str, translate: {},
|
||||
|
@ -9357,6 +9509,16 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
cookie):
|
||||
return
|
||||
|
||||
# edit news post
|
||||
if self._editNewsPost(callingDomain, self.path,
|
||||
self.server.translate,
|
||||
self.server.baseDir,
|
||||
self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.port,
|
||||
cookie):
|
||||
return
|
||||
|
||||
if self._showNewPost(callingDomain, self.path,
|
||||
self.server.mediaInstance,
|
||||
self.server.translate,
|
||||
|
@ -10845,6 +11007,16 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.defaultTimeline)
|
||||
return
|
||||
|
||||
if authorized and self.path.endswith('/newseditdata'):
|
||||
self._newsPostEdit(callingDomain, cookie, authorized, self.path,
|
||||
self.server.baseDir, self.server.httpPrefix,
|
||||
self.server.domain,
|
||||
self.server.domainFull,
|
||||
self.server.onionDomain,
|
||||
self.server.i2pDomain, self.server.debug,
|
||||
self.server.defaultTimeline)
|
||||
return
|
||||
|
||||
self._benchmarkPOSTtimings(POSTstartTime, POSTtimings, 3)
|
||||
|
||||
# moderator action buttons
|
||||
|
|
|
@ -35,7 +35,6 @@ from auth import removePassword
|
|||
from roles import setRole
|
||||
from media import removeMetaData
|
||||
from utils import validNickname
|
||||
from utils import noOfAccounts
|
||||
from utils import loadJson
|
||||
from utils import saveJson
|
||||
from utils import setConfigParam
|
||||
|
@ -450,6 +449,7 @@ def createPerson(baseDir: str, nickname: str, domain: str, port: int,
|
|||
setConfigParam(baseDir, 'admin', nickname)
|
||||
setRole(baseDir, nickname, domain, 'instance', 'admin')
|
||||
setRole(baseDir, nickname, domain, 'instance', 'moderator')
|
||||
setRole(baseDir, nickname, domain, 'instance', 'editor')
|
||||
setRole(baseDir, nickname, domain, 'instance', 'delegator')
|
||||
|
||||
if not os.path.isdir(baseDir + '/accounts'):
|
||||
|
|
28
posts.py
28
posts.py
|
@ -96,6 +96,34 @@ def isModerator(baseDir: str, nickname: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def isEditor(baseDir: str, nickname: str) -> bool:
|
||||
"""Returns true if the given nickname is an editor
|
||||
"""
|
||||
editorsFile = baseDir + '/accounts/editors.txt'
|
||||
|
||||
if not os.path.isfile(editorsFile):
|
||||
adminName = getConfigParam(baseDir, 'admin')
|
||||
if not adminName:
|
||||
return False
|
||||
if adminName == nickname:
|
||||
return True
|
||||
return False
|
||||
|
||||
with open(editorsFile, "r") as f:
|
||||
lines = f.readlines()
|
||||
if len(lines) == 0:
|
||||
adminName = getConfigParam(baseDir, 'admin')
|
||||
if not adminName:
|
||||
return False
|
||||
if adminName == nickname:
|
||||
return True
|
||||
for editor in lines:
|
||||
editor = editor.strip('\n').strip('\r')
|
||||
if editor == nickname:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def noOfFollowersOnDomain(baseDir: str, handle: str,
|
||||
domain: str, followFile='followers.txt') -> int:
|
||||
"""Returns the number of followers of the given handle from the given domain
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "إزالة التصويت",
|
||||
"This is a news instance": "هذا مثال أخبار",
|
||||
"News": "أخبار",
|
||||
"Read more...": "اقرأ أكثر..."
|
||||
"Read more...": "اقرأ أكثر...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Elimina el vot",
|
||||
"This is a news instance": "Aquesta és una instància de notícies",
|
||||
"News": "Notícies",
|
||||
"Read more...": "Llegeix més..."
|
||||
"Read more...": "Llegeix més...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Tynnwch y Bleidlais",
|
||||
"This is a news instance": "Dyma enghraifft newyddion",
|
||||
"News": "Newyddion",
|
||||
"Read more...": "Darllen mwy..."
|
||||
"Read more...": "Darllen mwy...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Abstimmung entfernen",
|
||||
"This is a news instance": "Dies ist eine Nachrichteninstanz",
|
||||
"News": "Nachrichten",
|
||||
"Read more...": "Weiterlesen..."
|
||||
"Read more...": "Weiterlesen...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Remove Vote",
|
||||
"This is a news instance": "This is a news instance",
|
||||
"News": "News",
|
||||
"Read more...": "Read more..."
|
||||
"Read more...": "Read more...",
|
||||
"Edit News Post": "Edit News Post"
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Eliminar voto",
|
||||
"This is a news instance": "Esta es una instancia de noticias",
|
||||
"News": "Noticias",
|
||||
"Read more...": "Lee mas..."
|
||||
"Read more...": "Lee mas...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Supprimer le vote",
|
||||
"This is a news instance": "Ceci est une instance d'actualité",
|
||||
"News": "Nouvelles",
|
||||
"Read more...": "Lire la suite..."
|
||||
"Read more...": "Lire la suite...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Bain Vóta",
|
||||
"This is a news instance": "Is sampla nuachta é seo",
|
||||
"News": "Nuacht",
|
||||
"Read more...": "Leigh Nios mo..."
|
||||
"Read more...": "Leigh Nios mo...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "वोट हटा दें",
|
||||
"This is a news instance": "यह एक समाचार का उदाहरण है",
|
||||
"News": "समाचार",
|
||||
"Read more...": "अधिक पढ़ें..."
|
||||
"Read more...": "अधिक पढ़ें...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Rimuovi voto",
|
||||
"This is a news instance": "Questa è un'istanza di notizie",
|
||||
"News": "Notizia",
|
||||
"Read more...": "Leggi di più..."
|
||||
"Read more...": "Leggi di più...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "投票を削除",
|
||||
"This is a news instance": "これはニュースインスタンスです",
|
||||
"News": "ニュース",
|
||||
"Read more...": "続きを読む..."
|
||||
"Read more...": "続きを読む...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -301,5 +301,6 @@
|
|||
"Remove Vote": "Remove Vote",
|
||||
"This is a news instance": "This is a news instance",
|
||||
"News": "News",
|
||||
"Read more...": "Read more..."
|
||||
"Read more...": "Read more...",
|
||||
"Edit News Post": "Edit News Post"
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Remover voto",
|
||||
"This is a news instance": "Esta é uma instância de notícias",
|
||||
"News": "Notícia",
|
||||
"Read more...": "Consulte Mais informação..."
|
||||
"Read more...": "Consulte Mais informação...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "Удалить голос",
|
||||
"This is a news instance": "Это новостной экземпляр",
|
||||
"News": "Новости",
|
||||
"Read more...": "Подробнее..."
|
||||
"Read more...": "Подробнее...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -305,5 +305,6 @@
|
|||
"Remove Vote": "删除投票",
|
||||
"This is a news instance": "这是一个新闻实例",
|
||||
"News": "新闻",
|
||||
"Read more...": "阅读更多..."
|
||||
"Read more...": "阅读更多...",
|
||||
"Edit News Post": ""
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ from posts import getUserUrl
|
|||
from posts import parseUserFeed
|
||||
from posts import populateRepliesJson
|
||||
from posts import isModerator
|
||||
from posts import isEditor
|
||||
from posts import downloadAnnounce
|
||||
from session import getJson
|
||||
from auth import createPassword
|
||||
|
@ -1346,6 +1347,85 @@ def htmlEditNewswire(translate: {}, baseDir: str, path: str,
|
|||
return editNewswireForm
|
||||
|
||||
|
||||
def htmlEditNewsPost(translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int,
|
||||
httpPrefix: str, postUrl: str) -> str:
|
||||
"""Edits a news post
|
||||
"""
|
||||
if '/users/' not in path:
|
||||
return ''
|
||||
pathOriginal = path
|
||||
path = path.replace('/inbox', '').replace('/outbox', '')
|
||||
path = path.replace('/shares', '')
|
||||
path = path.replace('/tlnews', '')
|
||||
|
||||
nickname = getNicknameFromActor(path)
|
||||
if not nickname:
|
||||
return ''
|
||||
|
||||
# is the user an editor?
|
||||
if not isEditor(baseDir, nickname):
|
||||
return ''
|
||||
|
||||
postFilename = locatePost(baseDir, nickname, domain, postUrl)
|
||||
if not postFilename:
|
||||
return ''
|
||||
postJsonObject = loadJson(postFilename)
|
||||
if not postJsonObject:
|
||||
return ''
|
||||
|
||||
cssFilename = baseDir + '/epicyon-links.css'
|
||||
if os.path.isfile(baseDir + '/links.css'):
|
||||
cssFilename = baseDir + '/links.css'
|
||||
with open(cssFilename, 'r') as cssFile:
|
||||
editCSS = cssFile.read()
|
||||
if httpPrefix != 'https':
|
||||
editCSS = \
|
||||
editCSS.replace('https://', httpPrefix + '://')
|
||||
|
||||
editNewsPostForm = htmlHeader(cssFilename, editCSS)
|
||||
editNewsPostForm += \
|
||||
'<form enctype="multipart/form-data" method="POST" ' + \
|
||||
'accept-charset="UTF-8" action="' + path + '/newseditdata">\n'
|
||||
editNewsPostForm += \
|
||||
' <div class="vertical-center">\n'
|
||||
editNewsPostForm += \
|
||||
' <p class="new-post-text">' + translate['Edit News Post'] + '</p>'
|
||||
editNewsPostForm += \
|
||||
' <div class="container">\n'
|
||||
editNewsPostForm += \
|
||||
' <a href="' + pathOriginal + '"><button class="cancelbtn">' + \
|
||||
translate['Go Back'] + '</button></a>\n'
|
||||
editNewsPostForm += \
|
||||
' <input type="submit" name="submitEditedNewsPost" value="' + \
|
||||
translate['Submit'] + '">\n'
|
||||
editNewsPostForm += \
|
||||
' </div>\n'
|
||||
|
||||
editNewsPostForm += \
|
||||
'<div class="container">'
|
||||
|
||||
editNewsPostForm += \
|
||||
' <input type="hidden" name="newsPostUrl" value="' + \
|
||||
postUrl + '">\n'
|
||||
|
||||
newsPostTitle = postJsonObject['object']['summary']
|
||||
editNewsPostForm += \
|
||||
' <input type="text" name="newsPostTitle" value="' + \
|
||||
newsPostTitle + '"><br>\n'
|
||||
|
||||
newsPostContent = postJsonObject['object']['content']
|
||||
editNewsPostForm += \
|
||||
' <textarea id="message" name="editedNewsPost" ' + \
|
||||
'style="height:600px">' + newsPostContent + '</textarea>'
|
||||
|
||||
editNewsPostForm += \
|
||||
'</div>'
|
||||
|
||||
editNewsPostForm += htmlFooter()
|
||||
return editNewsPostForm
|
||||
|
||||
|
||||
def htmlEditProfile(translate: {}, baseDir: str, path: str,
|
||||
domain: str, port: int, httpPrefix: str) -> str:
|
||||
"""Shows the edit profile screen
|
||||
|
@ -4515,8 +4595,8 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
if fullDomain + '/users/' + nickname in postJsonObject['actor']:
|
||||
if '/statuses/' in postJsonObject['object']['id']:
|
||||
if isBlogPost(postJsonObject):
|
||||
blogPostId = postJsonObject['object']['id']
|
||||
if not isNewsPost(postJsonObject):
|
||||
blogPostId = postJsonObject['object']['id']
|
||||
editStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
|
@ -4529,6 +4609,18 @@ def individualPostAsHtml(allowDownloads: bool,
|
|||
translate['Edit blog post'] + '" alt="' + \
|
||||
translate['Edit blog post'] + \
|
||||
' |" src="/' + iconsDir + '/edit.png"/></a>\n'
|
||||
else:
|
||||
editStr += \
|
||||
' ' + \
|
||||
'<a class="imageAnchor" href="/users/' + \
|
||||
nickname + '/editnewspost=' + \
|
||||
blogPostId.replace('/', '#') + \
|
||||
'?actor=' + actorNickname + \
|
||||
'" title="' + translate['Edit blog post'] + '">' + \
|
||||
'<img loading="lazy" title="' + \
|
||||
translate['Edit blog post'] + '" alt="' + \
|
||||
translate['Edit blog post'] + \
|
||||
' |" src="/' + iconsDir + '/edit.png"/></a>\n'
|
||||
elif isEvent:
|
||||
eventPostId = postJsonObject['object']['id']
|
||||
editStr += \
|
||||
|
|
Loading…
Reference in New Issue