From 9677bf6e18013fe84d6668b00e80296f9adcd31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Fri, 12 Sep 2025 22:20:21 +0200 Subject: [PATCH] [fansly] add 'creator-media' extractor (#4401) --- docs/supportedsites.md | 2 +- gallery_dl/extractor/fansly.py | 65 +++++++++++++++++++++++++++++----- scripts/supportedsites.py | 2 +- test/results/fansly.py | 5 +++ 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/docs/supportedsites.md b/docs/supportedsites.md index e8e836eb..ff4971f7 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -292,7 +292,7 @@ Consider all listed sites to potentially be NSFW. Fansly https://fansly.com/ - Creator Posts, Home Feed, Lists, Account Lists, Posts + Creator Media, Creator Posts, Home Feed, Lists, Account Lists, Posts diff --git a/gallery_dl/extractor/fansly.py b/gallery_dl/extractor/fansly.py index 873da0d0..d4d8ea26 100644 --- a/gallery_dl/extractor/fansly.py +++ b/gallery_dl/extractor/fansly.py @@ -191,13 +191,20 @@ class FanslyCreatorPostsExtractor(FanslyExtractor): example = "https://fansly.com/CREATOR/posts" def posts(self): - creator = self.groups[0] - if creator.startswith("id:"): - account = self.api.account_by_id(creator[3:]) - else: - account = self.api.account(creator) - wall_id = account["walls"][0]["id"] - return self.api.timeline_new(account["id"], wall_id) + account = self.api.account(self.groups[0]) + return self.api.timeline_new( + account["id"], account["walls"][0]["id"]) + + +class FanslyCreatorMediaExtractor(FanslyExtractor): + subcategory = "creator-media" + pattern = rf"{BASE_PATTERN}/([^/?#]+)/media" + example = "https://fansly.com/CREATOR/media" + + def posts(self): + account = self.api.account(self.groups[0]) + return self.api.mediaoffers_location( + account["id"], account["walls"][0]["id"]) class FanslyAPI(): @@ -217,7 +224,12 @@ class FanslyAPI(): else: self.extractor.log.warning("No 'token' provided") - def account(self, username): + def account(self, creator): + if creator.startswith("id:"): + return self.account_by_id(creator[3:]) + return self.account_by_username(creator) + + def account_by_username(self, username): endpoint = "/v1/account" params = {"usernames": username} return self._call(endpoint, params)[0] @@ -252,6 +264,20 @@ class FanslyAPI(): } return self._pagination(endpoint, params) + def mediaoffers_location(self, account_id, wall_id): + endpoint = "/v1/mediaoffers/location" + params = { + "locationId": wall_id, + "locationType": "1002", + "accountId": account_id, + "mediaType": "", + "before": "", + "after" : "0", + "limit" : "30", + "offset": "0", + } + return self._pagination_media(endpoint, params) + def post(self, post_id): endpoint = "/v1/post" params = {"ids": post_id} @@ -319,6 +345,19 @@ class FanslyAPI(): return posts + def _update_media(self, items, response): + posts = { + post["id"]: post + for post in response["posts"] + } + + response["posts"] = [ + posts[item["correlationId"]] + for item in items + ] + + return self._update_posts(response) + def _update_items(self, items): ids = [item["id"] for item in items] accounts = { @@ -353,3 +392,13 @@ class FanslyAPI(): posts = self._update_posts(response) yield from posts params["before"] = min(p["id"] for p in posts) + + def _pagination_media(self, endpoint, params): + while True: + response = self._call(endpoint, params) + + data = response["data"] + if not data: + return + yield from self._update_media(data, response["aggregationData"]) + params["before"] = data[-1]["id"] diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py index 328cd258..70ecda25 100755 --- a/scripts/supportedsites.py +++ b/scripts/supportedsites.py @@ -591,7 +591,7 @@ def subcategory_text(bc, c, sc): sc = f"{sc[:-1]}ies" elif sc.endswith("h"): sc = f"{sc}es" - elif not sc.endswith("s"): + elif not sc.endswith("s") and not sc.endswith("edia"): sc = f"{sc}s" return sc diff --git a/test/results/fansly.py b/test/results/fansly.py index cfebfcf7..1c460542 100644 --- a/test/results/fansly.py +++ b/test/results/fansly.py @@ -67,6 +67,11 @@ __tests__ = ( "#class" : fansly.FanslyCreatorPostsExtractor, }, +{ + "#url" : "https://fansly.com/Oliviaus/media", + "#class" : fansly.FanslyCreatorMediaExtractor, +}, + { "#url" : "https://fansly.com/home", "#class" : fansly.FanslyHomeExtractor,