From 022f216217dc0e2dbf728444a95390e5da458221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Sun, 27 Jul 2025 18:27:22 +0200 Subject: [PATCH] [itaku] add 'posts' extractors (#7707) --- docs/configuration.rst | 1 + docs/supportedsites.md | 2 +- gallery_dl/extractor/itaku.py | 67 ++++++++++++++++++++++-- scripts/supportedsites.py | 3 ++ test/results/itaku.py | 99 +++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 4 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 0cbc1340..5d77d9ee 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -3203,6 +3203,7 @@ Description Supported values are * ``gallery`` + * ``posts`` * ``followers`` * ``following`` * ``stars`` diff --git a/docs/supportedsites.md b/docs/supportedsites.md index 87e8e6d2..c0019210 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -514,7 +514,7 @@ Consider all listed sites to potentially be NSFW. Itaku https://itaku.ee/ - Followers, Followed Users, Galleries, individual Images, Search Results, Stars, User Profiles + Followers, Followed Users, Galleries, individual Images, Posts, Search Results, Stars, User Profiles diff --git a/gallery_dl/extractor/itaku.py b/gallery_dl/extractor/itaku.py index 0ea22526..624fcce1 100644 --- a/gallery_dl/extractor/itaku.py +++ b/gallery_dl/extractor/itaku.py @@ -32,7 +32,6 @@ class ItakuExtractor(Extractor): def items(self): if images := self.images(): for image in images: - image["date"] = text.parse_datetime( image["date_added"], "%Y-%m-%dT%H:%M:%S.%fZ") for category, tags in image.pop("categorized_tags").items(): @@ -59,7 +58,20 @@ class ItakuExtractor(Extractor): if posts := self.posts(): for post in posts: - ... + images = post.pop("gallery_images") or () + post["count"] = len(images) + post["date"] = text.parse_datetime( + post["date_added"], "%Y-%m-%dT%H:%M:%S.%fZ") + post["tags"] = [t["name"] for t in post["tags"]] + + yield Message.Directory, post + for post["num"], image in enumerate(images, 1): + post["file"] = image + image["date"] = text.parse_datetime( + image["date_added"], "%Y-%m-%dT%H:%M:%S.%fZ") + + url = image["image"] + yield Message.Url, url, text.nameext_from_url(url, post) return if users := self.users(): @@ -74,7 +86,7 @@ class ItakuExtractor(Extractor): class ItakuGalleryExtractor(ItakuExtractor): - """Extractor for posts from an itaku user gallery""" + """Extractor for images from an itaku user gallery""" subcategory = "gallery" pattern = USER_PATTERN + r"/gallery(?:/(\d+))?" example = "https://itaku.ee/profile/USER/gallery" @@ -87,6 +99,24 @@ class ItakuGalleryExtractor(ItakuExtractor): }) +class ItakuPostsExtractor(ItakuExtractor): + """Extractor for an itaku user's posts""" + subcategory = "posts" + directory_fmt = ("{category}", "{owner_username}", "Posts", + "{id}{title:? //}") + filename_fmt = "{file[id]}{file[title]:? //}.{extension}" + archive_fmt = "{id}_{file[id]}" + pattern = USER_PATTERN + r"/posts(?:/(\d+))?" + example = "https://itaku.ee/profile/USER/posts" + + def posts(self): + user, folder = self.groups + return self.api.posts({ + "owner" : self.api.user_id(user), + "folders": folder, + }) + + class ItakuStarsExtractor(ItakuExtractor): subcategory = "stars" pattern = USER_PATTERN + r"/stars(?:/(\d+))?" @@ -132,6 +162,7 @@ class ItakuUserExtractor(Dispatch, ItakuExtractor): base = f"{self.root}/profile/{self.groups[0]}/" return self._dispatch_extractors(( (ItakuGalleryExtractor , base + "gallery"), + (ItakuPostsExtractor , base + "posts"), (ItakuFollowersExtractor, base + "followers"), (ItakuFollowingExtractor, base + "following"), (ItakuStarsExtractor , base + "stara"), @@ -147,6 +178,19 @@ class ItakuImageExtractor(ItakuExtractor): return (self.api.image(self.groups[0]),) +class ItakuPostExtractor(ItakuExtractor): + subcategory = "post" + directory_fmt = ("{category}", "{owner_username}", "Posts", + "{id}{title:? //}") + filename_fmt = "{file[id]}{file[title]:? //}.{extension}" + archive_fmt = "{id}_{file[id]}" + pattern = BASE_PATTERN + r"/posts/(\d+)" + example = "https://itaku.ee/posts/12345" + + def posts(self): + return (self.api.post(self.groups[0]),) + + class ItakuSearchExtractor(ItakuExtractor): subcategory = "search" pattern = BASE_PATTERN + r"/home/images/?\?([^#]+)" @@ -200,6 +244,19 @@ class ItakuAPI(): } return self._pagination(endpoint, params, self.image) + def posts(self, params): + endpoint = "/posts/" + params = { + "cursor" : None, + "date_range": "", + "maturity_rating": ("SFW", "Questionable", "NSFW"), + "ordering" : "-date_added", + "page" : "1", + "page_size" : "30", + **params, + } + return self._pagination(endpoint, params) + def user_profiles(self, params): endpoint = "/user_profiles/" params = { @@ -216,6 +273,10 @@ class ItakuAPI(): endpoint = f"/galleries/images/{image_id}/" return self._call(endpoint) + def post(self, post_id): + endpoint = f"/posts/{post_id}/" + return self._call(endpoint) + @memcache(keyarg=1) def user(self, username): return self._call(f"/user_profiles/{username}/") diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py index 3b0ffa3c..50afbd98 100755 --- a/scripts/supportedsites.py +++ b/scripts/supportedsites.py @@ -293,6 +293,9 @@ SUBCATEGORY_MAP = { "saved": "Saved Posts", "tagged": "Tagged Posts", }, + "itaku": { + "posts": "", + }, "iwara": { "user-images": "User Images", "user-videos": "User Videos", diff --git a/test/results/itaku.py b/test/results/itaku.py index 325f8b6a..39c8ac1e 100644 --- a/test/results/itaku.py +++ b/test/results/itaku.py @@ -36,6 +36,36 @@ __tests__ = ( "sections" : ["Fanart/Pokemon"], }, +{ + "#url" : "https://itaku.ee/profile/piku/posts", + "#class" : itaku.ItakuPostsExtractor, + "#results" : ( + "https://itaku.ee/api/media/gallery_imgs/220415_xEFUVR6.png", + "https://itaku.ee/api/media/gallery_imgs/220308_J0mgJ24.png", + "https://itaku.ee/api/media/gallery_imgs/220511_rdGpatf.png", + "https://itaku.ee/api/media/gallery_imgs/220420b_4Lrk6gB.png", + ), + + "id" : {23762, 16422}, + "count": {3, 1}, + "num" : range(1, 3), + "date" : "type:datetime", + "title": {"Maids", ""}, +}, + +{ + "#url" : "https://itaku.ee/profile/starluxioad/posts/2008", + "#comment" : "posts folder", + "#class" : itaku.ItakuPostsExtractor, + "#count" : 12, + + "id" : {160779, 160163, 151859, 151851, 150443}, + "count": {2, 3}, + "num" : range(1, 3), + "date" : "type:datetime", + "title": str, +}, + { "#url" : "https://itaku.ee/profile/piku/stars", "#class" : itaku.ItakuStarsExtractor, @@ -119,6 +149,75 @@ __tests__ = ( "#results" : "https://itaku.ee/api/media/gallery_vids/sleepy_af_OY5GHWw.mp4", }, +{ + "#url" : "https://itaku.ee/posts/16422", + "#class" : itaku.ItakuPostExtractor, + "#results" : "https://itaku.ee/api/media/gallery_imgs/220420b_4Lrk6gB.png", + + "already_pinned" : None, + "can_reshare" : True, + "category" : "itaku", + "content" : "", + "content_warning": "", + "count" : 1, + "created_by_images": True, + "date" : "dt:2022-04-26 16:06:30", + "date_added" : "2022-04-26T16:06:30.352389Z", + "date_edited" : "2022-05-10T21:32:44.017311Z", + "extension" : "png", + "file" : { + "already_pinned" : None, + "animated" : False, + "blacklisted" : { + "blacklisted_tags": [], + "is_blacklisted" : False, + }, + "bookmarked_by_you": False, + "content_warning": "", + "date" : "dt:2022-04-26 16:06:28", + "date_added" : "2022-04-26T16:06:28.272442Z", + "date_edited" : "2022-06-30T09:43:58.816192Z", + "id" : 77775, + "image" : "https://itaku.ee/api/media/gallery_imgs/220420b_4Lrk6gB.png", + "image_lg" : "https://itaku.ee/api/media/gallery_imgs/220420b_4Lrk6gB/lg.jpg", + "image_xl" : "https://itaku.ee/api/media/gallery_imgs/220420b_4Lrk6gB/lg.jpg", + "is_thumbnail_for_video": False, + "liked_by_you" : False, + "maturity_rating": "SFW", + "num_comments" : 0, + "num_likes" : range(60, 90), + "num_reshares" : 0, + "owner" : 16775, + "owner_displayname": "Piku", + "show_content_warning": False, + "title" : "Felicia", + "too_mature" : False, + "visibility" : "PUBLIC", + }, + "filename" : "220420b_4Lrk6gB", + "folders" : [], + "id" : 16422, + "liked_by_you" : False, + "maturity_rating": "SFW", + "num" : 1, + "num_comments" : 0, + "num_images" : 1, + "num_likes" : range(40, 70), + "num_reshares" : 0, + "obj_tags" : 99052, + "owner" : 16775, + "owner_avatar" : "https://itaku.ee/api/media/profile_pics/av2022r_vKYVywc/md.jpg", + "owner_displayname": "Piku", + "owner_username" : "piku", + "poll" : None, + "reshared_by_you": False, + "subcategory" : "post", + "tags" : [], + "title" : "", + "too_mature" : False, + "visibility" : "PUBLIC", +}, + { "#url" : "https://itaku.ee/home/images?tags=cute", "#comment" : "simple search",