from __future__ import annotations import html from twisted.web import resource from DB import Database from Hatenatools import TMB from hatena import NotFound, as_text, client_ip, request_headers 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): isLeaf = False def getChild(self, name, request): fsid = as_text(name).upper() return CreatorNode(fsid) if Database.CreatorExists(fsid) else NotFound def render(self, request): request.setResponseCode(403); return b"" class CreatorNode(resource.Resource): isLeaf = False def __init__(self, fsid): super().__init__(); self.fsid = fsid def getChild(self, name, request): text = as_text(name) if "." in text: base, ext = text.rsplit(".", 1) if ext in {"ppm", "tmb", "info", "htm", "star", "dl", "reply", "delete"} and Database.FlipnoteExists(self.fsid, base): return FlipnoteFile(self.fsid, base, ext) if Database.FlipnoteExists(self.fsid, text): return FlipnoteActionNode(self.fsid, text) return NotFound class FlipnoteActionNode(resource.Resource): isLeaf = False def __init__(self, fsid, filename): super().__init__(); self.fsid = fsid; self.filename = filename def getChild(self, name, request): if as_text(name) == "keyboard.kbd": 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 class TextComment(resource.Resource): isLeaf = True def __init__(self, fsid, filename): super().__init__(); self.fsid = fsid; self.filename = filename def render_POST(self, request): fn = Database.get_flipnote(self.fsid, self.filename) if not fn: return NotFound.render(request) account, denied = posting_account(request) if denied is not None: return denied try: Database.add_text_comment( fn["id"], keyboard_value(request), ip_address=client_ip(request), account_id=(account["id"] if account else None), ) 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 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): isLeaf = True def __init__(self, fsid, filename, ext): super().__init__(); self.fsid = fsid; self.filename = filename; self.ext = ext def render_GET(self, request): fn = Database.get_flipnote(self.fsid, self.filename) if not fn: return NotFound.render(request) if self.ext == "ppm": Database.AddView(self.fsid, self.filename) request.setHeader(b"content-type", b"application/octet-stream") return Database.GetFlipnotePPM(self.fsid, self.filename) if self.ext == "tmb": request.setHeader(b"content-type", b"application/octet-stream") return Database.GetFlipnoteTMB(self.fsid, self.filename) if self.ext == "info": request.setHeader(b"content-type", b"text/plain") return b"0\n0\n" if self.ext == "dl": Database.AddDownload(self.fsid, self.filename) return b"OK" if self.ext == "star": headers = request_headers(request) try: amount = int(headers.get("x-hatena-star-count", "1")) except ValueError: amount = 1 Database.AddStar(self.fsid, self.filename, amount) return b"Success" if self.ext == "htm": return details_page(request, fn) request.setResponseCode(405); return b"" def render_POST(self, request): fn = Database.get_flipnote(self.fsid, self.filename) if not fn: 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": 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) if not tmb: return dialog(request, "That mini Flipnote is invalid.") if int(getattr(tmb, "FrameCount", 1)) != 1: return dialog(request, "Mini Flipnote comments must contain one frame.") try: Database.add_memo_comment( fn["id"], data, ip_address=client_ip(request), account_id=(account["id"] if account else None), ) except ValueError as exc: return dialog(request, str(exc)) forward(request, dsi_url(request, f"movie/{self.fsid}/{self.filename}.htm")) return b"OK" request.setResponseCode(405); return b"" def details_page(request, fn): try: tmb = TMB().Read(Database.GetFlipnoteTMB(fn["creator_fsid"], fn["filename"])) except Exception: tmb = None creator_name = fn.get("creator_name") or (getattr(tmb, "Username", "") if tmb else "Creator") creator_url = dsi_url(request, f"creator/{fn['creator_room_id']}/profile.htm") ppm_url = dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.ppm") star_url = dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.star") reply_url = dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.reply") text_url = dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}/keyboard.kbd") extra = ( '' '' '' '' '' ) % { "ppm": html.escape(ppm_url, quote=True), "star": html.escape(star_url, quote=True), "reply": html.escape(reply_url, quote=True), } account = current_account(request) owner = Database.owns_flipnote(account["id"] if account else None, client_ip(request), fn) editing = "" if owner: extra += '' % html.escape(dsi_url(request, f"movie/{fn['creator_fsid']}/{fn['filename']}.delete"), quote=True) editing = '

Edit title | Edit description

' % ( 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 = [] for c in comments: if c["type"] == "memo": npf = dsi_url(request, f"comment/{c['id']}.npf?w=128&h=64") ppm = dsi_url(request, f"comment/{c['id']}.ppm") content = '' % (html.escape(ppm, quote=True), html.escape(npf, quote=True)) else: content = dsi_text_html(c.get("text_content") or "", preserve_newlines=True) comment_html.append( '
%s
%s
' % ( dsi_text_html(c.get("creator_name") or "Guest"), content, ) ) body = ( '
%(title)s
' '' '' '' '' '
Creator%(creator)s
Stars%(stars)d
Views%(views)d
' '%(description)s%(editing)s

Play Flipnote

' '

Text Comment

' '
Comments (%(comment_count)d)
%(comments)s%(comment_pager)s' ) % { "title": dsi_text_html(fn.get("title") or "Untitled Flipnote"), "creator_url": html.escape(creator_url, quote=True), "creator": dsi_text_html(creator_name), "stars": int(fn.get("stars") or 0), "views": int(fn.get("views") or 0), "ppm": html.escape(ppm_url, quote=True), "text": html.escape(text_url, quote=True), "comments": "".join(comment_html) or '
No comments yet.
', "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": '

%s

' % 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)