From cf96fc6ebef9b4400a4195ca89d214c22016a2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Tue, 6 Jan 2026 09:35:47 +0100 Subject: [PATCH] [koofr] refactor (#8803) https://github.com/mikf/gallery-dl/issues/8803#issuecomment-3708215475 https://github.com/mikf/gallery-dl/issues/8803#issuecomment-3708358606 - add 'recursive' option, remove 'zip' - recurse into subdirectories - add 'path' metadata - remove 'count' & 'num' metadata - update default directory & archive format --- docs/configuration.rst | 12 ++++-- docs/gallery-dl.conf | 2 +- gallery_dl/extractor/koofr.py | 70 ++++++++++++++++++++++------------- test/results/koofr.py | 47 +++++++++++++++++------ 4 files changed, 89 insertions(+), 42 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 87a8af28..d720f014 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -4073,14 +4073,18 @@ Description the first in the list gets chosen (usually `mp3`). -extractor.koofr.zip -------------------- +extractor.koofr.recursive +------------------------- Type ``bool`` Default - ``false`` + ``true`` Description - Download shared `/links/` with multiple files as a single `.zip` file. + ``true`` + Recursively descent into subfolders + while downloading individual files. + ``false`` + Download shared `/links/` with multiple files as a single `.zip` file. extractor.lolisafe.domain diff --git a/docs/gallery-dl.conf b/docs/gallery-dl.conf index 3a6d4b09..202e2985 100644 --- a/docs/gallery-dl.conf +++ b/docs/gallery-dl.conf @@ -520,7 +520,7 @@ }, "koofr": { - "zip": false + "recursive": true }, "luscious": { diff --git a/gallery_dl/extractor/koofr.py b/gallery_dl/extractor/koofr.py index 08a737c5..5c029447 100644 --- a/gallery_dl/extractor/koofr.py +++ b/gallery_dl/extractor/koofr.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright 2025 Mike Fährmann +# Copyright 2025-2026 Mike Fährmann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as @@ -17,8 +17,8 @@ class KoofrSharedExtractor(Extractor): category = "koofr" subcategory = "shared" root = "https://app.koofr.net" - directory_fmt = ("{category}", "{date:%Y-%m-%d} {title}") - archive_fmt = "{post[id]}_{hash|id}" + directory_fmt = ("{category}", "{post[title]} ({post[id]})", "{path:I}") + archive_fmt = "{post[id]}_{path:J/}_{hash|id}" pattern = (r"(?:https?://)?(?:" r"(?:app\.)?koofr\.(?:net|eu)/links/([\w-]+)|" r"k00\.fr/(\w+))") @@ -43,35 +43,28 @@ class KoofrSharedExtractor(Extractor): "Sec-Fetch-Site" : "same-origin", } data = self.request_json(url, params=params, headers=headers) - root = data.get("publicUrlBase") or self.root - base = f"{root}/content/links/{uuid}/files/get/" - headers = {"Referer": referer} + file = data["file"] - - if file["type"] == "dir" and not self.config("zip", False): - path = True - url = url + "/bundle" - params["path"] = "/" - files = self.request_json( - url, params=params, headers=headers)["files"] + file["path"] = [] + if file["type"] == "dir" and self.config("recursive", True): + files = self._extract_files(file, url + "/bundle", params, headers) + recursive = True else: - path = False files = (file,) - - if password: - password = text.escape(password) + recursive = False post = { "id" : data["id"], "title": data["name"], - "count": len(files), "date" : self.parse_timestamp(file["modified"] / 1000), } - yield Message.Directory, "", post - for num, file in enumerate(files, 1): - file["count"] = len(files) - file["num"] = num + base = (f"{data.get('publicUrlBase') or self.root}" + f"/content/links/{uuid}/files/get/") + headers = {"Referer": referer} + password = "&password=" + text.escape(password) if password else "" + + for file in files: file["post"] = post file["date"] = self.parse_timestamp(file["modified"] / 1000) file["_http_headers"] = headers @@ -79,9 +72,34 @@ class KoofrSharedExtractor(Extractor): name = file["name"] text.nameext_from_name(name, file) - name = text.escape(name) - url = (f"{base}{name}?path=%2F{name if path else '&force'}") - if password: - url = f"{url}&password={password}" + if recursive: + if path := file["path"]: + path = f"{'/'.join(path)}/{name}" + else: + path = name + else: + path = "" + password += "&force" + url = (f"{base}{text.escape(name)}" + f"?path=/{text.escape(path)}{password}") + + yield Message.Directory, "", file yield Message.Url, url, file + + def _extract_files(self, dir, url, params, headers): + path = dir["path"] + params["path"] = "/" + "/".join(path) + + files = self.request_json( + url, params=params, headers=headers)["files"] + + for file in files: + if file["type"] == "dir": + file["path"] = path.copy() + file["path"].append(file["name"]) + yield from self._extract_files( + file, url, params.copy(), headers) + else: + file["path"] = path + yield file diff --git a/test/results/koofr.py b/test/results/koofr.py index ef7d5fdf..77b976ec 100644 --- a/test/results/koofr.py +++ b/test/results/koofr.py @@ -11,7 +11,7 @@ __tests__ = ( { "#url" : "https://k00.fr/cltf71jr", "#class" : koofr.KoofrSharedExtractor, - "#results" : "https://app.koofr.net/content/links/923b4f56-3aaf-49ee-95e3-d85c52b687b0/files/get/wsf-form-job-application-form.json?path=%2F&force", + "#results" : "https://app.koofr.net/content/links/923b4f56-3aaf-49ee-95e3-d85c52b687b0/files/get/wsf-form-job-application-form.json?path=/&force", "#sha1_content": "f65ccc63a99165ecb9ff2ab92302c25b245a904f", "contentType": "application/json", @@ -28,7 +28,7 @@ __tests__ = ( { "#url" : "https://app.koofr.net/links/923b4f56-3aaf-49ee-95e3-d85c52b687b0", "#class" : koofr.KoofrSharedExtractor, - "#results" : "https://app.koofr.net/content/links/923b4f56-3aaf-49ee-95e3-d85c52b687b0/files/get/wsf-form-job-application-form.json?path=%2F&force", + "#results" : "https://app.koofr.net/content/links/923b4f56-3aaf-49ee-95e3-d85c52b687b0/files/get/wsf-form-job-application-form.json?path=/&force", "#sha1_content": "f65ccc63a99165ecb9ff2ab92302c25b245a904f", "contentType": "application/json", @@ -52,12 +52,12 @@ __tests__ = ( "#comment" : "individual files", "#class" : koofr.KoofrSharedExtractor, "#options" : {"zip": False}, - "#pattern" : r"https://app\.koofr\.net/content/links/01deac62\-f5d6\-4d2b\-7043\-53b24cc0a038/files/get/smw_msu1\-\d+\.pcm\?path=%2Fsmw_msu1\-\d+\.pcm", + "#pattern" : r"https://app\.koofr\.net/content/links/01deac62\-f5d6\-4d2b\-7043\-53b24cc0a038/files/get/smw_msu1\-\d+\.pcm\?path=/smw_msu1\-\d+\.pcm", "#count" : 18, "contentType": "application/octet-stream", - "count" : 18, - "num" : range(1, 18), + "!count" : 18, + "!num" : range(1, 18), "date" : "type:datetime", "extension" : "pcm", "filename" : r"re:smw_msu1-\d+", @@ -68,7 +68,7 @@ __tests__ = ( "tags" : {}, "type" : "file", "post" : { - "count" : 18, + "!count" : 18, "date" : "dt:2023-11-19 16:27:56", "id" : "01deac62-f5d6-4d2b-7043-53b24cc0a038", "title" : "Church of Kondo", @@ -79,26 +79,51 @@ __tests__ = ( "#url" : "https://app.koofr.net/links/01deac62-f5d6-4d2b-7043-53b24cc0a038", "#comment" : ".zip container", "#class" : koofr.KoofrSharedExtractor, - "#options" : {"zip": True}, - "#results" : "https://app.koofr.net/content/links/01deac62-f5d6-4d2b-7043-53b24cc0a038/files/get/Church of Kondo?path=%2F&force", + "#options" : {"recursive": False}, + "#results" : "https://app.koofr.net/content/links/01deac62-f5d6-4d2b-7043-53b24cc0a038/files/get/Church of Kondo?path=/&force", "contentType": "", - "count" : 1, + "!count" : 1, + "!num" : 1, "date" : "dt:2023-11-19 16:27:56", "extension" : "", "filename" : "Church of Kondo", "modified" : 1700411276087, "name" : "Church of Kondo", - "num" : 1, "size" : 0, "tags" : {}, "type" : "dir", "post" : { - "count" : 1, + "!count" : 1, "date" : "dt:2023-11-19 16:27:56", "id" : "01deac62-f5d6-4d2b-7043-53b24cc0a038", "title" : "Church of Kondo", }, }, +{ + "#url" : "https://app.koofr.net/links/7667d857-c639-4f38-93d1-c42394492a0c", + "#comment" : "recursive directories", + "#class" : koofr.KoofrSharedExtractor, + "#pattern" : r"https://app\.koofr\.net/content/links/7667d857\-c639\-4f38\-93d1\-c42394492a0c/files/get/[\w]\.png\?path=/.*\w\.png", + "#count" : 16, + + "contentType": "image/png", + "date" : "type:datetime", + "extension" : "png", + "filename" : r"re:^[1-8a-d]$", + "hash" : "hash:md5", + "modified" : range(1767688000000, 1767700000000), + "name" : r"re:^[1-8a-d]\.png", + "path" : {(), ("dir-l1-1",), ("dir-l1-2",), ("dir-l1-1", "dir-l2-1")}, + "size" : range(200, 999), + "tags" : {}, + "type" : "file", + "post" : { + "date" : "dt:2026-01-06 08:27:26", + "id" : "7667d857-c639-4f38-93d1-c42394492a0c", + "title": "dir", + }, +}, + )