rss: title/description templating; closes #1047

also closes #1053, a PR which inspired this commit heavily
(slightly different approach for flexibility and performance)

Co-authored-by: Dawson Jeane <dawsonmjeane@gmail.com>
This commit is contained in:
ed
2025-12-14 00:06:54 +00:00
parent 965a4a6949
commit 5e85e3d628
3 changed files with 33 additions and 9 deletions

View File

@@ -1701,7 +1701,9 @@ def add_rss(ap):
ap2.add_argument("--rss", action="store_true", help="enable RSS output (experimental) (volflag=rss)")
ap2.add_argument("--rss-nf", metavar="HITS", type=int, default=250, help="default number of files to return (url-param 'nf')")
ap2.add_argument("--rss-fext", metavar="E,E", type=u, default="", help="default list of file extensions to include (url-param 'fext'); blank=all")
ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files")
ap2.add_argument("--rss-sort", metavar="ORD", type=u, default="m", help="default sort order (url-param 'sort'); [\033[32mm\033[0m]=last-modified [\033[32mu\033[0m]=upload-time [\033[32mn\033[0m]=filename [\033[32ms\033[0m]=filesize; Uppercase=oldest-first. Note that upload-time is 0 for non-uploaded files (volflag=rss_sort)")
ap2.add_argument("--rss-fmt-t", metavar="TXT", type=u, default="{fname}", help="title format (url-param 'rss_fmt_t') (volflag=rss_fmt_t)")
ap2.add_argument("--rss-fmt-d", metavar="TXT", type=u, default="{artist} - {title}", help="description format (url-param 'rss_fmt_d') (volflag=rss_fmt_d)")
def add_db_general(ap, hcores):

View File

@@ -131,6 +131,9 @@ def vf_vmap() -> dict[str, str]:
"readmes",
"mv_retry",
"rm_retry",
"rss_sort",
"rss_fmt_t",
"rss_fmt_d",
"shr_who",
"sort",
"tail_fd",
@@ -393,6 +396,12 @@ flagcats = {
"tail_tmax=30": "kill connection after 30 sec",
"tail_who=2": "restrict ?tail access (1=admins,2=authed,3=everyone)",
},
"rss": {
"rss": "allow '?rss' URL suffix (experimental)",
"rss_sort=m": "default sort-order (m/u/n/s)",
"rss_fmt_t={fname}": "default title-format",
"rss_fmt_d={album},{.tn}": "default description-format",
},
"others": {
"dots": "allow all users with read-access to\nenable the option to show dotfiles in listings",
"fk=8": 'generates per-file accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
@@ -400,7 +409,6 @@ flagcats = {
"dk=8": 'generates per-directory accesskeys,\nwhich are then required at the "g" permission;\nkeys are invalidated if filesize or inode changes',
"dks": "per-directory accesskeys allow browsing into subdirs",
"dky": 'allow seeing files (not folders) inside a specific folder\nwith "g" perm, and does not require a valid dirkey to do so',
"rss": "allow '?rss' URL suffix (experimental)",
"rmagic": "expensive analysis for mimetype accuracy",
"shr_who=auth": "who can create shares? no/auth/a",
"unp_who=2": "unpost only if same... 1=ip+name, 2=ip, 3=name",

View File

@@ -173,6 +173,7 @@ RE_MHOST = re.compile(r"^[][0-9a-zA-Z.:_-]+$") # match faster >=18ch
RE_K = re.compile(r"[^0-9a-zA-Z_-]") # search faster <=17ch
RE_HR = re.compile(r"[<>\"'&]")
RE_MDV = re.compile(r"(.*)\.([0-9]+\.[0-9]{3})(\.[Mm][Dd])$")
RE_RSS_KW = re.compile(r"(\{[^} ]+\})")
UPARAM_CC_OK = set("doc move tree".split())
@@ -1556,18 +1557,31 @@ class HttpCli(object):
ap = ""
use_magic = "rmagic" in self.vn.flags
tpl_t = self.uparam.get("fmt_t") or self.vn.flags["rss_fmt_t"]
tpl_d = self.uparam.get("fmt_d") or self.vn.flags["rss_fmt_d"]
kw_t = [[x, x[1:-1]] for x in RE_RSS_KW.findall(tpl_t)]
kw_d = [[x, x[1:-1]] for x in RE_RSS_KW.findall(tpl_d)]
for i in hits:
if use_magic:
ap = os.path.join(self.vn.realpath, i["rp"])
tags = i["tags"]
iurl = html_escape("%s%s" % (baseurl, i["rp"]), True, True)
title = unquotep(i["rp"].split("?")[0].split("/")[-1])
title = html_escape(title, True, True)
tag_t = str(i["tags"].get("title") or "")
tag_a = str(i["tags"].get("artist") or "")
desc = "%s - %s" % (tag_a, tag_t) if tag_t and tag_a else (tag_t or tag_a)
desc = html_escape(desc, True, True) if desc else title
mime = html_escape(guess_mime(title, ap))
fname = tags["fname"] = unquotep(i["rp"].split("?")[0].split("/")[-1])
title = tpl_t
desc = tpl_d
for zs1, zs2 in kw_t:
title = title.replace(zs1, str(tags.get(zs2, "")))
for zs1, zs2 in kw_d:
desc = desc.replace(zs1, str(tags.get(zs2, "")))
title = html_escape(title.strip(), True, True)
if desc.strip(" -,"):
desc = html_escape(desc.strip(), True, True)
else:
desc = title
mime = html_escape(guess_mime(fname, ap))
lmod = formatdate(max(0, i["ts"]))
zsa = (iurl, iurl, title, desc, lmod, iurl, mime, i["sz"])
zs = (