feat(dsi): add creator controls and comment pagination
Allow owners to edit details or remove uploads, and paginate comments without changing original play/save responses.
This commit is contained in:
1 parent
298d602395
commit
807df47534
1 file changed
+73
-12
+73
-12
@@ -7,7 +7,7 @@ from twisted.web import resource
|
|||||||
from DB import Database
|
from DB import Database
|
||||||
from Hatenatools import TMB
|
from Hatenatools import TMB
|
||||||
from hatena import NotFound, as_text, client_ip, request_headers
|
from hatena import NotFound, as_text, client_ip, request_headers
|
||||||
from reximemo import current_account, dialog, dsi_url, forward, html_page, keyboard_value
|
from reximemo import current_account, dialog, dsi_text_html, dsi_url, forward, html_page, keyboard_value, page_number, pagination_html, posting_account, read_ppm_upload
|
||||||
|
|
||||||
|
|
||||||
class PyResource(resource.Resource):
|
class PyResource(resource.Resource):
|
||||||
@@ -29,7 +29,7 @@ class CreatorNode(resource.Resource):
|
|||||||
text = as_text(name)
|
text = as_text(name)
|
||||||
if "." in text:
|
if "." in text:
|
||||||
base, ext = text.rsplit(".", 1)
|
base, ext = text.rsplit(".", 1)
|
||||||
if ext in {"ppm", "tmb", "info", "htm", "star", "dl", "reply"} and Database.FlipnoteExists(self.fsid, base):
|
if ext in {"ppm", "tmb", "info", "htm", "star", "dl", "reply", "delete"} and Database.FlipnoteExists(self.fsid, base):
|
||||||
return FlipnoteFile(self.fsid, base, ext)
|
return FlipnoteFile(self.fsid, base, ext)
|
||||||
if Database.FlipnoteExists(self.fsid, text):
|
if Database.FlipnoteExists(self.fsid, text):
|
||||||
return FlipnoteActionNode(self.fsid, text)
|
return FlipnoteActionNode(self.fsid, text)
|
||||||
@@ -43,6 +43,8 @@ class FlipnoteActionNode(resource.Resource):
|
|||||||
def getChild(self, name, request):
|
def getChild(self, name, request):
|
||||||
if as_text(name) == "keyboard.kbd":
|
if as_text(name) == "keyboard.kbd":
|
||||||
return TextComment(self.fsid, self.filename)
|
return TextComment(self.fsid, self.filename)
|
||||||
|
if as_text(name) in {"title.kbd", "description.kbd"}:
|
||||||
|
return MetadataKeyboard(self.fsid, self.filename, as_text(name).split(".", 1)[0])
|
||||||
return NotFound
|
return NotFound
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +56,9 @@ class TextComment(resource.Resource):
|
|||||||
fn = Database.get_flipnote(self.fsid, self.filename)
|
fn = Database.get_flipnote(self.fsid, self.filename)
|
||||||
if not fn:
|
if not fn:
|
||||||
return NotFound.render(request)
|
return NotFound.render(request)
|
||||||
account = current_account(request)
|
account, denied = posting_account(request)
|
||||||
|
if denied is not None:
|
||||||
|
return denied
|
||||||
try:
|
try:
|
||||||
Database.add_text_comment(
|
Database.add_text_comment(
|
||||||
fn["id"], keyboard_value(request), ip_address=client_ip(request),
|
fn["id"], keyboard_value(request), ip_address=client_ip(request),
|
||||||
@@ -68,6 +72,36 @@ class TextComment(resource.Resource):
|
|||||||
request.setResponseCode(405); return b""
|
request.setResponseCode(405); return b""
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataKeyboard(resource.Resource):
|
||||||
|
isLeaf = True
|
||||||
|
|
||||||
|
def __init__(self, fsid, filename, field):
|
||||||
|
super().__init__()
|
||||||
|
self.fsid, self.filename, self.field = fsid, filename, field
|
||||||
|
|
||||||
|
def render_POST(self, request):
|
||||||
|
flipnote = Database.get_flipnote(self.fsid, self.filename)
|
||||||
|
if not flipnote:
|
||||||
|
return NotFound.render(request)
|
||||||
|
account = current_account(request)
|
||||||
|
if not Database.owns_flipnote(account["id"] if account else None, client_ip(request), flipnote):
|
||||||
|
return dialog(request, "You can only edit Flipnotes you posted.")
|
||||||
|
value = keyboard_value(request)
|
||||||
|
fields = {"title": flipnote["title"], "description": flipnote.get("description") or "",
|
||||||
|
"channel_id": flipnote.get("channel_id") or 1}
|
||||||
|
fields[self.field] = value
|
||||||
|
try:
|
||||||
|
Database.update_flipnote(flipnote["id"], **fields)
|
||||||
|
except ValueError as exc:
|
||||||
|
return dialog(request, str(exc))
|
||||||
|
forward(request, dsi_url(request, f"movie/{self.fsid}/{self.filename}.htm"))
|
||||||
|
return b"OK"
|
||||||
|
|
||||||
|
def render_GET(self, request):
|
||||||
|
request.setResponseCode(405)
|
||||||
|
return b""
|
||||||
|
|
||||||
|
|
||||||
class FlipnoteFile(resource.Resource):
|
class FlipnoteFile(resource.Resource):
|
||||||
isLeaf = True
|
isLeaf = True
|
||||||
def __init__(self, fsid, filename, ext):
|
def __init__(self, fsid, filename, ext):
|
||||||
@@ -106,14 +140,26 @@ class FlipnoteFile(resource.Resource):
|
|||||||
fn = Database.get_flipnote(self.fsid, self.filename)
|
fn = Database.get_flipnote(self.fsid, self.filename)
|
||||||
if not fn:
|
if not fn:
|
||||||
return NotFound.render(request)
|
return NotFound.render(request)
|
||||||
|
if self.ext == "delete":
|
||||||
|
account = current_account(request)
|
||||||
|
if not Database.owns_flipnote(account["id"] if account else None, client_ip(request), fn):
|
||||||
|
return dialog(request, "You can only remove Flipnotes you posted.")
|
||||||
|
Database.delete_flipnote(fn["id"])
|
||||||
|
forward(request, dsi_url(request, f"creator/{fn['creator_room_id']}/movies.uls"))
|
||||||
|
return b"OK"
|
||||||
if self.ext == "reply":
|
if self.ext == "reply":
|
||||||
data = request.content.read()
|
account, denied = posting_account(request)
|
||||||
|
if denied is not None:
|
||||||
|
return denied
|
||||||
|
try:
|
||||||
|
data = read_ppm_upload(request)
|
||||||
|
except ValueError as exc:
|
||||||
|
return dialog(request, str(exc))
|
||||||
tmb = TMB().Read(data)
|
tmb = TMB().Read(data)
|
||||||
if not tmb:
|
if not tmb:
|
||||||
return dialog(request, "That mini Flipnote is invalid.")
|
return dialog(request, "That mini Flipnote is invalid.")
|
||||||
if int(getattr(tmb, "FrameCount", 1)) != 1:
|
if int(getattr(tmb, "FrameCount", 1)) != 1:
|
||||||
return dialog(request, "Mini Flipnote comments must contain one frame.")
|
return dialog(request, "Mini Flipnote comments must contain one frame.")
|
||||||
account = current_account(request)
|
|
||||||
try:
|
try:
|
||||||
Database.add_memo_comment(
|
Database.add_memo_comment(
|
||||||
fn["id"], data, ip_address=client_ip(request),
|
fn["id"], data, ip_address=client_ip(request),
|
||||||
@@ -148,7 +194,18 @@ def details_page(request, fn):
|
|||||||
"star": html.escape(star_url, quote=True),
|
"star": html.escape(star_url, quote=True),
|
||||||
"reply": html.escape(reply_url, quote=True),
|
"reply": html.escape(reply_url, quote=True),
|
||||||
}
|
}
|
||||||
comments = Database.list_comments(fn["id"], limit=30)
|
account = current_account(request)
|
||||||
|
owner = Database.owns_flipnote(account["id"] if account else None, client_ip(request), fn)
|
||||||
|
editing = ""
|
||||||
|
if owner:
|
||||||
|
extra += '<meta name="deletebutton" content="%s">' % html.escape(dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.delete"), quote=True)
|
||||||
|
editing = '<p><a href="%s">Edit title</a> | <a href="%s">Edit description</a></p>' % (
|
||||||
|
html.escape(dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}/title.kbd"), quote=True),
|
||||||
|
html.escape(dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}/description.kbd"), quote=True),
|
||||||
|
)
|
||||||
|
comment_total = Database.count_comments(fn["id"])
|
||||||
|
comment_page = page_number(request, comment_total, 30, "comments_page")
|
||||||
|
comments = Database.list_comments(fn["id"], limit=30, offset=(comment_page - 1) * 30)
|
||||||
comment_html = []
|
comment_html = []
|
||||||
for c in comments:
|
for c in comments:
|
||||||
if c["type"] == "memo":
|
if c["type"] == "memo":
|
||||||
@@ -156,10 +213,10 @@ def details_page(request, fn):
|
|||||||
ppm = dsi_url(request, f"comment/{c['id']}.ppm")
|
ppm = dsi_url(request, f"comment/{c['id']}.ppm")
|
||||||
content = '<a href="%s"><img src="%s" width="128" height="64"></a>' % (html.escape(ppm, quote=True), html.escape(npf, quote=True))
|
content = '<a href="%s"><img src="%s" width="128" height="64"></a>' % (html.escape(ppm, quote=True), html.escape(npf, quote=True))
|
||||||
else:
|
else:
|
||||||
content = html.escape(c.get("text_content") or "")
|
content = dsi_text_html(c.get("text_content") or "", preserve_newlines=True)
|
||||||
comment_html.append(
|
comment_html.append(
|
||||||
'<div class="comment"><strong>%s</strong><br>%s</div><div class="hr"></div>' % (
|
'<div class="comment"><strong>%s</strong><br>%s</div><div class="hr"></div>' % (
|
||||||
html.escape(c.get("creator_name") or "Guest"), content,
|
dsi_text_html(c.get("creator_name") or "Guest"), content,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
body = (
|
body = (
|
||||||
@@ -169,17 +226,21 @@ def details_page(request, fn):
|
|||||||
'<tr><th>Stars</th><td>%(stars)d</td></tr>'
|
'<tr><th>Stars</th><td>%(stars)d</td></tr>'
|
||||||
'<tr><th>Views</th><td>%(views)d</td></tr>'
|
'<tr><th>Views</th><td>%(views)d</td></tr>'
|
||||||
'</table>'
|
'</table>'
|
||||||
'<p><a href="%(ppm)s">Play Flipnote</a></p>'
|
'%(description)s%(editing)s<p><a href="%(ppm)s">Play Flipnote</a></p>'
|
||||||
'<p><a href="%(text)s">Text Comment</a></p>'
|
'<p><a href="%(text)s">Text Comment</a></p>'
|
||||||
'<div class="hr"></div><div class="subtitle">Comments</div>%(comments)s'
|
'<div class="hr"></div><div class="subtitle">Comments (%(comment_count)d)</div>%(comments)s%(comment_pager)s'
|
||||||
) % {
|
) % {
|
||||||
"title": html.escape(fn.get("title") or "Untitled Flipnote"),
|
"title": dsi_text_html(fn.get("title") or "Untitled Flipnote"),
|
||||||
"creator_url": html.escape(creator_url, quote=True),
|
"creator_url": html.escape(creator_url, quote=True),
|
||||||
"creator": html.escape(creator_name),
|
"creator": dsi_text_html(creator_name),
|
||||||
"stars": int(fn.get("stars") or 0),
|
"stars": int(fn.get("stars") or 0),
|
||||||
"views": int(fn.get("views") or 0),
|
"views": int(fn.get("views") or 0),
|
||||||
"ppm": html.escape(ppm_url, quote=True),
|
"ppm": html.escape(ppm_url, quote=True),
|
||||||
"text": html.escape(text_url, quote=True),
|
"text": html.escape(text_url, quote=True),
|
||||||
"comments": "".join(comment_html) or '<div class="notice">No comments yet.</div>',
|
"comments": "".join(comment_html) or '<div class="notice">No comments yet.</div>',
|
||||||
|
"comment_count": comment_total,
|
||||||
|
"comment_pager": pagination_html(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.htm", comment_page, comment_total, 30, name="comments_page"),
|
||||||
|
"description": '<p>%s</p>' % dsi_text_html(fn["description"], preserve_newlines=True) if fn.get("description") else "",
|
||||||
|
"editing": editing,
|
||||||
}
|
}
|
||||||
return html_page(request, fn.get("title") or "Flipnote", body, extra)
|
return html_page(request, fn.get("title") or "Flipnote", body, extra)
|
||||||
Reference in new issue
Block a user
