mirror of
https://github.com/9001/copyparty.git
synced 2025-10-26 10:12:43 -06:00
Merge 2969d9ff92 into 260da2f45c
This commit is contained in:
commit
987322440e
21
README.md
21
README.md
|
|
@ -1039,6 +1039,27 @@ url parameters:
|
|||
* `a` = filesize
|
||||
* uppercase = reverse-sort; `M` = oldest file first
|
||||
|
||||
# opds feeds
|
||||
|
||||
browse and download files from your e-book reader
|
||||
|
||||
enabled with the `opds` volflag or `--opds` global option
|
||||
|
||||
add `?opds` to the end of the url you would like to browse, then input that in your opds client.
|
||||
for example: `https://copyparty.example/books/?opds`.
|
||||
|
||||
to log in with a password, enter it into either of the username or password fields in your client.
|
||||
|
||||
- if you've enabled `--usernames`, then you need to enter both username and password .
|
||||
|
||||
note: some clients (e.g. Moon+ Reader) will not send the password when downloading cover images, which will
|
||||
cause your ip to be banned by copyparty. to work around this, you can grant the [`g` permission](#accounts-and-volumes)
|
||||
to unauthenticated requests and enable [filekeys](#filekeys) to prevent guessing filenames. for example:
|
||||
`-vbooks:books:r,ed:g:c,fk,opds`
|
||||
|
||||
by default, not all file types will be listed in opds feeds. to change this, add the extension to
|
||||
`--opds-allowed` (volflag: `opds_allowed`), or empty the list to list everything
|
||||
|
||||
|
||||
## recent uploads
|
||||
|
||||
|
|
|
|||
|
|
@ -1436,6 +1436,10 @@ def add_smb(ap):
|
|||
ap2.add_argument("--smbvv", action="store_true", help="verboser")
|
||||
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
||||
|
||||
def add_opds(ap):
|
||||
ap2 = ap.add_argument_group("OPDS options")
|
||||
ap2.add_argument("--opds", action="store_true", help="enable opds -- allows e-book readers to browse and download files (volflag=opds)")
|
||||
ap2.add_argument("--opds-allowed", metavar="T,T", type=u, default="epub,cbz,pdf", help="file formats to list in OPDS feeds; leave empty to show everything (volflag=opds_allowed)")
|
||||
|
||||
def add_handlers(ap):
|
||||
ap2 = ap.add_argument_group("handlers (see --help-handlers)")
|
||||
|
|
@ -1864,6 +1868,7 @@ def run_argparse(
|
|||
add_webdav(ap)
|
||||
add_tftp(ap)
|
||||
add_smb(ap)
|
||||
add_opds(ap)
|
||||
add_safety(ap)
|
||||
add_salt(ap, fk_salt, dk_salt, ah_salt)
|
||||
add_optouts(ap)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ def vf_bmap() -> dict[str, str]:
|
|||
"og",
|
||||
"og_no_head",
|
||||
"og_s_title",
|
||||
"opds",
|
||||
"rand",
|
||||
"reflink",
|
||||
"rmagic",
|
||||
|
|
@ -145,6 +146,7 @@ def vf_cmap() -> dict[str, str]:
|
|||
"mte",
|
||||
"mth",
|
||||
"mtp",
|
||||
"opds_allowed",
|
||||
"xac",
|
||||
"xad",
|
||||
"xar",
|
||||
|
|
@ -331,6 +333,10 @@ flagcats = {
|
|||
"og_no_head": "you want to add tags manually with og_tpl",
|
||||
"og_ua": "if defined: only send OG html if useragent matches this regex",
|
||||
},
|
||||
"opds": {
|
||||
"opds": "enable OPDS",
|
||||
"opds_allowed": "file formats to list in OPDS feeds; leave empty to show everything"
|
||||
},
|
||||
"textfiles": {
|
||||
"md_no_br": "newline only on double-newline or two tailing spaces",
|
||||
"md_hist": "where to put markdown backups; s=subfolder, v=volHist, n=nope",
|
||||
|
|
|
|||
|
|
@ -4027,6 +4027,10 @@ class HttpCli(object):
|
|||
raise Pebkac(403, t)
|
||||
return ""
|
||||
|
||||
def _can_opds(self, volflags: dict[str, Any]) -> str:
|
||||
# TODO: Permissions
|
||||
return ""
|
||||
|
||||
def tx_res(self, req_path: str) -> bool:
|
||||
status = 200
|
||||
logmsg = "{:4} {} ".format("", self.req)
|
||||
|
|
@ -4229,7 +4233,7 @@ class HttpCli(object):
|
|||
#
|
||||
# force download
|
||||
|
||||
if "dl" in self.ouparam:
|
||||
if "dl" in self.ouparam or "opds" in self.uparam:
|
||||
cdis = gen_content_disposition(os.path.basename(req_path))
|
||||
self.out_headers["Content-Disposition"] = cdis
|
||||
|
||||
|
|
@ -6234,7 +6238,7 @@ class HttpCli(object):
|
|||
|
||||
add_og = "og" in vn.flags
|
||||
if add_og:
|
||||
if "th" in self.uparam or "raw" in self.uparam:
|
||||
if "th" in self.uparam or "raw" in self.uparam or "opds" in self.uparam:
|
||||
add_og = False
|
||||
elif vn.flags["og_ua"]:
|
||||
add_og = vn.flags["og_ua"].search(self.ua)
|
||||
|
|
@ -6446,9 +6450,19 @@ class HttpCli(object):
|
|||
is_ls = True
|
||||
|
||||
tpl = "browser"
|
||||
is_opds = False
|
||||
if "b" in self.uparam:
|
||||
tpl = "browser2"
|
||||
is_js = False
|
||||
elif "opds" in self.uparam:
|
||||
# Display directory listing as OPDS v1.2 catalog feed
|
||||
if not (self.args.opds or "opds" in self.vn.flags):
|
||||
raise Pebkac(405, "OPDS is disabled in server config")
|
||||
if not self.can_read:
|
||||
raise Pebkac(401, "OPDS requires read permission")
|
||||
is_opds = True
|
||||
tpl = "opds"
|
||||
is_js = False
|
||||
|
||||
vf = vn.flags
|
||||
ls_ret = {
|
||||
|
|
@ -6575,6 +6589,12 @@ class HttpCli(object):
|
|||
|
||||
no_zip = bool(self._can_zip(vf))
|
||||
|
||||
volflag_opds_allowed = vf.get("opds_allowed")
|
||||
if volflag_opds_allowed is not None:
|
||||
opds_no_filter = len(volflag_opds_allowed) == 0
|
||||
else:
|
||||
opds_no_filter = len(self.args.opds_allowed) == 0
|
||||
|
||||
dirs = []
|
||||
files = []
|
||||
ptn_hr = RE_HR
|
||||
|
|
@ -6638,6 +6658,8 @@ class HttpCli(object):
|
|||
ext = ptn_hr.sub("@", fn.rsplit(".", 1)[1])
|
||||
if len(ext) > 16:
|
||||
ext = ext[:16]
|
||||
if is_opds and not opds_no_filter and ext not in self.args.opds_allowed:
|
||||
continue
|
||||
else:
|
||||
ext = "%"
|
||||
|
||||
|
|
@ -6660,6 +6682,43 @@ class HttpCli(object):
|
|||
else:
|
||||
href = quotep(href)
|
||||
|
||||
mime = None
|
||||
if is_opds:
|
||||
href += "&" if "?" in href else "?"
|
||||
href += "opds"
|
||||
if not is_dir:
|
||||
if "rmagic" in self.vn.flags:
|
||||
mime = guess_mime(fn, fspath)
|
||||
else:
|
||||
mime = guess_mime(fn)
|
||||
# Make sure we can actually generate JPEG thumbnails
|
||||
if (
|
||||
self.args.th_no_jpg
|
||||
or not self.thumbcli
|
||||
or "dthumb" in dbv.flags
|
||||
or "dithumb" in dbv.flags
|
||||
):
|
||||
jpeg_thumb_href = None
|
||||
jpeg_thumb_href_hires = None
|
||||
else:
|
||||
jpeg_thumb_href = href + "&th=jf"
|
||||
jpeg_thumb_href_hires = jpeg_thumb_href + "3"
|
||||
|
||||
iso8601 = "%04d-%02d-%02dT%02d:%02d:%02dZ" % (
|
||||
zd.year,
|
||||
zd.month,
|
||||
zd.day,
|
||||
zd.hour,
|
||||
zd.minute,
|
||||
zd.second,
|
||||
)
|
||||
|
||||
else:
|
||||
mime = None
|
||||
iso8601 = None
|
||||
jpeg_thumb_href = None
|
||||
jpeg_thumb_href_hires = None
|
||||
|
||||
item = {
|
||||
"lead": margin,
|
||||
"href": href,
|
||||
|
|
@ -6667,7 +6726,11 @@ class HttpCli(object):
|
|||
"sz": sz,
|
||||
"ext": ext,
|
||||
"dt": dt,
|
||||
"iso8601": iso8601,
|
||||
"ts": int(linf.st_mtime),
|
||||
"mime": mime,
|
||||
"jpeg_thumb_href": jpeg_thumb_href,
|
||||
"jpeg_thumb_href_hires": jpeg_thumb_href_hires,
|
||||
}
|
||||
if is_dir:
|
||||
dirs.append(item)
|
||||
|
|
@ -6856,6 +6919,9 @@ class HttpCli(object):
|
|||
"taglist": taglist,
|
||||
}
|
||||
j2a["files"] = []
|
||||
elif is_opds:
|
||||
j2a["files"] = files
|
||||
j2a["dirs"] = dirs
|
||||
else:
|
||||
j2a["files"] = dirs + files
|
||||
|
||||
|
|
@ -7006,5 +7072,9 @@ class HttpCli(object):
|
|||
self.html_head = zs.replace("\n\n", "\n")
|
||||
|
||||
html = self.j2s(tpl, **j2a)
|
||||
self.reply(html.encode("utf-8", "replace"))
|
||||
if is_opds:
|
||||
mime = "application/atom+xml;profile=opds-catalog"
|
||||
else:
|
||||
mime = None
|
||||
self.reply(html.encode("utf-8", "replace"), mime=mime)
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ class HttpSrv(object):
|
|||
"svcs",
|
||||
]
|
||||
self.j2 = {x: env.get_template(x + ".html") for x in jn}
|
||||
self.j2["opds"] = env.get_template("opds.xml")
|
||||
self.prism = has_resource(self.E, "web/deps/prism.js.gz")
|
||||
|
||||
if self.args.ipu:
|
||||
|
|
|
|||
31
copyparty/web/opds.xml
Normal file
31
copyparty/web/opds.xml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
{%- for d in dirs %}
|
||||
<entry>
|
||||
<title>{{ d.name }}</title>
|
||||
<link rel="subsection"
|
||||
href="{{ d.href | e }}"
|
||||
type="application/atom+xml;profile=opds-catalog"/>
|
||||
<updated>{{ d.iso8601 }}</updated>
|
||||
</entry>
|
||||
{%- endfor %}
|
||||
{%- for f in files %}
|
||||
<entry>
|
||||
<title>{{ f.name }}</title>
|
||||
<updated>{{ f.iso8601 }}</updated>
|
||||
<link rel="http://opds-spec.org/acquisition"
|
||||
href="{{ f.href | e }}"
|
||||
type="{{ f.mime }}"/>
|
||||
{%- if f.jpeg_thumb_href != None %}
|
||||
<link rel="http://opds-spec.org/image/thumbnail"
|
||||
href="{{ f.jpeg_thumb_href | e }}"
|
||||
type="image/jpeg"/>
|
||||
{%- endif %}
|
||||
{%- if f.jpeg_thumb_href_hires != None %}
|
||||
<link rel="http://opds-spec.org/image"
|
||||
href="{{ f.jpeg_thumb_href_hires | e }}"
|
||||
type="image/jpeg"/>
|
||||
{%- endif %}
|
||||
</entry>
|
||||
{%- endfor %}
|
||||
</feed>
|
||||
Loading…
Reference in a new issue