1+ from util import download
2+ import requests
3+ import re
4+ from pathlib import Path
5+ from urllib .parse import unquote , urljoin
6+ import concurrent .futures
7+
8+ API_POSTS = "https://fantia.jp/api/v1/posts/{}"
9+ API_FANCLUB = "https://fantia.jp/api/v1/fanclubs/{}" # Not used for now
10+ HTML_POSTLIST = "https://fantia.jp/fanclubs/{}/posts?page={}"
11+
12+ class FantiaDownloader :
13+
14+ def __init__ (self , fanclub , output , key ):
15+ super ().__init__ ()
16+ self .key = key
17+ self .fanclub = fanclub
18+ self .output = Path (output )
19+ self .skip_existing = True
20+
21+ def fetch (self , url ):
22+ return requests .get (url , cookies = {"_session_id" : self .key })
23+
24+ def downloadAll (self ):
25+ if not self .output .exists ():
26+ self .output .exists .mkdir (parents = True , exist_ok = True )
27+ existing_ids = [m [1 ] for f in self .output .iterdir () if (m := re .match (r'(\d+) *' , f .name ))]
28+ existing_ids = list (dict .fromkeys (existing_ids ))
29+ print (f'{ len (existing_ids )} IDs have already been downloaded.' )
30+
31+ page = 1
32+ results = []
33+
34+ while True :
35+ print (f"Attempting to fetch page { page } ..." )
36+ out = self .fetchGalleryPage (page )
37+ if out :
38+ results .extend (out )
39+ page += 1
40+ else :
41+ break
42+
43+ with concurrent .futures .ThreadPoolExecutor (max_workers = 10 ) as ex :
44+ for id in results :
45+ if self .skip_existing and id in existing_ids :
46+ print (f'{ id } is already downloaded. Skip.' )
47+ continue
48+ ex .submit (self .getPostPhotos , id )
49+
50+ def fetchGalleryPage (self , page ):
51+ url = HTML_POSTLIST .format (self .fanclub , page )
52+ r = self .fetch (url )
53+ r .encoding = 'utf-8'
54+ html = r .text
55+ return re .findall (r'\/posts\/(?P<id>[0-9]{1,8})' , html )
56+
57+ def getPostPhotos (self , id ):
58+ def getWebName (url ):
59+ name = unquote (url .split ('?' )[0 ].split ('/' )[- 1 ])
60+ return name .rsplit ('.' , 1 )
61+
62+ print (f'Fetching post { id } ...' )
63+ d = self .fetch (API_POSTS .format (id )).json ()
64+ with concurrent .futures .ThreadPoolExecutor (max_workers = 3 ) as ex :
65+ if thumb := d ['post' ].get ('thumb' , None ):
66+ img_url = thumb ['original' ]
67+ stem , ext = getWebName (img_url )
68+ ex .submit (download , img_url , filename = self .output / f'{ id } !cover.{ ext } ' , verbose = 1 )
69+ if post_contents := d ['post' ].get ('post_contents' , None ):
70+ for c in post_contents :
71+ cid = c ['id' ]
72+ if photos := c .get ('post_content_photos' , None ):
73+ for idx , p in enumerate (photos , 1 ):
74+ img_url = p ['url' ]['original' ]
75+ stem , ext = getWebName (img_url )
76+ stem = re .sub (r'^[0-9a-fA-F]{8}_(.+)$' , r'\1' , stem )
77+ stem = re .sub (r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}' , '' , stem )
78+ idx_string = '_' + str (idx ).zfill (len (str (len (photos )))) if len (photos ) > 1 else ''
79+ stem = f'{ id } { cid } { idx_string } { stem } ' .strip ()
80+ ex .submit (download , img_url , filename = self .output / f'{ stem } .{ ext } ' , verbose = 1 )
81+ if 'download_uri' in c : #TODO: needs testing
82+ dl_url = urljoin ('https://fantia.jp' , c ['download_uri' ]) + '?ofn=' + c ['filename' ]
83+ ex .submit (download , dl_url , prefix = id , save_path = self .output , verbose = 1 )
84+
85+ if __name__ == "__main__" :
86+ pass
0 commit comments