diff --git a/bin/README.md b/bin/README.md index 171bb925..4ed2e2ca 100644 --- a/bin/README.md +++ b/bin/README.md @@ -5,6 +5,11 @@ * if something breaks just restart it +# [`partyjournal.py`](partyjournal.py) +produces a chronological list of all uploads by collecting info from up2k databases and the filesystem +* outputs a standalone html file +* optional mapping from IP-addresses to nicknames + # [`copyparty-fuse.py`](copyparty-fuse.py) * mount a copyparty server as a local filesystem (read-only) diff --git a/bin/mtag/very-bad-idea.py b/bin/mtag/very-bad-idea.py index 2240e91d..bd55e25d 100755 --- a/bin/mtag/very-bad-idea.py +++ b/bin/mtag/very-bad-idea.py @@ -1,10 +1,18 @@ #!/usr/bin/env python3 """ -use copyparty to xdg-open anything that is posted to it, - and also xdg-open file uploads +use copyparty as a chromecast replacement: + * post a URL and it will open in the default browser + * upload a file and it will open in the default application + * the `key` command simulates keyboard input + * the `x` command executes other xdotool commands + * the `c` command executes arbitrary unix commands -HELLA DANGEROUS, +the android app makes it a breeze to post pics and links: + https://github.com/9001/party-up/releases + (iOS devices have to rely on the web-UI) + +goes without saying, but this is HELLA DANGEROUS, GIVES RCE TO ANYONE WHO HAVE UPLOAD PERMISSIONS example copyparty config to use this: @@ -12,6 +20,7 @@ example copyparty config to use this: recommended deps: apt install xdotool libnotify-bin + https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js and you probably want `twitter-unmute.user.js` from the res folder @@ -62,6 +71,7 @@ cd ~/Downloads; python3 copyparty-sfx.py --urlform save,get -v.::rw:c,e2d,e2t,mt import os import sys +import time import subprocess as sp from urllib.parse import unquote_to_bytes as unquote @@ -79,16 +89,48 @@ def main(): def open_post(txt): txt = unquote(txt.replace(b"+", b" ")).decode("utf-8")[4:] - open_url(txt) + try: + k, v = txt.split(" ", 1) + except: + open_url(txt) + + if k == "key": + sp.call(["xdotool", "key"] + v.split(" ")) + elif k == "x": + sp.call(["xdotool"] + v.split(" ")) + elif k == "c": + env = os.environ.copy() + while " " in v: + v1, v2 = v.split(" ", 1) + if "=" not in v1: + break + + ek, ev = v1.split("=", 1) + env[ek] = ev + v = v2 + + sp.call(v.split(" "), env=env) + else: + open_url(txt) def open_url(txt): - sp.call(["notify-send", "", txt]) - # sp.call(["wmctrl", "-c", ":ACTIVE:"]) # closes the active window correctly - sp.call(["xdotool", "key", "ctrl+w"]) # closes the open tab correctly - sp.call(["xdotool", "getactivewindow", "windowminimize"]) + ext = txt.rsplit(".")[-1].lower() + sp.call(["notify-send", "--", txt]) + if ext not in ["jpg", "jpeg", "png", "gif", "webp"]: + # sp.call(["wmctrl", "-c", ":ACTIVE:"]) # closes the active window correctly + sp.call(["killall", "vlc"]) + sp.call(["killall", "mpv"]) + sp.call(["killall", "feh"]) + time.sleep(0.5) + for _ in range(20): + sp.call(["xdotool", "key", "ctrl+w"]) # closes the open tab correctly + # else: + # sp.call(["xdotool", "getactivewindow", "windowminimize"]) # minimizes the focused windo + + # close any error messages: + sp.call(["xdotool", "search", "--name", "Error", "windowclose"]) # sp.call(["xdotool", "key", "ctrl+alt+d"]) # doesnt work at all - sp.call(["killall", "vlc"]) # sp.call(["xdotool", "keydown", "--delay", "100", "ctrl+alt+d"]) # sp.call(["xdotool", "keyup", "ctrl+alt+d"]) sp.call(["xdg-open", txt]) diff --git a/bin/partyjournal.py b/bin/partyjournal.py new file mode 100755 index 00000000..65d0cc3b --- /dev/null +++ b/bin/partyjournal.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +""" +partyjournal.py: chronological history of uploads +2021-12-31, v0.1, ed , MIT-Licensed +https://github.com/9001/copyparty/blob/hovudstraum/bin/partyjournal.py + +produces a chronological list of all uploads, +by collecting info from up2k databases and the filesystem + +specify subnet `192.168.1.*` with argument `.=192.168.1.`, +affecting all successive mappings + +usage: + ./partyjournal.py > partyjournal.html .=192.168.1. cart=125 steen=114 steen=131 sleepy=121 fscarlet=144 ed=101 ed=123 + +""" + +import sys +import base64 +import sqlite3 +import argparse +from datetime import datetime +from urllib.parse import quote_from_bytes as quote +from urllib.parse import unquote_to_bytes as unquote + + +FS_ENCODING = sys.getfilesystemencoding() + + +class APF(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): + pass + + +## +## snibbed from copyparty + + +def s3dec(v): + if not v.startswith("//"): + return v + + v = base64.urlsafe_b64decode(v.encode("ascii")[2:]) + return v.decode(FS_ENCODING, "replace") + + +def quotep(txt): + btxt = txt.encode("utf-8", "replace") + quot1 = quote(btxt, safe=b"/") + quot1 = quot1.encode("ascii") + quot2 = quot1.replace(b" ", b"+") + return quot2.decode("utf-8", "replace") + + +def html_escape(s, quote=False, crlf=False): + """html.escape but also newlines""" + s = s.replace("&", "&").replace("<", "<").replace(">", ">") + if quote: + s = s.replace('"', """).replace("'", "'") + if crlf: + s = s.replace("\r", " ").replace("\n", " ") + + return s + + +## end snibs +## + + +def main(): + ap = argparse.ArgumentParser(formatter_class=APF) + ap.add_argument("who", nargs="*") + ar = ap.parse_args() + + imap = {} + subnet = "" + for v in ar.who: + if "=" not in v: + raise Exception("bad who: " + v) + + k, v = v.split("=") + if k == ".": + subnet = v + continue + + imap["{}{}".format(subnet, v)] = k + + print(repr(imap), file=sys.stderr) + + print( + """\ + + + + + + + + +""" + ) + + db_path = ".hist/up2k.db" + conn = sqlite3.connect(db_path) + q = r"pragma table_info(up)" + inf = conn.execute(q).fetchall() + cols = [x[1] for x in inf] + print("") + # ['w', 'mt', 'sz', 'rd', 'fn', 'ip', 'at'] + + q = r"select * from up order by case when at > 0 then at else mt end" + for w, mt, sz, rd, fn, ip, at in conn.execute(q): + link = "/".join([s3dec(x) for x in [rd, fn] if x]) + if fn.startswith("put-") and sz < 4096: + try: + with open(link, "rb") as f: + txt = f.read().decode("utf-8", "replace") + except: + continue + + if txt.startswith("msg="): + txt = txt.encode("utf-8", "replace") + txt = unquote(txt.replace(b"+", b" ")) + link = txt.decode("utf-8")[4:] + + sz = "{:,}".format(sz) + v = [ + w[:16], + datetime.utcfromtimestamp(at if at > 0 else mt).strftime( + "%Y-%m-%d %H:%M:%S" + ), + sz, + imap.get(ip, ip), + ] + + row = "\n " + row += "\n ".join(["'.format(link, html_escape(link)) + row += "\n" + print(row) + + print("
warktimesizewholink
{}".format(x) for x in v]) + row += '\n {}
") + + +if __name__ == "__main__": + main() diff --git a/contrib/README.md b/contrib/README.md index 25bb36f4..c72afc81 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,3 +1,6 @@ +### [`plugins/`](plugins/) +* example extensions + ### [`copyparty.bat`](copyparty.bat) * launches copyparty with no arguments (anon read+write within same folder) * intended for windows machines with no python.exe in PATH diff --git a/contrib/plugins/README.md b/contrib/plugins/README.md new file mode 100644 index 00000000..5a077da0 --- /dev/null +++ b/contrib/plugins/README.md @@ -0,0 +1,25 @@ +# example resource files + +can be provided to copyparty to tweak things + + + +## example `.epilogue.html` +save one of these as `.epilogue.html` inside a folder to customize it: + +* [`minimal-up2k.html`](minimal-up2k.html) will [simplify the upload ui](https://user-images.githubusercontent.com/241032/118311195-dd6ca380-b4ef-11eb-86f3-75a3ff2e1332.png) + + + +## example browser-css +point `--css-browser` to one of these by URL: + +* [`browser.css`](browser.css) changes the background +* [`browser-icons.css`](browser-icons.css) adds filetype icons + + + +## meadup.js + +* turns copyparty into chromecast just more flexible (and probably way more buggy) +* usage: put the js somewhere in the webroot and `--js-browser /memes/meadup.js` diff --git a/docs/browser-icons.css b/contrib/plugins/browser-icons.css similarity index 100% rename from docs/browser-icons.css rename to contrib/plugins/browser-icons.css diff --git a/docs/browser.css b/contrib/plugins/browser.css similarity index 100% rename from docs/browser.css rename to contrib/plugins/browser.css diff --git a/contrib/plugins/meadup.js b/contrib/plugins/meadup.js new file mode 100644 index 00000000..e6d6d2f3 --- /dev/null +++ b/contrib/plugins/meadup.js @@ -0,0 +1,506 @@ +// USAGE: +// place this file somewhere in the webroot and then +// python3 -m copyparty --js-browser /memes/meadup.js +// +// FEATURES: +// * adds an onscreen keyboard for operating a media center remotely, +// relies on https://github.com/9001/copyparty/blob/hovudstraum/contrib/plugins/meadup.js +// * adds an interactive anime girl (if you can find the dependencies) + +var hambagas = [ + "https://www.youtube.com/watch?v=pFA3KGp4GuU" +]; + +// keybaord, +// onscreen keyboard by @steinuil +function initKeybaord(BASE_URL, HAMBAGA, consoleLog, consoleError) { + document.querySelector('.keybaord-container').innerHTML = ` +
+
+
+ esc +
+
+ F1 +
+
+ F2 +
+
+ F3 +
+
+ F4 +
+
+ F5 +
+
+ F6 +
+
+ F7 +
+
+ F8 +
+
+ F9 +
+
+ F10 +
+
+ F11 +
+
+ F12 +
+
+ ins +
+
+ del +
+
+
+
+ \` +
+
+ 1 +
+
+ 2 +
+
+ 3 +
+
+ 4 +
+
+ 5 +
+
+ 6 +
+
+ 7 +
+
+ 8 +
+
+ 9 +
+
+ 0 +
+
+ - +
+
+ = +
+
+ backspace +
+
+
+
+ tab +
+
+ q +
+
+ w +
+
+ e +
+
+ r +
+
+ t +
+
+ y +
+
+ u +
+
+ i +
+
+ o +
+
+ p +
+
+ [ +
+
+ ] +
+
+ enter +
+
+
+
+ 🍔 +
+
+ a +
+
+ s +
+
+ d +
+
+ f +
+
+ g +
+
+ h +
+
+ j +
+
+ k +
+
+ l +
+
+ ; +
+
+ ' +
+
+ \\ +
+
+
+
+ shift +
+
+ \\ +
+
+ z +
+
+ x +
+
+ c +
+
+ v +
+
+ b +
+
+ n +
+
+ m +
+
+ , +
+
+ . +
+
+ / +
+
+ shift +
+
+
+
+ ctrl +
+
+ win +
+
+ alt +
+
+ space +
+
+ altgr +
+
+ menu +
+
+ ctrl +
+
+
+
+ 🔉 +
+
+ 🔊 +
+
+ ⬅️ +
+
+ ⬇️ +
+
+ ⬆️ +
+
+ ➡️ +
+
+ PgUp +
+
+ PgDn +
+
+ 🏠 +
+
+ End +
+
+
+ `; + + function arraySample(array) { + return array[Math.floor(Math.random() * array.length)]; + } + + function sendMessage(msg) { + return fetch(BASE_URL, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + }, + body: "msg=" + encodeURIComponent(msg), + }).then( + (r) => r.text(), // so the response body shows up in network tab + (err) => consoleError(err) + ); + } + const MODIFIER_ON_CLASS = "keybaord-modifier-on"; + const KEY_DATASET = "data-keybaord-key"; + const KEY_CLASS = "keybaord-key"; + + const modifiers = new Set() + + function toggleModifier(button, key) { + button.classList.toggle(MODIFIER_ON_CLASS); + if (modifiers.has(key)) { + modifiers.delete(key); + } else { + modifiers.add(key); + } + } + + function popModifiers() { + let modifierString = ""; + + modifiers.forEach((mod) => { + document.querySelector("[" + KEY_DATASET + "='" + mod + "']") + .classList.remove(MODIFIER_ON_CLASS); + + modifierString += mod + "+"; + }); + + modifiers.clear(); + + return modifierString; + } + + Array.from(document.querySelectorAll("." + KEY_CLASS)).forEach((button) => { + const key = button.dataset.keybaordKey; + + button.addEventListener("click", (ev) => { + switch (key) { + case "HAMBAGA": + sendMessage(arraySample(HAMBAGA)); + break; + + case "Shift_L": + case "Shift_R": + + case "Control_L": + case "Control_R": + + case "Meta_L": + + case "Alt_L": + case "Alt_R": + toggleModifier(button, key); + break; + + default: { + const keyWithModifiers = popModifiers() + key; + + consoleLog(keyWithModifiers); + + sendMessage("key " + keyWithModifiers) + .then(() => consoleLog(keyWithModifiers + " OK")); + } + } + }); + }); +} + + +// keybaord integration +(function () { + var o = mknod('div'); + clmod(o, 'keybaord-container', 1); + ebi('op_msg').appendChild(o); + + o = mknod('style'); + o.innerHTML = ` +.keybaord-body { + display: flex; + flex-flow: column nowrap; + margin: .6em 0; +} + +.keybaord-row { + display: flex; +} + +.keybaord-key { + border: 1px solid rgba(128,128,128,0.2); + width: 41px; + height: 40px; + + display: flex; + justify-content: center; + align-items: center; +} + +.keybaord-key:active { + background-color: lightgrey; +} + +.keybaord-key.keybaord-modifier-on { + background-color: lightblue; +} + +.keybaord-key.keybaord-backspace { + width: 82px; +} + +.keybaord-key.keybaord-tab { + width: 55px; +} + +.keybaord-key.keybaord-enter { + width: 69px; +} + +.keybaord-key.keybaord-capslock { + width: 80px; +} + +.keybaord-key.keybaord-backslash { + width: 88px; +} + +.keybaord-key.keybaord-lshift { + width: 65px; +} + +.keybaord-key.keybaord-rshift { + width: 103px; +} + +.keybaord-key.keybaord-lctrl { + width: 55px; +} + +.keybaord-key.keybaord-super { + width: 55px; +} + +.keybaord-key.keybaord-alt { + width: 55px; +} + +.keybaord-key.keybaord-altgr { + width: 55px; +} + +.keybaord-key.keybaord-what { + width: 55px; +} + +.keybaord-key.keybaord-rctrl { + width: 55px; +} + +.keybaord-key.keybaord-spacebar { + width: 302px; +} +`; + document.head.appendChild(o); + + initKeybaord('/', hambagas, + (msg) => { toast.inf(2, msg.toString()) }, + (msg) => { toast.err(30, msg.toString()) }); +})(); + + +// live2d (dumb pointless meme) +// dependencies for this part are not tracked in git +// so delete this section if you wanna use this file +// (or supply your own l2d model and js) +(function () { + var o = mknod('link'); + o.setAttribute('rel', 'stylesheet'); + o.setAttribute('href', "/bad-memes/pio.css"); + document.head.appendChild(o); + + o = mknod('style'); + o.innerHTML = '.pio-container{text-shadow:none;z-index:1}'; + document.head.appendChild(o); + + o = mknod('div'); + clmod(o, 'pio-container', 1); + o.innerHTML = '
'; + document.body.appendChild(o); + + var remaining = 3; + for (var a of ['pio', 'l2d', 'fireworks']) { + import_js(`/bad-memes/${a}.js`, function () { + if (remaining-- > 1) + return; + + o = mknod('script'); + o.innerHTML = 'var pio = new Paul_Pio({"selector":[],"mode":"fixed","hidden":false,"content":{"close":"ok bye"},"model":["/bad-memes/sagiri/model.json"]});'; + document.body.appendChild(o); + }); + } +})(); diff --git a/docs/minimal-up2k.html b/contrib/plugins/minimal-up2k.html similarity index 100% rename from docs/minimal-up2k.html rename to contrib/plugins/minimal-up2k.html diff --git a/copyparty/util.py b/copyparty/util.py index 475301ed..69bf31a0 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -1097,7 +1097,8 @@ def read_socket(sr, total_size): buf = sr.recv(bufsz) if not buf: - raise Pebkac(400, "client d/c during binary post") + m = "client d/c during binary post after {} bytes, {} bytes remaining" + raise Pebkac(400, m.format(total_size - remains, remains)) remains -= len(buf) yield buf diff --git a/copyparty/web/util.js b/copyparty/web/util.js index 6bb20b45..37168a5e 100644 --- a/copyparty/web/util.js +++ b/copyparty/web/util.js @@ -86,6 +86,9 @@ function vis_exh(msg, url, lineNo, columnNo, error) { if ((msg + '').indexOf('ResizeObserver') !== -1) return; // chrome issue 809574 (benign, from