Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/scripts/supportedsites.py
8768 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License version 2 as
6
# published by the Free Software Foundation.
7
8
"""Generate a Markdown document listing all supported sites"""
9
10
import os
11
import sys
12
import collections
13
14
import util
15
from gallery_dl import extractor
16
17
try:
18
from test import results
19
except ImportError:
20
results = None
21
22
23
CATEGORY_MAP = {
24
"2chan" : "Futaba Channel",
25
"35photo" : "35PHOTO",
26
"adultempire" : "Adult Empire",
27
"agnph" : "AGNPH",
28
"ahottie" : "AHottie",
29
"aibooru" : "AIBooru",
30
"allgirlbooru" : "All girl",
31
"ao3" : "Archive of Our Own",
32
"archivedmoe" : "Archived.Moe",
33
"archiveofsins" : "Archive of Sins",
34
"arena" : "Are.na",
35
"artstation" : "ArtStation",
36
"aryion" : "Eka's Portal",
37
"atfbooru" : "ATFBooru",
38
"atfforum" : "All The Fallen",
39
"azurlanewiki" : "Azur Lane Wiki",
40
"b4k" : "arch.b4k.dev",
41
"baraag" : "baraag",
42
"batoto" : "BATO.TO",
43
"bbc" : "BBC",
44
"booth" : "BOOTH",
45
"celebforum" : "celebforum",
46
"cfake" : "Celebrity Fakes",
47
"cien" : "Ci-en",
48
"cohost" : "cohost!",
49
"comedywildlifephoto": "Comedy Wildlife Photography Awards",
50
"comicvine" : "Comic Vine",
51
"cyberfile" : "CyberFile",
52
"dankefuerslesen": "Danke fürs Lesen",
53
"deviantart" : "DeviantArt",
54
"drawfriends" : "Draw Friends",
55
"dynastyscans" : "Dynasty Reader",
56
"e621" : "e621",
57
"e926" : "e926",
58
"e6ai" : "e6AI",
59
"erome" : "EroMe",
60
"eporner" : "EPORNER",
61
"everia" : "EVERIA.CLUB",
62
"e-hentai" : "E-Hentai",
63
"exhentai" : "ExHentai",
64
"fallenangels" : "Fallen Angels Scans",
65
"fanbox" : "pixivFANBOX",
66
"fappic" : "Fappic.com",
67
"fashionnova" : "Fashion Nova",
68
"fikfap" : "FikFap",
69
"fitnakedgirls" : "FitNakedGirls",
70
"furaffinity" : "Fur Affinity",
71
"furry34" : "Furry 34 com",
72
"girlswithmuscle": "Girls with Muscle",
73
"hatenablog" : "HatenaBlog",
74
"hbrowse" : "HBrowse",
75
"hdoujin" : "HDoujin Galleries",
76
"hentai2read" : "Hentai2Read",
77
"hentaicosplay" : "Hentai Cosplay",
78
"hentaienvy" : "HentaiEnvy",
79
"hentaiera" : "HentaiEra",
80
"hentaifoundry" : "Hentai Foundry",
81
"hentaifox" : "HentaiFox",
82
"hentaihand" : "HentaiHand",
83
"hentaihere" : "HentaiHere",
84
"hentaiimg" : "Hentai Image",
85
"hentainexus" : "HentaiNexus",
86
"hentairox" : "HentaiRox",
87
"hentaizap" : "HentaiZap",
88
"hiperdex" : "HiperDEX",
89
"hitomi" : "Hitomi.la",
90
"horne" : "horne",
91
"idolcomplex" : "Idol Complex",
92
"illusioncardsbooru": "Illusion Game Cards",
93
"imagebam" : "ImageBam",
94
"imagefap" : "ImageFap",
95
"imagepond" : "ImagePond",
96
"imagetwist" : "ImageTwist",
97
"imgadult" : "ImgAdult",
98
"imgbb" : "ImgBB",
99
"imgbox" : "imgbox",
100
"imagechest" : "ImageChest",
101
"imgdrive" : "ImgDrive.net",
102
"imgkiwi" : "IMG.Kiwi",
103
"imglike" : "Nude Celeb",
104
"imgpile" : "imgpile",
105
"imgpv" : "IMGPV",
106
"imgtaxi" : "ImgTaxi.com",
107
"imgth" : "imgth",
108
"imgur" : "imgur",
109
"imgwallet" : "ImgWallet.com",
110
"imhentai" : "IMHentai",
111
"imxto" : "IMX.to",
112
"joyreactor" : "JoyReactor",
113
"itchio" : "itch.io",
114
"jpgfish" : "JPG Fish",
115
"kabeuchi" : "かべうち",
116
"kaliscan" : "KaliScan",
117
"mangafire" : "MangaFire",
118
"mangafreak" : "MangaFreak",
119
"mangareader" : "MangaReader",
120
"mangataro" : "MangaTaro",
121
"s3ndpics" : "S3ND",
122
"schalenetwork" : "Schale Network",
123
"leakgallery" : "Leak Gallery",
124
"livedoor" : "livedoor Blog",
125
"lofter" : "LOFTER",
126
"ohpolly" : "Oh Polly",
127
"omgmiamiswimwear": "Omg Miami Swimwear",
128
"mangadex" : "MangaDex",
129
"mangafox" : "Manga Fox",
130
"mangahere" : "Manga Here",
131
"mangakakalot" : "MangaKakalot",
132
"mangatown" : "MangaTown",
133
"manganato" : "MangaNato",
134
"mangapark" : "MangaPark",
135
"mangaread" : "MangaRead",
136
"mariowiki" : "Super Mario Wiki",
137
"mastodon.social": "mastodon.social",
138
"mediawiki" : "MediaWiki",
139
"micmicidol" : "MIC MIC IDOL",
140
"myhentaigallery": "My Hentai Gallery",
141
"myportfolio" : "Adobe Portfolio",
142
"natomanga" : "MangaNato",
143
"naver-blog" : "Naver Blog",
144
"naver-chzzk" : "CHZZK",
145
"naver-webtoon" : "Naver Webtoon",
146
"nelomanga" : "MangaNelo",
147
"nhentai" : "nhentai",
148
"nijie" : "nijie",
149
"nozomi" : "Nozomi.la",
150
"nozrip" : "GaryC Booru",
151
"nsfwalbum" : "NSFWalbum.com",
152
"nudostar" : "NudoStar.TV",
153
"nudostarforum" : "NudoStar Forums",
154
"okporn" : "OK.PORN",
155
"paheal" : "Rule 34",
156
"photovogue" : "PhotoVogue",
157
"picstate" : "PicState",
158
"pidgiwiki" : "PidgiWiki",
159
"pixeldrain" : "pixeldrain",
160
"pixhost" : "PiXhost",
161
"pixiv" : "[pixiv]",
162
"pixiv-novel" : "[pixiv] Novels",
163
"pornimage" : "Porn Image",
164
"pornpics" : "PornPics.com",
165
"pornreactor" : "PornReactor",
166
"pornstarstube" : "PORNSTARS.TUBE",
167
"postimg" : "Postimages",
168
"readcomiconline": "Read Comic Online",
169
"rbt" : "RebeccaBlackTech",
170
"redgifs" : "RedGIFs",
171
"rozenarcana" : "Rozen Arcana",
172
"rule34" : "Rule 34",
173
"rule34hentai" : "Rule34Hentai",
174
"rule34us" : "Rule 34",
175
"rule34vault" : "R34 Vault",
176
"rule34xyz" : "Rule 34 XYZ",
177
"sankaku" : "Sankaku Channel",
178
"sankakucomplex" : "Sankaku Complex",
179
"seiga" : "Niconico Seiga",
180
"senmanga" : "Sen Manga",
181
"sensescans" : "Sense-Scans",
182
"sexcom" : "Sex.com",
183
"silverpic" : "SilverPic.com",
184
"simpcity" : "SimpCity Forums",
185
"simplyhentai" : "Simply Hentai",
186
"sizebooru" : "Size Booru",
187
"slickpic" : "SlickPic",
188
"slideshare" : "SlideShare",
189
"smugmug" : "SmugMug",
190
"socialmediagirlsforum": "Social Media Girls Forums",
191
"speakerdeck" : "Speaker Deck",
192
"steamgriddb" : "SteamGridDB",
193
"subscribestar" : "SubscribeStar",
194
"tbib" : "The Big ImageBoard",
195
"tcbscans" : "TCB Scans",
196
"tco" : "Twitter t.co",
197
"thatpervert" : "ThatPervert",
198
"thebarchive" : "The /b/ Archive",
199
"thecollection" : "The /co/llection",
200
"thecollectionS" : "The /co/llection",
201
"thefap" : "TheFap",
202
"thehentaiworld" : "The Hentai World",
203
"tiktok" : "TikTok",
204
"titsintops" : "Tits In Tops Forum",
205
"tmohentai" : "TMOHentai",
206
"tumblrgallery" : "TumblrGallery",
207
"turboimagehost" : "TurboImageHost.com",
208
"turbo" : "turbo.cr",
209
"vanillarock" : "もえぴりあ",
210
"vidyart2" : "/v/idyart2",
211
"vidyapics" : "Vidya Booru",
212
"vipr" : "Vipr.im",
213
"visuabusters" : "VISUABUSTERS",
214
"vk" : "VK",
215
"vsco" : "VSCO",
216
"wallpapercave" : "Wallpaper Cave",
217
"webmshare" : "webmshare",
218
"webtoons" : "WEBTOON",
219
"weebcentral" : "Weeb Central",
220
"weebdex" : "WeebDex",
221
"wikiart" : "WikiArt.org",
222
"wikigg" : "wiki.gg",
223
"wikimediacommons": "Wikimedia Commons",
224
"xbunkr" : "xBunkr",
225
"xhamster" : "xHamster",
226
"xvideos" : "XVideos",
227
"yandere" : "yande.re",
228
"yiffverse" : "Yiff verse",
229
"yourlesbians" : "YourLesbians",
230
}
231
232
SUBCATEGORY_MAP = {
233
"" : "",
234
"art" : "Art",
235
"audio" : "Audio",
236
"doujin" : "Doujin",
237
"home" : "Home Feed",
238
"image" : "individual Images",
239
"index" : "Site Index",
240
"info" : "User Profile Information",
241
"issue" : "Comic Issues",
242
"manga" : "Manga",
243
"media" : "Media Files",
244
"people" : "People",
245
"popular": "Popular Images",
246
"recent" : "Recent Images",
247
"saved" : "Saved Posts",
248
"search" : "Search Results",
249
"status" : "Images from Statuses",
250
"tag" : "Tag Searches",
251
"tweets" : "",
252
"user" : "User Profiles",
253
"watch" : "Watches",
254
"direct-messages": "DMs",
255
"following" : "Followed Users",
256
"related-pin" : "related Pins",
257
"related-board" : "",
258
259
"arcalive": {
260
"user": "User Posts",
261
},
262
"artstation": {
263
"artwork": "Artwork Listings",
264
"collections": "",
265
},
266
"audiochan": {
267
"audio": "Audios",
268
},
269
"bilibili": {
270
"user-articles-favorite": "User Article Favorites",
271
},
272
"bluesky": {
273
"posts": "",
274
},
275
"boosty": {
276
"feed": "Subscriptions Feed",
277
},
278
"booth": {
279
"category": "Item Categories",
280
},
281
"cfake": {
282
"created": "Created",
283
},
284
"civitai": {
285
"models": "Model Listings",
286
"images": "Image Listings",
287
"videos": "Video Listings",
288
"posts" : "Post Listings",
289
"search-models": "Model Searches",
290
"search-images": "Image Searches",
291
"user-images": ("User Images", "Image Reactions"),
292
"user-videos": ("User Videos", "Video Reactions"),
293
"generated": "Generated Files",
294
},
295
"coomer": {
296
"discord" : "",
297
"discord-server": "",
298
"posts" : "",
299
},
300
"cyberfile": {
301
"shared": "Shares",
302
},
303
"Danbooru": {
304
"favgroup": "Favorite Groups",
305
"random" : "Random Posts",
306
},
307
"desktopography": {
308
"site": "",
309
},
310
"deviantart": {
311
"stash" : "Sta.sh",
312
"status": "Status Updates",
313
"watch-posts": "",
314
},
315
"discord": {
316
"direct-message" : "",
317
},
318
"facebook": {
319
"photos" : "Profile Photos",
320
},
321
"fanbox": {
322
"supporting": "Supported User Feed",
323
"redirect" : "Pixiv Redirects",
324
},
325
"fansly": {
326
"lists": "Account Lists",
327
},
328
"fapello": {
329
"path": ["Videos", "Trending Posts", "Popular Videos", "Top Models"],
330
},
331
"furaffinity": {
332
"submissions": "New Submissions",
333
},
334
"hatenablog": {
335
"archive": "Archive",
336
"entry" : "Individual Posts",
337
},
338
"hentaifoundry": {
339
"story": "",
340
},
341
"imgur": {
342
"favorite-folder": "Favorites Folders",
343
"me": "Personal Posts",
344
},
345
"inkbunny": {
346
"unread": "Unread Submissions",
347
},
348
"instagram": {
349
"posts": "",
350
"tagged": "Tagged Posts",
351
"stories-tray": "Stories Home Tray",
352
},
353
"itaku": {
354
"posts": "",
355
},
356
"kemono": {
357
"discord" : "Discord Servers",
358
"discord-server": "",
359
"posts" : "",
360
},
361
"koofr": {
362
"shared": "Shared Links",
363
},
364
"leakgallery": {
365
"trending" : "Trending Medias",
366
"mostliked": "Most Liked Posts",
367
},
368
"lensdump": {
369
"albums": "",
370
},
371
"mangadex": {
372
"feed": "Updates Feed",
373
"following" : "Library",
374
"list": "MDLists",
375
},
376
"misskey": {
377
"notes": "User Notes",
378
},
379
"nijie": {
380
"followed": "Followed Users",
381
"nuita" : "Nuita History",
382
},
383
"pinterest": {
384
"board": "",
385
"pinit": "pin.it Links",
386
"created": "Created Pins",
387
"allpins": "All Pins",
388
},
389
"pixeldrain": {
390
"folder": "Filesystems",
391
},
392
"pixiv": {
393
"me" : "pixiv.me Links",
394
"pixivision": "pixivision",
395
"sketch": "Sketch",
396
"unlisted": "Unlisted Works",
397
"work": "individual Images",
398
},
399
"poringa": {
400
"post": "Posts Images",
401
},
402
"pornhub": {
403
"gifs": "",
404
},
405
"raddle": {
406
"usersubmissions": "User Profiles",
407
"post" : "Individual Posts",
408
"shorturl" : "",
409
},
410
"redgifs": {
411
"collections": "",
412
},
413
"sankaku": {
414
"books": "Book Searches",
415
},
416
"scrolller": {
417
"user" : "Reddit Users",
418
"following": "Followed Subreddits",
419
},
420
"sexcom": {
421
"pins": "User Pins",
422
"feed": "Feed",
423
},
424
"sizebooru": {
425
"user": "User Uploads",
426
},
427
"skeb": {
428
"following" : "Followed Creators",
429
"following-users": "Followed Users",
430
"sentrequests" : "Sent Requests",
431
},
432
"smugmug": {
433
"path": "Images from Users and Folders",
434
},
435
"steamgriddb": {
436
"asset": "Individual Assets",
437
},
438
"tiktok": {
439
"posts": "User Posts",
440
"vmpost": "VM Posts",
441
"following": "Followed Users (Stories Only)",
442
},
443
"tumblr": {
444
"day": "Days",
445
},
446
"twitter": {
447
"media": "Media Timelines",
448
"tweets": "",
449
"replies": "",
450
"community": "",
451
"list-members": "List Members",
452
},
453
"vk": {
454
"tagged": "Tagged Photos",
455
"wall-post": "individual Wall Posts",
456
},
457
"vsco": {
458
"spaces": "",
459
},
460
"wallhaven": {
461
"collections": "",
462
"uploads" : "",
463
},
464
"wallpapercave": {
465
"image": ["individual Images", "Search Results"],
466
},
467
"weasyl": {
468
"journals" : "",
469
"submissions": "",
470
},
471
"weibo": {
472
"home": "",
473
"newvideo": "",
474
},
475
"wikiart": {
476
"artists": "Artist Listings",
477
},
478
"wikimedia": {
479
"article": ["Articles", "Categories", "Files"],
480
},
481
"xenforo": {
482
"media-user": "User Media",
483
"media-item": "Media Files",
484
"media-category": "Media Categories",
485
"media-album" : "Albums",
486
},
487
}
488
489
BASE_MAP = {
490
"E621" : "e621 Instances",
491
"foolfuuka" : "FoolFuuka 4chan Archives",
492
"foolslide" : "FoOlSlide Instances",
493
"gelbooru_v01": "Gelbooru Beta 0.1.11",
494
"gelbooru_v02": "Gelbooru Beta 0.2",
495
"hentaicosplays": "Hentai Cosplay Instances",
496
"imagehost" : "Image Hosting Sites",
497
"IMHentai" : "IMHentai and Mirror Sites",
498
"jschan" : "jschan Imageboards",
499
"lolisafe" : "lolisafe and chibisafe",
500
"lynxchan" : "LynxChan Imageboards",
501
"manganelo" : "MangaNelo and Mirror Sites",
502
"moebooru" : "Moebooru and MyImouto",
503
"szurubooru" : "szurubooru Instances",
504
"urlshortener": "URL Shorteners",
505
"vichan" : "vichan Imageboards",
506
"xenforo" : "XenForo Forums",
507
}
508
509
URL_MAP = {
510
"blogspot" : "https://www.blogger.com/",
511
"wikimedia": "https://www.wikimedia.org/",
512
}
513
514
_OAUTH = '<a href="https://github.com/mikf/gallery-dl#oauth">OAuth</a>'
515
_COOKIES = '<a href="https://github.com/mikf/gallery-dl#cookies">Cookies</a>'
516
_APIKEY_DB = ('<a href="https://gdl-org.github.io/docs/configuration.html'
517
'#extractor-derpibooru-api-key">API Key</a>')
518
_APIKEY_WH = ('<a href="https://gdl-org.github.io/docs/configuration.html'
519
'#extractor-wallhaven-api-key">API Key</a>')
520
_APIKEY_WY = ('<a href="https://gdl-org.github.io/docs/configuration.html'
521
'#extractor-weasyl-api-key">API Key</a>')
522
523
AUTH_MAP = {
524
"aibooru" : "Supported",
525
"ao3" : "Supported",
526
"aryion" : "Supported",
527
"atfbooru" : "Supported",
528
"baraag" : _OAUTH,
529
"bluesky" : "Supported",
530
"booruvar" : "Supported",
531
"boosty" : _COOKIES,
532
"coomer" : "Supported",
533
"danbooru" : "Supported",
534
"derpibooru" : _APIKEY_DB,
535
"deviantart" : _OAUTH,
536
"e621" : "Supported",
537
"e6ai" : "Supported",
538
"e926" : "Supported",
539
"e-hentai" : "Supported",
540
"exhentai" : "Supported",
541
"facebook" : _COOKIES,
542
"fanbox" : _COOKIES,
543
"fantia" : _COOKIES,
544
"flickr" : _OAUTH,
545
"furaffinity" : _COOKIES,
546
"furbooru" : "API Key",
547
"girlswithmuscle": "Supported",
548
"horne" : "Required",
549
"idolcomplex" : "Supported",
550
"imgbb" : "Supported",
551
"inkbunny" : "Supported",
552
"instagram" : _COOKIES,
553
"iwara" : "Supported",
554
"kemono" : "Supported",
555
"madokami" : "Required",
556
"mangadex" : "Supported",
557
"mangoxo" : "Supported",
558
"mastodon.social": _OAUTH,
559
"newgrounds" : "Supported",
560
"nijie" : "Required",
561
"nudostarforum" : "Supported",
562
"patreon" : _COOKIES,
563
"pawoo" : _OAUTH,
564
"pillowfort" : "Supported",
565
"pinterest" : _COOKIES,
566
"pixiv" : _OAUTH,
567
"pixiv-novel" : _OAUTH,
568
"poipiku" : _COOKIES,
569
"ponybooru" : "API Key",
570
"reddit" : _OAUTH,
571
"rule34xyz" : "Supported",
572
"sankaku" : "Supported",
573
"scrolller" : "Supported",
574
"seiga" : "Supported",
575
"simpcity" : "Supported",
576
"smugmug" : _OAUTH,
577
"subscribestar" : "Supported",
578
"tapas" : "Supported",
579
"tiktok" : _COOKIES,
580
"tsumino" : "Supported",
581
"tumblr" : _OAUTH,
582
"twitter" : _COOKIES,
583
"vipergirls" : "Supported",
584
"wallhaven" : _APIKEY_WH,
585
"weasyl" : _APIKEY_WY,
586
"zerochan" : "Supported",
587
}
588
589
IGNORE_LIST = (
590
"directlink",
591
"oauth",
592
"recursive",
593
"test",
594
"ytdl",
595
"generic",
596
"noop",
597
)
598
599
600
def domain(cls):
601
"""Return the domain name associated with an extractor class"""
602
try:
603
url = sys.modules[cls.__module__].__doc__.split()[-1]
604
if url.startswith("http"):
605
return url
606
except Exception:
607
pass
608
609
if hasattr(cls, "root") and cls.root:
610
return cls.root + "/"
611
612
url = cls.example
613
return url[:url.find("/", 8)+1]
614
615
616
def category_text(c):
617
"""Return a human-readable representation of a category"""
618
return CATEGORY_MAP.get(c) or c.capitalize()
619
620
621
def subcategory_text(bc, c, sc):
622
"""Return a human-readable representation of a subcategory"""
623
if c in SUBCATEGORY_MAP:
624
scm = SUBCATEGORY_MAP[c]
625
if sc in scm:
626
txt = scm[sc]
627
if not isinstance(txt, str):
628
txt = ", ".join(txt)
629
return txt
630
631
if bc and bc in SUBCATEGORY_MAP:
632
scm = SUBCATEGORY_MAP[bc]
633
if sc in scm:
634
txt = scm[sc]
635
if not isinstance(txt, str):
636
txt = ", ".join(txt)
637
return txt
638
639
if sc in SUBCATEGORY_MAP:
640
return SUBCATEGORY_MAP[sc]
641
642
if "-" in sc:
643
sc = " ".join(s.capitalize() for s in sc.split("-"))
644
else:
645
sc = sc.capitalize()
646
647
if sc.endswith("y"):
648
sc = f"{sc[:-1]}ies"
649
elif sc.endswith("h"):
650
sc = f"{sc}es"
651
elif not sc.endswith("s") and not sc.endswith("edia"):
652
sc = f"{sc}s"
653
return sc
654
655
656
def category_key(c):
657
"""Generate sorting keys by category"""
658
return category_text(c[0]).lower().lstrip("[")
659
660
661
def subcategory_key(sc):
662
"""Generate sorting keys by subcategory"""
663
return "A" if sc == "issue" else sc
664
665
666
def build_extractor_list():
667
"""Generate a sorted list of lists of extractor classes"""
668
categories = collections.defaultdict(lambda: collections.defaultdict(list))
669
default = categories[""]
670
domains = {"": ""}
671
672
for extr in extractor._list_classes():
673
category = extr.category
674
if category in IGNORE_LIST:
675
continue
676
if category:
677
if extr.basecategory == "imagehost":
678
base = categories[extr.basecategory]
679
else:
680
base = default
681
base[category].append(extr.subcategory)
682
if category not in domains:
683
domains[category] = domain(extr)
684
else:
685
base = categories[extr.basecategory]
686
if not extr.instances:
687
base[""].append(extr.subcategory)
688
continue
689
for category, root, info in extr.instances:
690
base[category].append(extr.subcategory)
691
if category not in domains:
692
if not root:
693
if category in URL_MAP:
694
root = URL_MAP[category].rstrip("/")
695
elif results:
696
# use domain from first matching test
697
test = results.category(category)[0]
698
root = test["#class"].from_url(test["#url"]).root
699
domains[category] = root + "/"
700
701
# sort subcategory lists
702
for base in categories.values():
703
for subcategories in base.values():
704
subcategories.sort(key=subcategory_key)
705
706
domains["pixiv-novel"] += "novel"
707
708
# add e-hentai.org
709
default["e-hentai"] = default["exhentai"]
710
domains["e-hentai"] = domains["exhentai"].replace("x", "-")
711
712
# add coomer.st
713
default["coomer"] = default["kemono"]
714
domains["coomer"] = "https://coomer.st/"
715
716
# add wikifeetx.com
717
default["wikifeetx"] = default["wikifeet"]
718
domains["wikifeetx"] = "https://www.wikifeetx.com/"
719
720
# imgdrive / imgtaxi / imgwallet
721
base = categories["imagehost"]
722
base["imgtaxi"] = base["imgdrive"]
723
base["imgwallet"] = base["imgdrive"]
724
categories["imagehost"] = {k: base[k] for k in sorted(base)}
725
domains["postimg"] = "https://postimages.org/"
726
domains["imgtaxi"] = "https://imgtaxi.com/"
727
domains["imgwallet"] = "https://imgwallet.com/"
728
729
# add extra e621 extractors
730
categories["E621"]["e621"].extend(default.pop("e621", ()))
731
732
return categories, domains
733
734
735
# define table columns
736
COLUMNS = (
737
("Site", 20,
738
lambda bc, c, scs, d: category_text(c)),
739
("URL" , 35,
740
lambda bc, c, scs, d: d),
741
("Capabilities", 50,
742
lambda bc, c, scs, d: ", ".join(subcategory_text(bc, c, sc) for sc in scs
743
if subcategory_text(bc, c, sc))),
744
("Authentication", 16,
745
lambda bc, c, scs, d: AUTH_MAP.get(c, "")),
746
)
747
748
749
def generate_output(columns, categories, domains):
750
751
thead = []
752
thead.append("<tr>")
753
for column in columns:
754
thead.append(f" <th>{column[0]}</th>")
755
thead.append("</tr>")
756
757
tbody = []
758
for bcat, base in categories.items():
759
if bcat and base:
760
name = BASE_MAP.get(bcat) or (bcat.capitalize() + " Instances")
761
tbody.append(f"""
762
<tr id="{bcat}" title="{bcat}">
763
<td colspan="4"><strong>{name}</strong></td>
764
</tr>\
765
""")
766
clist = base.items()
767
else:
768
clist = sorted(base.items(), key=category_key)
769
770
for category, subcategories in clist:
771
tbody.append(f"""<tr id="{category}" title="{category}">""")
772
for column in columns:
773
domain = domains[category]
774
content = column[2](bcat, category, subcategories, domain)
775
tbody.append(f" <td>{content}</td>")
776
tbody.append("</tr>")
777
778
NL = "\n"
779
GENERATOR = "/".join(os.path.normpath(__file__).split(os.sep)[-2:])
780
return f"""\
781
# Supported Sites
782
783
<!-- auto-generated by {GENERATOR} -->
784
Consider all listed sites to potentially be NSFW.
785
786
<table>
787
<thead valign="bottom">
788
{NL.join(thead)}
789
</thead>
790
<tbody valign="top">
791
{NL.join(tbody)}
792
</tbody>
793
</table>
794
"""
795
796
797
def main(path=None):
798
categories, domains = build_extractor_list()
799
800
if path is None:
801
path = util.path("docs", "supportedsites.md")
802
with util.lazy(path) as fp:
803
fp.write(generate_output(COLUMNS, categories, domains))
804
805
806
if __name__ == "__main__":
807
main(sys.argv[1] if len(sys.argv) > 1 else None)
808
809