Skip to content

Commit

Permalink
add hook side-effects; closes #86
Browse files Browse the repository at this point in the history
hooks can now interrupt or redirect actions, and initiate
related actions, by printing json on stdout with commands

mainly to mitigate limitations such as ShareX/ShareX#3992

xbr/xau can redirect uploads to other destinations with `reloc`
and most hooks can initiate indexing or deletion of additional
files by giving a list of vpaths in json-keys `idx` or `del`

there are limitations;
* xbu/xau effects don't apply to ftp, tftp, smb
* xau will intentionally fail if a reloc destination exists
* xau effects do not apply to up2k

also provides more details for hooks:
* xbu/xau: basic-uploader vpath with filename
* xbr/xar: add client ip
  • Loading branch information
9001 committed Aug 11, 2024
1 parent 20669c7 commit 6c94a63
Show file tree
Hide file tree
Showing 15 changed files with 793 additions and 175 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,8 @@ you can set hooks before and/or after an event happens, and currently you can ho
there's a bunch of flags and stuff, see `--help-hooks`
if you want to write your own hooks, see [devnotes](./docs/devnotes.md#event-hooks)
### upload events
Expand Down
4 changes: 2 additions & 2 deletions bin/hooks/into-the-cache-it-goes.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
t10 = abort download and continue if it takes longer than 10sec
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:xau=j,t10,bin/hooks/into-the-cache-it-goes.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-v srv/inc:inc:r:rw,ed:c,xau=j,t10,bin/hooks/into-the-cache-it-goes.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
Expand Down
94 changes: 94 additions & 0 deletions bin/hooks/reloc-by-ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3

import json
import os
import sys


_ = r"""
relocate/redirect incoming uploads according to file extension
example usage as global config:
--xbu j,c1,bin/hooks/reloc-by-ext.py
parameters explained,
xbu = execute before upload
j = this hook needs upload information as json (not just the filename)
c1 = this hook returns json on stdout, so tell copyparty to read that
example usage as a volflag (per-volume config):
-v srv/inc:inc:r:rw,ed:c,xbu=j,c1,bin/hooks/reloc-by-ext.py
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(share filesystem-path srv/inc as volume /inc,
readable by everyone, read-write for user 'ed',
running this plugin on all uploads with the params explained above)
example usage as a volflag in a copyparty config file:
[/inc]
srv/inc
accs:
r: *
rw: ed
flags:
xbu: j,c1,bin/hooks/reloc-by-ext.py
note: this only works with the basic uploader (sharex and such),
does not work with up2k / dragdrop into browser
note: this could also work as an xau hook (after-upload), but
because it doesn't need to read the file contents its better
as xbu (before-upload) since that's safer / less buggy
"""


PICS = "avif bmp gif heic heif jpeg jpg jxl png psd qoi tga tif tiff webp"
VIDS = "3gp asf avi flv mkv mov mp4 mpeg mpeg2 mpegts mpg mpg2 nut ogm ogv rm ts vob webm wmv"
MUSIC = "aac aif aiff alac amr ape dfpwm flac m4a mp3 ogg opus ra tak tta wav wma wv"


def main():
inf = json.loads(sys.argv[1])
vdir, fn = os.path.split(inf["vp"])

try:
fn, ext = fn.rsplit(".", 1)
except:
# no file extension; abort
return

ext = ext.lower()

##
## some example actions to take; pick one by
## selecting it inside the print at the end:
##

# create a subfolder named after the filetype and move it into there
into_subfolder = {"vp": ext}

# move it into a toplevel folder named after the filetype
into_toplevel = {"vp": "/" + ext}

# move it into a filetype-named folder next to the target folder
into_sibling = {"vp": "../" + ext}

# move images into "/just/pics", vids into "/just/vids",
# music into "/just/tunes", and anything else as-is
if ext in PICS.split():
by_category = {"vp": "/just/pics"}
elif ext in VIDS.split():
by_category = {"vp": "/just/vids"}
elif ext in MUSIC.split():
by_category = {"vp": "/just/tunes"}
else:
by_category = {}

# now choose the effect to apply; can be any of these:
# into_subfolder into_toplevel into_sibling by_category
effect = into_subfolder
print(json.dumps({"reloc": effect}))


if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions copyparty/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,11 @@ def get_sects():
\033[36mxban\033[0m can be used to overrule / cancel a user ban event;
if the program returns 0 (true/OK) then the ban will NOT happen
effects can be used to redirect uploads into other
locations, and to delete or index other files based
on new uploads, but with certain limitations. See
bin/hooks/reloc* and docs/devnotes.md#hook-effects
except for \033[36mxm\033[0m, only one hook / one action can run at a time,
so it's recommended to use the \033[36mf\033[0m flag unless you really need
to wait for the hook to finish before continuing (without \033[36mf\033[0m
Expand Down Expand Up @@ -1132,6 +1137,7 @@ def add_hooks(ap):
ap2.add_argument("--xad", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m after a file delete")
ap2.add_argument("--xm", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m on message")
ap2.add_argument("--xban", metavar="CMD", type=u, action="append", help="execute \033[33mCMD\033[0m if someone gets banned (pw/404/403/url)")
ap2.add_argument("--hook-v", action="store_true", help="verbose hooks")


def add_stats(ap):
Expand Down
6 changes: 3 additions & 3 deletions copyparty/authsrv.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,8 @@ def get(
t = "{} has no {} in [{}] => [{}] => [{}]"
self.log("vfs", t.format(uname, msg, vpath, cvpath, ap), 6)

t = 'you don\'t have %s-access in "/%s"'
raise Pebkac(err, t % (msg, cvpath))
t = 'you don\'t have %s-access in "/%s" or below "/%s"'
raise Pebkac(err, t % (msg, cvpath, vn.vpath))

return vn, rem

Expand Down Expand Up @@ -1898,7 +1898,7 @@ def _reload(self) -> None:
self.log(t.format(vol.vpath), 1)
del vol.flags["lifetime"]

needs_e2d = [x for x in hooks if x != "xm"]
needs_e2d = [x for x in hooks if x in ("xau", "xiu")]
drop = [x for x in needs_e2d if vol.flags.get(x)]
if drop:
t = 'removing [{}] from volume "/{}" because e2d is disabled'
Expand Down
7 changes: 5 additions & 2 deletions copyparty/ftpd.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def rename(self, src: str, dst: str) -> None:
svp = join(self.cwd, src).lstrip("/")
dvp = join(self.cwd, dst).lstrip("/")
try:
self.hub.up2k.handle_mv(self.uname, svp, dvp)
self.hub.up2k.handle_mv(self.uname, self.h.cli_ip, svp, dvp)
except Exception as ex:
raise FSE(str(ex))

Expand Down Expand Up @@ -471,6 +471,9 @@ def ftp_STOR(self, file: str, mode: str = "w") -> Any:
xbu = vfs.flags.get("xbu")
if xbu and not runhook(
None,
None,
self.hub.up2k,
"xbu.ftpd",
xbu,
ap,
vp,
Expand All @@ -480,7 +483,7 @@ def ftp_STOR(self, file: str, mode: str = "w") -> Any:
0,
0,
self.cli_ip,
0,
time.time(),
"",
):
raise FSE("Upload blocked by xbu server config")
Expand Down
Loading

0 comments on commit 6c94a63

Please sign in to comment.