diff --git a/docs/supportedsites.md b/docs/supportedsites.md index 411a86de..5a627ebd 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -268,7 +268,7 @@ Consider all listed sites to potentially be NSFW. Discord https://discord.com/ - Channels, DMs, Messages, Servers + Channels, DMs, Messages, Servers, Server Assets diff --git a/gallery_dl/extractor/discord.py b/gallery_dl/extractor/discord.py index 54cc433e..8e0211b4 100644 --- a/gallery_dl/extractor/discord.py +++ b/gallery_dl/extractor/discord.py @@ -9,7 +9,6 @@ from .common import Extractor, Message from .. import text, exception - BASE_PATTERN = r"(?:https?://)?discord\.com" @@ -203,29 +202,47 @@ class DiscordExtractor(Extractor): def parse_server(self, server): self.server_metadata = { - "server": server["name"], + "server" : server["name"], "server_id": server["id"], - "server_files": [], - "owner_id": server["owner_id"] + "owner_id" : server["owner_id"], + "server_files": self.collect_server_assets_general(server), } - for icon_type, icon_path in ( - ("icon", "icons"), - ("banner", "banners"), - ("splash", "splashes"), - ("discovery_splash", "discovery-splashes") - ): - if server.get(icon_type): - self.server_metadata["server_files"].append({ - "url": (f"https://cdn.discordapp.com/{icon_path}/" - f"{self.server_metadata['server_id']}/" - f"{server[icon_type]}.png?size=4096"), - "filename": icon_type, - "extension": "png", - }) - return self.server_metadata + def collect_server_assets_general(self, server): + return [ + { + "url": (f"https://cdn.discordapp.com/{asset_path}/" + f"{server['id']}/{asset_id}.png?size=4096"), + "id" : f"{server['id']}/{asset_id}", + "label" : "general", + "name" : asset_type, + "filename" : asset_type, + "extension": "png", + } + for asset_type, asset_path in ( + ("icon" , "icons"), + ("banner", "banners"), + ("splash", "splashes"), + ("discovery_splash", "discovery-splashes") + ) + if (asset_id := server.get(asset_type)) + ] + + def collect_server_assets_type(self, server, asset_type): + return [ + { + **asset, + "url": (f"https://cdn.discordapp.com/{asset_type}/" + f"{asset['id']}.png?size=4096"), + "label" : asset_type, + "filename" : f"{asset['name']} ({asset['id']})", + "extension": "png", + } + for asset in assets + ] if (assets := server.get(asset_type)) else () + def build_server_and_channels(self, server_id): self.parse_server(self.api.get_server(server_id)) @@ -266,6 +283,38 @@ class DiscordMessageExtractor(DiscordExtractor): self.api.get_message(channel_id, message_id)) +class DiscordServerAssetsExtractor(DiscordExtractor): + subcategory = "server-assets" + filename_fmt = "{name} ({id}).{extension}" + directory_fmt = ["{category}", "{server_id}_{server}", "Assets"] + archive_fmt = "asset_{server_id}_{id}" + pattern = (BASE_PATTERN + + r"/channels/(\d+)/(?:assets?|files)(?:/([\w-]+))?/?$") + example = "https://discord.com/channels/1234567890/assets" + + def items(self): + server_id, asset_type = self.groups + server = self.api.get_server(server_id) + parsed = self.parse_server(server) + + if asset_type is None: + assets = [ + *self.collect_server_assets_general(server), + *self.collect_server_assets_type(server, "emojis"), + *self.collect_server_assets_type(server, "stickers"), + ] + elif asset_type == "general": + assets = self.collect_server_assets_general(server) + else: + assets = self.collect_server_assets_type(server, asset_type) + + parsed["count"] = len(assets) + yield Message.Directory, "", parsed + for asset in assets: + asset.update(parsed) + yield Message.Url, asset["url"], asset + + class DiscordServerExtractor(DiscordExtractor): subcategory = "server" pattern = BASE_PATTERN + r"/channels/(\d+)/?$" diff --git a/test/results/discord.py b/test/results/discord.py index 5b2350db..f3cbd1aa 100644 --- a/test/results/discord.py +++ b/test/results/discord.py @@ -73,4 +73,38 @@ __tests__ = ( "#class" : discord.DiscordDirectMessagesExtractor, }, +{ + "#url" : "https://discord.com/channels/403905762268545024/assets", + "#class" : discord.DiscordServerAssetsExtractor, + "#auth" : "token", + "#count" : range(380, 450), + + "name" : str, + "filename" : str, + "extension": "png", + "id" : str, + "label" : {"general", "emojis", "stickers"}, + "owner_id" : "699203962691256400", + "server" : "MangaDex", + "server_id": "403905762268545024", + "url" : str, +}, + +{ + "#url" : "https://discord.com/channels/403905762268545024/assets/general", + "#class" : discord.DiscordServerAssetsExtractor, + "#auth" : "token", + "#count" : 3, + + "name" : {"icon", "banner", "splash"}, + "filename" : {"icon", "banner", "splash"}, + "extension": "png", + "id" : str, + "label" : "general", + "owner_id" : "699203962691256400", + "server" : "MangaDex", + "server_id": "403905762268545024", + "url" : str, +}, + )