11# -*- coding: utf-8 -*-
22
3- # Copyright 2019-2025 Mike Fährmann
3+ # Copyright 2019-2026 Mike Fährmann
44#
55# This program is free software; you can redistribute it and/or modify
66# it under the terms of the GNU General Public License version 2 as
@@ -29,7 +29,8 @@ class WeiboExtractor(Extractor):
2929
3030 def __init__ (self , match ):
3131 Extractor .__init__ (self , match )
32- self ._prefix , self .user = match .groups ()
32+ self ._prefix = match [1 ]
33+ self .user = match [2 ]
3334
3435 def _init (self ):
3536 self .livephoto = self .config ("livephoto" , True )
@@ -199,26 +200,30 @@ def _status_by_id(self, status_id):
199200 f"?id={ status_id } &isGetLongText=true" )
200201 return self .request_json (url )
201202
203+ def _user (self , user ):
204+ url = (f"{ self .root } /ajax/profile/info?"
205+ f"{ 'screen_name' if self ._prefix == 'n' else 'custom' } ={ user } " )
206+ return self .request_json (url , interval = False )["data" ]["user" ]
207+
202208 def _user_id (self ):
203- if len (self .user ) >= 10 and self .user .isdecimal ():
204- return self .user [- 10 :]
209+ user = self .user
210+ if len (user ) >= 10 and user .isdecimal ():
211+ return user [- 10 :]
205212 else :
206- url = (f"{ self .root } /ajax/profile/info?"
207- f"{ 'screen_name' if self ._prefix == 'n' else 'custom' } ="
208- f"{ self .user } " )
209- return self .request_json (url )["data" ]["user" ]["idstr" ]
213+ return self ._user (user )["idstr" ]
210214
211- def _pagination (self , endpoint , params ):
215+ def _pagination (self , endpoint , params ,
216+ since_key = "sinceid" , subalbums = None ):
212217 url = f"{ self .root } /ajax{ endpoint } "
213218 headers = {
219+ "Accept" : "application/json, text/plain, */*" ,
214220 "X-Requested-With" : "XMLHttpRequest" ,
215221 "X-XSRF-TOKEN" : None ,
216222 "Referer" : f"{ self .root } /u/{ params ['uid' ]} " ,
217223 }
218224
219225 while True :
220226 response = self .request (url , params = params , headers = headers )
221- headers ["Accept" ] = "application/json, text/plain, */*"
222227 headers ["X-XSRF-TOKEN" ] = response .cookies .get ("XSRF-TOKEN" )
223228
224229 data = response .json ()
@@ -234,6 +239,10 @@ def _pagination(self, endpoint, params):
234239 except KeyError :
235240 return
236241
242+ if subalbums is not None :
243+ subalbums = None
244+ yield data .get ("album_list" ) or ()
245+
237246 yield from statuses
238247
239248 # videos, newvideo
@@ -244,8 +253,10 @@ def _pagination(self, endpoint, params):
244253 continue
245254
246255 # album
247- if since_id := data .get ("since_id" ):
248- params ["sinceid" ] = since_id
256+ if "since_id" in data :
257+ params [since_key ] = since_id = data ["since_id" ]
258+ if not since_id :
259+ return
249260 if "page" in params :
250261 params ["page" ] += 1
251262 continue
@@ -383,9 +394,33 @@ def statuses(self):
383394class WeiboAlbumExtractor (WeiboExtractor ):
384395 """Extractor for weibo 'album' listings"""
385396 subcategory = "album"
386- pattern = USER_PATTERN + r"\?tabtype=album"
397+ pattern = USER_PATTERN + r"\?tabtype=album(?:[:_-]([^&#]+))? "
387398 example = "https://weibo.com/USER?tabtype=album"
388399
400+ def items (self ):
401+ subalbum = self .groups [2 ]
402+
403+ if not subalbum and not self .config ("subalbums" , False ):
404+ return WeiboExtractor .items (self )
405+
406+ self .directory_fmt = ("{category}" , "{user[screen_name]}" ,
407+ "Album" , "{subalbum[pic_title]|''}" )
408+ self .filename_fmt = "{filename}.{extension}"
409+ self .archive_fmt = "{subalbum[pic_title]}_{pid}"
410+ return self .items_subalbum (subalbum )
411+
412+ def items_subalbum (self , subalbum ):
413+ user = self .kwdict ["user" ] = self ._user (self .user )
414+ base = self .root + "/ajax/common/download?pid="
415+
416+ for data , files in self .albums (user ["idstr" ], subalbum ):
417+ self .kwdict ["subalbum" ] = data
418+ yield Message .Directory , "" , {}
419+ for file in files :
420+ file ["filename" ] = file ["pid" ]
421+ file ["extension" ] = "jpg"
422+ yield Message .Url , base + file ["pid" ], file
423+
389424 def statuses (self ):
390425 endpoint = "/profile/getImageWall"
391426 params = {"uid" : self ._user_id ()}
@@ -401,6 +436,51 @@ def statuses(self):
401436 else :
402437 yield status
403438
439+ def albums (self , uid , subalbum ):
440+ endpoint = "/profile/getImageWall"
441+ params = {
442+ "uid" : uid ,
443+ "sinceid" : "0" ,
444+ "has_album" : "true" ,
445+ }
446+ album = self ._pagination (endpoint , params , subalbums = True )
447+ subalbums = next (album , ())
448+
449+ if not subalbum or subalbum == "0" :
450+ return (({}, album ),)
451+
452+ if subalbum == "all" :
453+ results = [
454+ (sub , self ._pagination_subalbum (uid , sub ))
455+ for sub in subalbums
456+ ]
457+ results .append (({}, album ))
458+ return results
459+
460+ if subalbum == "only" :
461+ return [
462+ (sub , self ._pagination_subalbum (uid , sub ))
463+ for sub in subalbums
464+ ]
465+
466+ if subalbum .isdecimal ():
467+ try :
468+ sub = subalbums [int (subalbum )- 1 ]
469+ except Exception :
470+ raise exception .NotFoundError ("subalbum" )
471+ else :
472+ subalbum = text .unquote (subalbum )
473+ for sub in subalbums :
474+ if sub ["pic_title" ] == subalbum :
475+ break
476+ else :
477+ raise exception .NotFoundError ("subalbum" )
478+ return ((sub , self ._pagination_subalbum (uid , sub )),)
479+
480+ def _pagination_subalbum (self , uid , sub ):
481+ params = {"uid" : uid , "containerid" : text .unquote (sub ["containerid" ])}
482+ return self ._pagination ("/profile/getAlbumDetail" , params , "since_id" )
483+
404484
405485class WeiboStatusExtractor (WeiboExtractor ):
406486 """Extractor for a weibo status"""
0 commit comments