diff --git a/docs/configuration.rst b/docs/configuration.rst index e922f813f1..cbc54a7dfb 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1401,7 +1401,13 @@ Description when processing a user profile. Possible values are - ``"gallery"``, ``"scraps"``, ``"journal"``, ``"favorite"``, ``"status"``. + ``"avatar"``, + ``"background"``, + ``"gallery"``, + ``"scraps"``, + ``"journal"``, + ``"favorite"``, + ``"status"``. It is possible to use ``"all"`` instead of listing all values separately. diff --git a/docs/supportedsites.md b/docs/supportedsites.md index b538749bfe..dbdaac24ad 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -148,7 +148,7 @@ Consider all listed sites to potentially be NSFW. DeviantArt https://www.deviantart.com/ - Collections, Deviations, Favorites, Folders, Followed Users, Galleries, Gallery Searches, Journals, Popular Images, Scraps, Search Results, Sta.sh, Status Updates, Tag Searches, User Profiles, Watches + Avatars, Backgrounds, Collections, Deviations, Favorites, Folders, Followed Users, Galleries, Gallery Searches, Journals, Popular Images, Scraps, Search Results, Sta.sh, Status Updates, Tag Searches, User Profiles, Watches OAuth diff --git a/gallery_dl/extractor/deviantart.py b/gallery_dl/extractor/deviantart.py index 2ba47e1e23..4b5f1d772a 100644 --- a/gallery_dl/extractor/deviantart.py +++ b/gallery_dl/extractor/deviantart.py @@ -38,7 +38,7 @@ class DeviantartExtractor(Extractor): def __init__(self, match): Extractor.__init__(self, match) - self.user = match.group(1) or match.group(2) + self.user = (match.group(1) or match.group(2)).lower() self.offset = 0 def _init(self): @@ -104,7 +104,6 @@ def items(self): raise exception.StopExtraction() else: self.subcategory = "group-" + self.subcategory - self.user = self.user.lower() self.group = True for deviation in self.deviations(): @@ -513,11 +512,13 @@ def initialize(self): def items(self): base = "{}/{}/".format(self.root, self.user) return self._dispatch_extractors(( - (DeviantartGalleryExtractor , base + "gallery"), - (DeviantartScrapsExtractor , base + "gallery/scraps"), - (DeviantartJournalExtractor , base + "posts"), - (DeviantartStatusExtractor , base + "posts/statuses"), - (DeviantartFavoriteExtractor, base + "favourites"), + (DeviantartAvatarExtractor , base + "avatar"), + (DeviantartBackgroundExtractor, base + "banner"), + (DeviantartGalleryExtractor , base + "gallery"), + (DeviantartScrapsExtractor , base + "gallery/scraps"), + (DeviantartJournalExtractor , base + "posts"), + (DeviantartStatusExtractor , base + "posts/statuses"), + (DeviantartFavoriteExtractor , base + "favourites"), ), ("gallery",)) @@ -538,6 +539,47 @@ def deviations(self): return self._folder_urls(folders, "gallery", DeviantartFolderExtractor) +class DeviantartAvatarExtractor(DeviantartExtractor): + """Extractor for an artist's avatar""" + subcategory = "avatar" + archive_fmt = "a_{_username}_{index}" + pattern = BASE_PATTERN + r"/avatar" + example = "https://www.deviantart.com/USER/avatar/" + + def deviations(self): + profile = self.api.user_profile(self.user.lower()) + if profile: + url = profile["user"]["usericon"] + return ({ + "author" : profile["user"], + "category" : "avatar", + "index" : text.parse_int(url.rpartition("?")[2]), + "is_deleted" : False, + "is_downloadable": False, + "published_time" : 0, + "title" : "avatar", + "content" : { + "src": url.replace("/avatars/", "/avatars-big/", 1), + }, + },) + return () + + +class DeviantartBackgroundExtractor(DeviantartExtractor): + """Extractor for an artist's banner""" + subcategory = "background" + archive_fmt = "b_{index}" + pattern = BASE_PATTERN + r"/ba(?:nner|ckground)" + example = "https://www.deviantart.com/USER/banner/" + + def deviations(self): + try: + return (self.api.user_profile(self.user.lower()) + ["cover_deviation"]["cover_deviation"],) + except Exception: + return () + + class DeviantartFolderExtractor(DeviantartExtractor): """Extractor for deviations inside an artist's gallery folder""" subcategory = "folder" diff --git a/test/results/deviantart.py b/test/results/deviantart.py index 4196f32c6f..45ee6c18c6 100644 --- a/test/results/deviantart.py +++ b/test/results/deviantart.py @@ -14,7 +14,7 @@ "#url" : "https://www.deviantart.com/shimoda7", "#category": ("", "deviantart", "user"), "#class" : deviantart.DeviantartUserExtractor, - "#pattern" : "/shimoda7/gallery$", + "#urls" : "https://www.deviantart.com/shimoda7/gallery", }, { @@ -22,8 +22,15 @@ "#category": ("", "deviantart", "user"), "#class" : deviantart.DeviantartUserExtractor, "#options" : {"include": "all"}, - "#pattern" : "/shimoda7/(gallery(/scraps)?|posts(/statuses)?|favourites)$", - "#count" : 5, + "#urls" : ( + "https://www.deviantart.com/shimoda7/avatar", + "https://www.deviantart.com/shimoda7/banner", + "https://www.deviantart.com/shimoda7/gallery", + "https://www.deviantart.com/shimoda7/gallery/scraps", + "https://www.deviantart.com/shimoda7/posts", + "https://www.deviantart.com/shimoda7/posts/statuses", + "https://www.deviantart.com/shimoda7/favourites", + ), }, { @@ -195,6 +202,93 @@ "#class" : deviantart.DeviantartGalleryExtractor, }, +{ + "#url" : "https://deviantart.com/shimoda7/avatar", + "#category": ("", "deviantart", "avatar"), + "#class" : deviantart.DeviantartAvatarExtractor, + "#urls" : "https://a.deviantart.net/avatars-big/s/h/shimoda7.jpg?4", + "#sha1_content": "abf2cc79b842315f2e54bfdd93bf794a0f612b6f", + + "author" : { + "type" : "premium", + "usericon": "https://a.deviantart.net/avatars/s/h/shimoda7.jpg?4", + "userid" : "9AE51FC7-0278-806C-3FFF-F4961ABF9E2B", + "username": "shimoda7", + }, + "content" : { + "src": "https://a.deviantart.net/avatars-big/s/h/shimoda7.jpg?4" + }, + "da_category" : "avatar", + "date" : "dt:1970-01-01 00:00:00", + "extension" : "jpg", + "filename" : "avatar_by_shimoda7-d4", + "index" : 4, + "index_base36" : "4", + "is_deleted" : False, + "is_downloadable": False, + "is_original" : True, + "published_time" : 0, + "target" : { + "extension": "jpg", + "filename" : "avatar_by_shimoda7-d4", + "src" : "https://a.deviantart.net/avatars-big/s/h/shimoda7.jpg?4" + }, + "title" : "avatar", + "username" : "shimoda7", +}, + +{ + "#url" : "https://deviantart.com/gdldev/banner", + "#category": ("", "deviantart", "background"), + "#class" : deviantart.DeviantartBackgroundExtractor, + "#pattern" : r"https://wixmp-\w+\.wixmp\.com/f/b042e0ae-a7ff-420b-a41a-b35503427360/dgntyqc-3deebb65-04b4-4085-992a-aa0c0e7e225d\.png\?token=ey[\w.-]+$", + "#sha1_content": "980eaa76ce515f1b6bef60dfadf26a5bbe9c583f", + + "allows_comments" : True, + "author" : { + "type" : "regular", + "usericon": "https://a.deviantart.net/avatars/g/d/gdldev.jpg?2", + "userid" : "1A12BA26-33C2-AA0A-7678-0B6DFBA7AC8E", + "username": "gdldev" + }, + "category_path" : "", + "content" : { + "filename" : "banner_by_gdldev_dgntyqc.png", + "filesize" : 84510, + "height" : 4000, + "src" : r"re:https://wixmp-\w+\.wixmp\.com/f/b042e0ae-a7ff-420b-a41a-b35503427360/dgntyqc-3deebb65-04b4-4085-992a-aa0c0e7e225d\.png\?token=ey[\w.-]+$", + "transparency": False, + "width" : 6400 + }, + "da_category" : "Uncategorized", + "date" : "dt:2024-01-02 21:16:06", + "deviationid" : "8C8D6B28-766A-DE21-7F7D-CE055C3BD50A", + "download_filesize": 84510, + "extension" : "png", + "filename" : "banner_by_gdldev-dgntyqc", + "index" : 1007488020, + "index_base36" : "gntyqc", + "is_blocked" : False, + "is_deleted" : False, + "is_downloadable" : True, + "is_favourited" : False, + "is_mature" : False, + "is_original" : True, + "is_published" : False, + "preview" : dict, + "printid" : None, + "published_time" : 1704230166, + "stats" : { + "comments" : 0, + "favourites": 0, + }, + "target" : dict, + "thumbs" : list, + "title" : "Banner", + "url" : "https://sta.sh/0198jippkeys", + "username" : "gdldev", +}, + { "#url" : "https://www.deviantart.com/shimoda7/gallery/722019/Miscellaneous", "#comment" : "user",