copyparty 插件机制详解:用 .epilogue.html 与 --js-browser 定制浏览器界面与上传行为
【免费下载链接】copypartyPortable file server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file项目地址: https://gitcode.com/GitHub_Trending/co/copyparty
copyparty 除了作为单文件文件服务器提供 WebDAV、SFTP、FTP、TFTP 等协议外,还内置了一套轻量级的"前端插件"体系:通过向指定目录放置.epilogue.html文件,或让服务器在页面中追加加载外部 JS/CSS 文件,即可在不改动服务端代码的前提下定制文件浏览器界面、简化上传 UI、给文件列表加图标,甚至为 up2k 上传器注册"文件名过滤器"钩子。本文基于 contrib/plugins/README.md 逐条讲解官方示例插件的用法,并结合 copyparty/httpcli.py、copyparty/main.py 与 copyparty/web/browser.html 的源码,说明这些插件是如何被服务端注入并执行的,以及沙箱、缓存指令等配套参数的细节。
读完本文你可以掌握:
- 四类定制手段(
.epilogue.html目录级 HTML、--js-browser、--js-other/--css-browser、--html-head)各自的作用范围与适用场景; - 每个官方示例插件(minimal-up2k、quickmove、up2k-hooks、banner、browser-icons、meadup 等)的安装位置、启用命令与行为原理;
- 服务端注入
<script nonce="...">的完整链路,以及 up2k 钩子链(up2k_hooks)的注册与调用机制。
一、插件体系的四种注入点
contrib/plugins/README.md 把示例资源按注入方式分成几组:目录内的.epilogue.html、全局--js-browser、--js-browser+--js-other双开、以及--css-browser。对应的 CLI 参数定义在 copyparty/main.py:
ap2.add_argument("--js-browser", metavar="L", type=u, default="", help="URL to additional JS to include in the filebrowser html") ap2.add_argument("--css-browser", metavar="L", type=u, default="", help="URL to additional CSS to include in the filebrowser html") ap2.add_argument("--js-other", metavar="L", type=u, default="", help="URL to additional JS to include in all other pages") ap2.add_argument("--html-head", metavar="TXT", type=u, default="", help="text to append to the <head> of all HTML pages (except for basic-browser); " "can be @PATH to send the contents of a file at PATH, and/or begin with %% " "to render as jinja2 template; <script> will not work, use " "<script nonce=\"{{ js_nonce }}\"> (volflag=html_head)")| 注入点 | 作用范围 | 典型用途 |
|---|---|---|
.epilogue.html(目录文件) | 仅所在目录的目录列表页(嵌入在列表下方) | 针对某个"只写"上传目录做 UI 减法 |
--js-browser | 文件浏览器(目录列表)HTML 页 | 全局 JS 插件、热键、up2k 钩子 |
--js-other | 除浏览器外的所有其他页面(登录、控制面板、splash、shares 等) | 法律横幅等需要出现在所有页面的元素 |
--css-browser | 文件浏览器 HTML 页 | 文件类型图标等纯样式增强 |
从源码结构看,--js-browser与--css-browser并不是在 Python 侧拼接 HTML,而是作为 Jinja2 模板变量传入:copyparty/httpcli.py 在渲染浏览器页面前执行:
if self.args.js_browser: zs = self.args.js_browser zs += "&" if "?" in zs else "?" j2a["js"] = zs if self.args.css_browser: zs = self.args.css_browser zs += "&" if "?" in zs else "?" j2a["css"] = zs这里有个容易忽略的细节:如果 URL 已带?cache=...之类的查询参数,代码会自动把分隔符改为&,追加_={{ ts }}时间戳用于缓存控制(zs += "&" if "?" in zs else "?")。
这些变量最终落入 copyparty/web/browser.html 的<head>:
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}"> ... <script nonce="{{ js_nonce }}" src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script> <script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>注意两点:
- 注入的
<script>带有nonce="{{ js_nonce }}"。由于 copyparty 对页面启用了 CSP(Content-Security-Policy),自定义脚本必须携带服务端生成的 nonce 才能执行——这也是--html-head帮助文本特别强调"<script>will not work, use<script nonce=\"{{ js_nonce }}\">"的原因。通过--js-browser走的插件不存在这个问题,因为 nonce 由模板自动补上。 - 插件脚本加载在
browser.js、up2k.js等核心脚本之后(见 copyparty/web/browser.html 的脚本顺序),因此插件代码可以直接使用up2k、msel、thegrid、perms等全局对象。
--js-other的注入点在 copyparty/httpcli.py(其他 HTML 页面的公共头部处理)等位置,覆盖splash.html、shares.html、idp.html、svcs.html、mde.html等所有页面模板(它们同样都含<script nonce="{{ js_nonce }}" src="{{ js }}_={{ ts }}"></script>占位符)。
二、目录级定制:.epilogue.html
这是最"无侵入"的方式:把 HTML 片段保存为某个目录下的.epilogue.html,服务端渲染该目录的列表页时会自动读取并嵌入到文件列表之后(--epilogues参数,默认为.epilogue.html,见 copyparty/main.py)。
读取逻辑在 copyparty/httpcli.py 的_add_logues中:
def _add_logues(self, vn: VFS, abspath: str, lnames): logues = ["", ""] for n, fns1, fns2 in [] if self.args.no_logues else vn.flags["emb_lgs"]: for fn in fns1 if lnames is None else fns2: ... fn = "%s/%s" % (abspath, fn) if not bos.path.isfile(fn): continue logues[n] = read_utf8(self.log, fsenc(fn), False) if "exp" in vn.flags: logues[n] = self._expand(logues[n], vn.flags.get("exp_lg") or []) if "plainlogues" in vn.flags: logues[n] = html_escape(logues[n]).replace("\n", "<br />") break即:prologue(列表前)与 epilogue(列表后)各有一份,文件名列表来自卷配置emb_lgs(对应 volflagepilogues/prologues),可用--epilogues改成扫描其他文件名;--no-logues可整体禁用。此外若卷开启了占位符扩展(--exp-lg,默认DEF_EXP),文件内容还会经过_expand做{{placeholder}}替换,支持hdr.*(请求头)、cfg.*(CLI 参数)、vf.*(卷标志)、srv.itime/srv.htime(时间戳)等占位符,见 _expand。
安全模型上,.epilogue.html是在浏览器中执行的 HTML/JS,而非纯文本。默认情况下它被放进带sandbox属性的 iframe(--lg-sbf控制允许的能力白名单,默认downloads forms popups scripts top-navigation-by-user-activation;--lg-sba控制allow属性),--no-sb-lg则完全取消沙箱。相关参数定义:
ap2.add_argument("--lg-sbf", ..., default="downloads forms popups scripts top-navigation-by-user-activation", help="list of capabilities to allow in the iframe 'sandbox' attribute ...") ap2.add_argument("--lg-sba", metavar="TXT", ..., help="the value of the iframe 'allow' attribute ...") ap2.add_argument("--no-sb-lg", action="store_true", help="don't sandbox prologue/epilogue docs (volflags: no_sb_lg | sb_lg); enables non-js support")官方示例:minimal-up2k.html(已弃用,仅作历史参考)
contrib/plugins/minimal-up2k.html 是最经典的"上传目录极简 UI"方案:把一个只能写入、不能读取的目录(write-only folder)变成只露出一个巨大上传按钮的页面。其文件头注释写得很清楚:
<!-- NOTE: DEPRECATED; please use the javascript version instead: .../minimal-up2k.js save this as .epilogue.html inside a write-only folder to declutter the UI only works if you disable the prologue/epilogue sandbox with --no-sb-lg which should probably be combined with --no-dot-ren to prevent damage (`no_sb_lg` can also be set per-volume with volflags) -->原理是一段内嵌<style>:隐藏主标签页、目录树/面包屑(#ops, #tree, #path, #wfp)、大部分 up2k 配置项、搜索拖放区(#srch_dz, #srch_zd)和进度标签(#u2cards, #u2etaw),再加大上传按钮内边距,最后提供一个show advanced options链接恢复原貌:
<style> #ops, #tree, #path, #wfp, #u2conf tr:first-child>td[rowspan]:not(#u2btn_cw), #srch_dz, #srch_zd, #u2cards, #u2etaw {display: none !important} ... </style> <a href="#" onclick="this.parentNode.innerHTML='';">show advanced options</a>关键限制:因为它要靠 DOM 操作隐藏元素,所以必须搭配--no-sb-lg(取消沙箱)才能工作,且作者建议同时加--no-dot-ren防止误删点号目录。README 中对该示例的说明是"save one of these as.epilogue.htmlinside a folder to customize it",并配了一张简化后的上传界面截图说明。
三、全局 JS 插件:--js-browser
README 的第二类示例通过--js-browser指向某个可由 URL 访问的 JS 文件来启用。标准部署三步(minimal-up2k.js 头部注释给出的官方步骤):
- 建一个任何人可读的卷(比如把仓库里
.res之类的目录暴露出去); - 把 JS 文件拷进该卷,让所有人都能下载它;
- 在配置文件中启用(
[global]段):
[global] js-browser: /res/minimal-up2k.js或不使用配置文件,改用命令行参数:
python3 -m copyparty --js-browser=/res/minimal-up2k.js配置文件示例可参考 docs/example.conf、docs/example2.conf。
下面逐个过一遍官方提供的示例插件。
1. minimal-up2k.js:所有"只写"目录的极简上传 UI
minimal-up2k.js 与上一节的 HTML 版效果接近,但差异点(其头部注释原话)是:
- 配合
--js-browser使用时,对每一个只写目录都生效(HTML 版只对放了文件的那个目录生效); - 仅在启用 JavaScript 时生效;
- 不隐藏总上传 ETA 显示;
- 视觉效果略好。
实现上它把一段<style>与一个"恢复"链接存进字符串u2min,然后在运行时判断当前视图是否无读权限:
if (!has(perms, 'read')) { var e2 = mknod('div'); e2.innerHTML = u2min; ebi('wrap').insertBefore(e2, QS('#wfp')); ebi('u2min_off').onclick = function () { this.parentNode.innerHTML=''; }; }perms是浏览器页内表示当前用户权限的全局数组(服务端在 copyparty/httpcli.py 中按can_delete/can_dot/can_read/can_write/can_admin等组装perms列表传入模板),mknod/ebi是 copyparty 前端工具函数里的 DOM 构造与元素查找助手。也就是说,该插件不需要取消 iframe 沙箱(它不在 epilogue 里运行),只需服务器端一条--js-browser配置即可全局生效——这也是该文件注释中推荐 JS 版替代 HTML 版的原因。
2. quickmove.js:热键把选中文件移入子目录
quickmove.js 演示了如何"钩住" copyparty 前端的热键系统。用法(文件头部注释):
# 把文件放到 webroot 下(例如 .res 目录以便隐藏),然后: python3 copyparty-sfx.py -v .::A --js-browser /.res/quickmove.js其中.::A表示"当前目录作为 webroot、所有人拥有 Admin 权限"的单一卷。
插件本体开头是显式的配置区:
var action_to_perform = ask_for_confirmation_and_then_move; // ask_for_confirmation_and_then_move = 弹 yes/no 确认框 // move_selected_files = 直接移动 var move_destination = "foobar"; // 目标文件夹:默认是相对当前目录的子目录, // 也可以是绝对路径,如 "/foo/bar"行为上它注册了热键W:按下后取msel.getsel()(当前选中集),经modal.confirm确认后逐个发起移动请求,目标为当前目录下名为foobar的子文件夹;若没有选中文件则用toast.warn提示。它还顺带关闭了可能打开的图片/视频查看器(thegrid.bbox/baguetteBox.destroy())。这个插件是学习"如何调用浏览器页内全局 API(msel、modal、toast、baguetteBox)"的最佳范本。
3. up2k-hooks.js:up2k 上传器的文件名过滤钩子
up2k-hooks.js 演示了 up2k(copyparty 内置的加速断点续传上传器)对插件开放的钩子机制。其完整源码仅 40 余行,核心是一个up2k_namefilter函数加一次注册:
// hooks into up2k function up2k_namefilter(good_files, nil_files, bad_files, hooks) { // 在文件被拖入浏览器、遍历目录树发现所有文件之后、 // 上传确认对话框显示之前被调用。 // good_files 将正常上传;nil_files 是空文件(最后会告警); // bad_files 不可读、无法上传。 ... // 示例:只保留 webm 文件 for (var ent of lst) if (/\.webm$/.test(ent[1])) keep.push(ent); // 调用链中下一个钩子 hooks0); } // register up2k_hooks.push(function () { up2k.gotallfiles.unshift(up2k_namefilter); });服务端与源码中的对应关系:
- copyparty/web/up2k.js 定义了全局
up2k_hooks = [];copyparty/web/up2k.js 在初始化时遍历执行所有已注册的钩子(up2k_hooks[a]()),这就是插件"注册"的时机; - 真正的文件收集回调是
up2k.gotallfiles数组(copyparty/web/up2k.js 中以"gotallfiles": [gotallfiles] // hooks暴露),copyparty/web/up2k.js 在确认对话框前以r.gotallfiles0)的方式链式调用——每个钩子拿到三份文件列表,处理后必须再调用hooks0)交给下一环; - 因此插件用
up2k.gotallfiles.unshift(...)把自己的过滤器插到链头,即可在用户点击上传前按文件名/内容规则剔除或改写待上传集合(good_files中每个条目是[blob, filename, ...]形式的元组,ent[1]即文件名)。
up2k-hook-ytid.js 是该机制的一个"更具体"的生产级示例:它假设拖入的文件名中含 YouTube ID,先从文件名(必要时再从文件内嵌元数据中,借助手写的bstrpos字节查找函数)提取 ID 列表,POST 给一个部署在反代后面的独立 API(/ytq),由服务端返回"允许上传的 ID 白名单",再把不在名单里的文件从good_files中滤掉。它同时展示了钩子与用户交互的配合:若用户在 up2k 里已启用文件搜索(up2k.uc.fsearch),则直接透传给下一个钩子,避免两套过滤逻辑打架。
4. banner.js:跨所有页面的法律横幅
banner.js 是唯一一个 README 标注为"--js-browserand/or--js-other"的示例,因为它要覆盖的不止文件浏览器:
# 把文件拷到 webroot 下的 '.banner.js',然后: --js-browser /.banner.js --js-other /.banner.js--js-browser负责目录列表页,--js-other负责其余页面(登录、控制面板、splash 等)。插件内部先检测当前页面类型再决定插入位置:
if (QS("h1#cc") && QS("a#k")) { // this is the controlpanel show_msgbox = true; login_top = true; bottom = true; } else if (ebi("swin") && ebi("smac")) { // this is the connect-page, same deal here ... }即:通过 DOM 特征(h1#cc+a#k表示控制面板,#swin/#smac表示连接页)判断页面身份,然后在顶部/底部或消息框中插入横幅 div(bannerdiv()生成带上下边线的<div>)。横幅文本(默认是美国政府系统免责声明)直接改bannertext变量即可。这一模式说明:--js-other是"全站级"插件的通道,插件需要自己探测页面上下文。
5. browser-icons.css:--css-browser的文件类型图标
browser-icons.css 是最轻量的示例——纯 CSS,指向--css-browser即可(同样需要一个可下载的 URL)。它利用 CSS 的:is(...)与[href$=".mp4"i]这类属性后缀选择器,在网格视图(#ggrid)的缩略图上叠加文件类型标识,文件内给了两种视频图标的备选方案(左上角 emoji 或缩略图中央播放图标),其余格式同理。适合不想写 JS、只想增强视觉辨识度的场景。
6. meadup.js:把 copyparty 变成媒体中心遥控器
meadup.js 在 README 中被单列一节("turns copyparty into chromecast just more flexible (and probably way more buggy)"),用法是把 js 放进 webroot 后--js-browser /memes/meadup.js。从源码头注释看它提供:
- 一个屏幕虚拟键盘(keybaord,用于远程操作媒体中心,依赖
bin/mtag/very-bad-idea.py一类的 mtag 工具链); - 一个"互动 anime girl"彩蛋(需要能找到依赖资源才显示)。
它更像功能演示:展示如何向页面注入大型自定义 UI 并对接 copyparty 的mtag媒体标签体系。
7. graft-thumbs.js(README 未提及的补充示例)
contrib/plugins/下还有一个 README 没有列出的 graft-thumbs.js,值得一并了解:作为网格视图插件,它为文件夹中每个文件寻找同基名、不同扩展名的兄弟文件;若其中一个是图片(jpeg/png/gif/webp/jxl)而另一个不是(如 mp3),则把图片"嫁接"为后者在网格中的缩略图,同时默认隐藏那张图片文件、并让点击音频时一并打开图片。启用方式同样是python3 -m copyparty --js-browser /.res/graft-thumbs.js。这展示了插件可以改写网格渲染结果而无需改服务端的又一方向。
四、相关 CLI 参数速查
把本文涉及的参数汇总(均定义于 copyparty/main.py,支持配置文件等价写法,如docs/chungus.conf、docs/example.conf):
| 参数 | 配置文件键 | 默认值 | 说明 |
|---|---|---|---|
--js-browser | js-browser | 空 | 文件浏览器页追加的 JS URL |
--css-browser | css-browser | 空 | 文件浏览器页追加的 CSS URL |
--js-other | js-other | 空 | 其他所有 HTML 页面追加的 JS URL |
--html-head | html_head(volflag) | 空 | 向所有 HTML 页<head>追加文本;支持@PATH读文件、%%开头按 Jinja2 渲染;内联<script>必须写成<script nonce="{{ js_nonce }}"> |
--epilogues | epilogues(volflag) | .epilogue.html | 要扫描并嵌入到列表之后的文件名列表 |
--prologues(同族参数,帮助文本见main.py) | prologues | — | 嵌入到列表之前的文件名列表 |
--no-logues | no_logues(volflag) | 关 | 完全禁用 prologue/epilogue 渲染 |
--lg-sbf | lg_sbf(volflag) | downloads forms popups scripts top-navigation-by-user-activation | prologue/epilogue iframesandbox允许的能力 |
--lg-sba | lg_sba(volflag) | 空 | prologue/epilogue iframeallow属性值 |
--no-sb-lg | no_sb_lg(volflag) | 关 | 取消 prologue/epilogue 沙箱(minimal-up2k.html 必需) |
--exp-lg | exp_lg(volflag) | 内置默认集 | prologue/epilogue 内容中展开的占位符列表 |
两个来自 changelog 的实用细节(docs/changelog.md):
--css-browser与--js-browser支持缓存指令 URL:--css-browser=/the.css?cache=600(秒)或--js-browser=/.res/the.js?cache=i(7 天);源码中zs += "&" if "?" in zs else "?"的兼容逻辑正是为此服务;--js-browser只影响文件浏览器页,--js-other负责其余页面——这与 README 的分组方式完全一致。
五、自定义插件开发要点
综合上面所有示例,写一个 copyparty 浏览器端插件需要知道的约束:
- 加载时机与依赖:插件脚本经
--js-browser注入时,位于browser.js/up2k.js之后加载(copyparty/web/browser.html),可直接调用up2k、msel、thegrid、perms、toast、modal、mknod、ebi、QS等全局对象; - CSP nonce:通过上述两个参数注入的脚本自动带
nonce="{{ js_nonce }}";若改用--html-head手写内联脚本,必须自己带上nonce="{{ js_nonce }}"属性,否则被 CSP 拦截; - 可访问性前提:插件文件必须能通过 HTTP 从该服务器取到(放进可读卷,路径如
/res/xxx.js或/.res/xxx.js); - up2k 过滤钩子:
up2k_hooks.push(fn)注册 +up2k.gotallfiles.unshift(namefilter)插入,处理完必须把三份文件列表传回hooks0; - epilogue 与沙箱:
.epilogue.html默认跑在受限 iframe 里,需要 DOM 操作的方案要么调整--lg-sbf/--lg-sba,要么干脆像 minimal-up2k 那样用--no-sb-lg(并考虑--no-dot-ren降低误操作风险),或者优先选择--js-browser的 JS 插件方案(不受 iframe 限制); - 卷级差异化:
js-browser/js-other/html_head/epilogues等均标注了 volflag,意味着可以按卷(而不是只按全局)开启不同的插件与 epilogue 文件名列表,实现"某个卷的目录长得不一样"这类需求。
六、参考文件索引
- contrib/plugins/README.md —— 本文主体依据的插件清单
- contrib/plugins/minimal-up2k.html / contrib/plugins/minimal-up2k.js —— 极简上传 UI(HTML/JS 两版)
- contrib/plugins/quickmove.js —— 热键移动选中文件
- contrib/plugins/up2k-hooks.js / contrib/plugins/up2k-hook-ytid.js —— up2k 过滤钩子(通用/YouTube ID 白名单)
- contrib/plugins/banner.js —— 全站法律横幅
- contrib/plugins/browser-icons.css —— 网格视图文件类型图标
- contrib/plugins/meadup.js / contrib/plugins/graft-thumbs.js —— 媒体中心遥控器 / 边车缩略图
- contrib/plugins/rave.js —— README 归入 "junk" 的愚人节特效插件(官方标注未维护、有癫痫警告,不建议生产使用)
- copyparty/httpcli.py ——
_add_logues:prologue/epilogue 读取与展开 - copyparty/httpcli.py ——
js/css模板变量注入 - copyparty/web/browser.html —— 页面模板中的
{{ css }}/{{ js }}占位符与 nonce 脚本 - copyparty/web/up2k.js ——
gotallfiles钩子链与up2k_hooks注册入口 - docs/changelog.md ——
--js-browser/--css-browser的历史与缓存指令说明
【免费下载链接】copypartyPortable file server with accelerated resumable uploads, dedup, WebDAV, SFTP, FTP, TFTP, zeroconf, media indexer, thumbnails++ all in one file项目地址: https://gitcode.com/GitHub_Trending/co/copyparty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考