from spider import Spider, SpiderEpisode from cache import get_cache, set_cache from proxy import get_proxy_func_url, ProxyFuncResult from downloader import get_download_url import requests import re import json import time import xbmcaddon _ADDON = xbmcaddon.Addon() base_headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36', 'Referer': 'https://www.aliyundrive.com/', } class SpiderAliyunDrive(Spider): setting_key_refresh_token = 'aliyundrive_refresh_token' setting_key_display_file_size_switch = 'aliyundrive_display_file_size_switch' setting_key_downloder_switch = 'downloader_switch' regex_share_id = re.compile( r'www.aliyundrive.com\/s\/([^\/]+)(\/folder\/([^\/]+))?') cache = {} def name(self): return '阿里云盘' def is_searchable(self): return False def hide(self): return True def list_categories(self): return [] def list_videos(self, category_id, page): return [], False def list_episodes(self, video_id): m = self.regex_share_id.search(video_id) share_id = m.group(1) file_id = m.group(3) r = requests.post( 'https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous', json={'share_id': share_id}) share_info = r.json() if len(share_info['file_infos']) == 0: return [] file_info = None if file_id: for fi in share_info['file_infos']: if fi['file_id'] == file_id: file_info = fi break if file_info is None: return [] else: file_info = share_info['file_infos'][0] file_id = file_info['file_id'] parent_file_id = None if file_info['type'] == 'folder': parent_file_id = file_id elif file_info['type'] == 'file' and file_info['category'] == 'video': parent_file_id = 'root' else: return [] share_token = self._get_share_token(share_id) video_file_infos = [] subtitle_file_infos = [] self._list_files(video_file_infos, subtitle_file_infos, share_id, share_token, parent_file_id) video_file_infos.sort(key=lambda x: x['name']) subtitles = [] for file_info in subtitle_file_infos: subtitles.append({ 'name': file_info['name'], 'url': get_proxy_func_url( SpiderAliyunDrive.__name__, self.proxy_download_url.__name__, { 'share_id': file_info['share_id'], 'file_id': file_info['file_id'], 'drive_id': file_info['drive_id'], }, ) }) episodes = [] display_file_size = _ADDON.getSettingBool( self.setting_key_display_file_size_switch) for file_info in video_file_infos: if display_file_size: name = '[{}] {}'.format( self._sizeof_fmt(file_info['size']), file_info['name'], ) else: name = file_info['name'] sources = [{ 'name': '原画', 'params': { 'template_id': '', 'share_id': file_info['share_id'], 'file_id': file_info['file_id'], 'drive_id': file_info['drive_id'], }, }, { 'name': '超高清', 'params': { 'template_id': 'FHD', 'share_id': file_info['share_id'], 'file_id': file_info['file_id'], 'drive_id': file_info['drive_id'], }, }, { 'name': '高清', 'params': { 'template_id': 'HD', 'share_id': file_info['share_id'], 'file_id': file_info['file_id'], 'drive_id': file_info['drive_id'], }, }, { 'name': '标清', 'params': { 'template_id': 'SD', 'share_id': file_info['share_id'], 'file_id': file_info['file_id'], 'drive_id': file_info['drive_id'], }, }] episodes.append( SpiderEpisode( name=name, sources=sources, cover=share_info['avatar'], description=video_id, subtitles=subtitles, )) return episodes def resolve_play_url(self, episode_params): if len(episode_params['template_id']) == 0: downloader_switch = _ADDON.getSettingBool( self.setting_key_downloder_switch) if downloader_switch: return get_download_url( spider_class=SpiderAliyunDrive.__name__, func_name=self.proxy_download_url.__name__, params=episode_params) else: return get_proxy_func_url(SpiderAliyunDrive.__name__, self.proxy_download_url.__name__, episode_params) else: return get_proxy_func_url(SpiderAliyunDrive.__name__, self.proxy_preview_m3u8.__name__, episode_params) def search_videos(self, keyword): return [] def _get_refresh_token(self): token_or_url = _ADDON.getSettingString(self.setting_key_refresh_token) if token_or_url.startswith('http://') or token_or_url.startswith( 'https://'): return requests.get(token_or_url).text.strip() else: return token_or_url def _get_access_token(self): key = 'aliyundrive:access_token' data = self._get_cache(key) if data: return data['access_token'] r = requests.post('https://api.aliyundrive.com/token/refresh', json={ 'refresh_token': self._get_refresh_token(), }) data = r.json() access_token = '{} {}'.format(data['token_type'], data['access_token']) expires_at = int(time.time()) + int(data['expires_in'] / 2) self._set_cache(key, { 'access_token': access_token, 'expires_at': expires_at }) return access_token def _get_share_token(self, share_id, share_pwd=''): key = 'aliyundrive:share_token' data = self._get_cache(key) if data: if data['share_id'] == share_id and data['share_pwd'] == share_pwd: return data['share_token'] r = requests.post( 'https://api.aliyundrive.com/v2/share_link/get_share_token', json={ 'share_id': share_id, 'share_pwd': share_pwd }) data = r.json() share_token = data['share_token'] expires_at = int(time.time()) + int(data['expires_in'] / 2) self._set_cache( key, { 'share_token': share_token, 'expires_at': expires_at, 'share_id': share_id, 'share_pwd': share_pwd }) return share_token def _list_files(self, video_file_infos, subtitle_file_infos, share_id, share_token, parent_file_id): marker = '' headers = base_headers.copy() headers['x-share-token'] = share_token for page in range(1, 51): if page >= 2 and len(marker) == 0: break r = requests.post( 'https://api.aliyundrive.com/adrive/v3/file/list', json={ "image_thumbnail_process": "image/resize,w_160/format,jpeg", "image_url_process": "image/resize,w_1920/format,jpeg", "limit": 200, "order_by": "updated_at", "order_direction": "DESC", "parent_file_id": parent_file_id, "share_id": share_id, "video_thumbnail_process": "video/snapshot,t_1000,f_jpg,ar_auto,w_300", 'marker': marker, }, headers=headers) data = r.json() for item in data['items']: if item['type'] == 'folder': self._list_files(video_file_infos, subtitle_file_infos, share_id, share_token, item['file_id']) elif item['type'] == 'file' and item['category'] == 'video': video_file_infos.append(item) elif item['type'] == 'file' and item['file_extension'] in [ 'srt', 'ass', 'nfo', 'vtt' ]: subtitle_file_infos.append(item) marker = data['next_marker'] def _get_m3u8_cache(self, share_id, file_id, template_id): key = 'aliyundrive:m3u8' data = self._get_cache(key) if data: if data['share_id'] == share_id and data[ 'file_id'] == file_id and data[ 'template_id'] == template_id: return data['m3u8'], data['media_urls'] access_token = self._get_access_token() share_token = self._get_share_token(share_id) headers = base_headers.copy() headers['x-share-token'] = share_token headers['Authorization'] = access_token r = requests.post( 'https://api.aliyundrive.com/v2/file/get_share_link_video_preview_play_info', json={ 'share_id': share_id, 'category': 'live_transcoding', 'file_id': file_id, 'template_id': '', }, headers=headers, ) preview_url = '' for t in r.json( )['video_preview_play_info']['live_transcoding_task_list']: if t['template_id'] == template_id: preview_url = t['url'] break r = requests.get(preview_url, headers=base_headers.copy(), allow_redirects=False) preview_url = r.headers['Location'] lines = [] media_urls = [] r = requests.get(preview_url, headers=base_headers.copy(), stream=True) media_id = 0 for line in r.iter_lines(): line = line.decode() if 'x-oss-expires' in line: media_url = preview_url[:preview_url.rindex('/') + 1] + line media_urls.append(media_url) line = get_proxy_func_url( SpiderAliyunDrive.__name__, self.proxy_preview_media.__name__, { 'share_id': share_id, 'file_id': file_id, 'template_id': template_id, 'media_id': media_id }) media_id += 1 lines.append(line) m3u8 = '\n'.join(lines) self._set_cache( key, { 'share_id': share_id, 'file_id': file_id, 'template_id': template_id, 'm3u8': m3u8, 'media_urls': media_urls, 'expires_at': int(time.time()) + 300, }) return m3u8, media_urls def proxy_preview_m3u8(self, params): share_id = params['share_id'] file_id = params['file_id'] template_id = params['template_id'] m3u8, _ = self._get_m3u8_cache(share_id, file_id, template_id) return ProxyFuncResult( body=m3u8, headers={'Content-Type': 'application/vnd.apple.mpegurl'}) def proxy_preview_media(self, params): share_id = params['share_id'] file_id = params['file_id'] template_id = params['template_id'] media_id = params['media_id'] _, media_urls = self._get_m3u8_cache(share_id, file_id, template_id) media_url = media_urls[media_id] return ProxyFuncResult(url=media_url, headers=base_headers.copy()) def proxy_download_url(self, params): share_id = params['share_id'] file_id = params['file_id'] key = 'aliyundrive:download_url:{}:{}'.format(share_id, file_id) data = self._get_cache(key) if data: return ProxyFuncResult(url=data['download_url'], headers=base_headers.copy()) access_token = self._get_access_token() share_token = self._get_share_token(share_id) headers = base_headers.copy() headers['x-share-token'] = share_token headers['Authorization'] = access_token r = requests.post( 'https://api.aliyundrive.com/v2/file/get_share_link_download_url', json={ 'share_id': share_id, 'file_id': file_id, 'expires_sec': 7200, }, headers=headers) data = r.json() r = requests.get(data['download_url'], headers=base_headers.copy(), allow_redirects=False) download_url = r.headers['Location'] self._set_cache( key, { 'download_url': download_url, 'expires_at': int(time.time()) + 300, 'share_id': share_id, 'file_id': file_id, }) return ProxyFuncResult(url=download_url, headers=base_headers.copy()) def _sizeof_fmt(self, num, suffix="B"): for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: if num < 1024.0: return f"{num:3.1f} {unit}{suffix}" num /= 1024.0 return f"{num:.1f}Yi{suffix}" def _get_cache(self, key): if key in self.cache: data = self.cache[key] if data['expires_at'] >= int(time.time()): return data data = get_cache(key) if data: data = json.loads(data) if data['expires_at'] >= int(time.time()): return data return None def _set_cache(self, key, value): set_cache(key, json.dumps(value)) self.cache[key] = value