from Crypto.Cipher import AES from base.spider import Spider import re,sys,json,base64,requests from Crypto.Util.Padding import unpad from urllib.parse import quote, unquote, urljoin sys.path.append('..') class Spider(Spider): headers = { 'User-Agent': "Mozilla/5.0 (Linux; Android 12; SM-S9080 Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36 uni-app Html5Plus/1.0 (Immersed/0.6666667)", 'Connection': "Keep-Alive", 'Accept-Encoding': "gzip", 'Version': "1.3.26", 'Token': "" } play_headers = {'User-Agent': 'io.dcloud.application.DCloudApplication/1.3.26 (Linux;Android 12)'} host, datakey, dataiv, deviceid, home_class, block_id,bn = '', '', '', '', '',[],b'\xe6\x83\x85\xe8\x89\xb2' def init(self, extend=""): try: config = json.loads(extend) except (json.JSONDecodeError, TypeError): config = {} self.host = config.get("host", "https://58api.zggggs.com") self.datakey = config.get("datakey", "58928cae68092afc") self.dataiv = config.get("dataiv", "e9d732a1edcdcc0a") self.deviceid = config.get("deviceid", "d60ddbcd469741f68e2755dca38f5171") payload = { 'UserId': "0", 'device_id': self.host } response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_config2', data=payload, headers=self.headers).json() data = self.decrypt(response['data']) data2 = json.loads(data) block_id = [] for i in data2['viphome']: block_id.append(i['id']) self.block_id = block_id self.home_class = data2['home'] def homeContent(self, filter): home_class = self.home_class classes = [] for i in home_class: if i['id'] == 0: continue classes.append({'type_id':i['id'],'type_name':i['title']}) return {'class': classes} def homeVideoContent(self): payload = { 'UserId': "0", 'device_id': self.deviceid, 'Id': "0", 'Type': "1", 'Page': "1", 'Limit': "10" } response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_vod_list2', data=payload, headers=self.headers).json() data = self.decrypt(response['data']) data2 = json.loads(data) vods = [] for i in data2['sections']: vods.extend(i['vods']) vods.extend(data2['vods']) videos = [] for i in vods: if i['type_id'] in self.block_id or i['group_id'] != 0 or self.bn.decode('utf-8') in i['vod_class']: continue vod_pic = i.get('vod_pic') if vod_pic.startswith('mac://'): vod_pic = vod_pic.replace('mac://', 'https://', 1) videos.append({ 'vod_id': i.get('vod_id'), 'vod_name': i.get('vod_name'), 'vod_class': i.get('vod_class'), 'vod_pic': vod_pic, 'vod_remarks': i.get('vod_remarks'), 'vod_score': i.get('vod_score') }) return {'list': videos} def detailContent(self, ids): payload = { 'UserId': "0", 'device_id': self.deviceid, 'id': ids } data = self.post(f"{self.host}/addons/appto/app.php/tindex/page_player", data=payload, headers=self.headers).json() data2 = self.decrypt(data['data']) data3 = json.loads(data2) if data3['type_id'] in self.block_id: return {'list': []} if not data3['group_id'] == 0: return {'list': []} videos = [] videos.append({ 'vod_id': data3.get('vod_id'), 'vod_name': data3.get('vod_name'), 'vod_content': data3.get('vod_blurb'), 'vod_remarks': data3.get('vod_serial'), 'vod_year': data3.get('vod_year'), 'vod_area': data3.get('vod_area'), 'vod_play_from': '58视频', 'vod_play_url': data3['vod_play_url'] }) return {'list': videos} def searchContent(self, key, quick, pg="1"): url = f"{self.host}/addons/appto/app.php/tindex/search_film" videos = [] type_list = {'film','short'} for search_type in type_list: payload = { 'UserId': "0", 'device_id': self.deviceid, 'Search': key, 'type': search_type, 'Page': pg, 'Limit': "10" } response = self.post(url, data=payload, headers=self.headers).json() data = self.decrypt(response['data']) vods =json.loads(data)['vods'] for i in vods['list']: if i['type_id'] in self.block_id or self.bn.decode('utf-8') in i['vod_class'] or b'\xe4\xbc\x9a\xe5\x91\x98'.decode('utf-8') in i['vod_type_name']: continue vod_pic = i['vod_pic'] if vod_pic.startswith('mac://'): vod_pic = vod_pic.replace('mac://', 'https://', 1) video = { "vod_id": i['vod_id'], "vod_name": i['vod_name'], "vod_class": i['vod_class'], "vod_pic": vod_pic, "vod_remarks": i['vod_remarks'] } videos.append(video) return {'list': videos, 'page': pg, 'limit': vods['limit'], 'total': vods['total']} def categoryContent(self, tid, pg, filter, extend): payload = { 'UserId': "0", 'device_id': self.host, 'Id': tid, 'Type': "1", 'Page': pg, 'Limit': "10" } response = self.post(f'{self.host}/addons/appto/app.php/tindex/home_vod_list2', data=payload,headers=self.headers).json() data = self.decrypt(response['data']) data2 = json.loads(data) videos = [] for i in data2['vods']: if 'payload' in i or 'banner' in i['vod_class']: continue vod_pic = i.get('vod_pic') if vod_pic.startswith('mac://'): vod_pic = vod_pic.replace('mac://', 'https://', 1) videos.append({ 'vod_id': i.get('vod_id'), 'vod_name': i.get('vod_name'), 'vod_class': i.get('vod_class'), 'vod_pic': vod_pic, 'vod_score':i.get('vod_score'), 'vod_remarks': i.get('vod_remarks'), 'vod_score': i.get('vod_score') }) return {'list': videos} def playerContent(self, flag, id, vipFlags): if '.m3u8' in id: try: proxyurl = f'{self.getProxyUrl(True)}&type=58sp' except Exception: proxyurl = 'http://127.0.0.1:9978/proxy?do=py&type=58sp' url = f"{proxyurl}&url={quote(id,safe='')}" return {'jx': 0, 'playUrl': '', 'parse': 0, 'url': url,'header': self.play_headers} def proxy58sp(self, params): url = unquote(params['url']) data = self.modify_m3u8(url) return [200, "application/vnd.apple.mpegurl", data] def decrypt(self,ciphertext): try: ciphertext = base64.b64decode(ciphertext) key_bytes = self.datakey.encode('utf-8') iv_bytes = self.dataiv.encode('utf-8') cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes) decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size) return decrypted_data.decode('utf-8') except Exception as e: return None def modify_m3u8(self, url, retries=3, timeout=10): current_url = url while True: try: for attempt in range(retries): try: response = requests.get(current_url, timeout=timeout,headers=self.play_headers) response.raise_for_status() content = response.text break except (requests.RequestException, ValueError) as e: if attempt == retries - 1: raise Exception(f"请求失败: {str(e)}") print(f"请求尝试 {attempt + 1}/{retries} 失败,正在重试...") base_url = current_url.rsplit('/', 1)[0] + '/' lines = content.strip().split('\n') is_master_playlist = any(line.startswith('#EXT-X-STREAM-INF:') for line in lines) if is_master_playlist: highest_bandwidth = 0 best_playlist_url = None bandwidth_regex = re.compile(r'BANDWIDTH=(\d+)') for i, line in enumerate(lines): if line.startswith('#EXT-X-STREAM-INF:'): match = bandwidth_regex.search(line) if match: bandwidth = int(match.group(1)) if i + 1 < len(lines) and not lines[i + 1].startswith('#'): playlist_url = lines[i + 1].strip() if not playlist_url.startswith(('http:', 'https:')): playlist_url = urljoin(base_url, playlist_url) if bandwidth > highest_bandwidth: highest_bandwidth = bandwidth best_playlist_url = playlist_url if best_playlist_url: print(f"选择最高清晰度流: {highest_bandwidth}bps") current_url = best_playlist_url continue else: raise Exception("未找到有效的子播放列表") key_regex = re.compile(r'#EXT-X-KEY:(.*)URI="([^"]+)"(.*)') segment_regex = re.compile(r'#EXTINF:([\d.]+),') m3u8_output = [] first_segment_index = -1 segment_durations = [] segment_indices = [] for i, line in enumerate(lines): if line.startswith('#EXTINF:'): match = segment_regex.search(line) if match: duration = float(match.group(1)) segment_durations.append(duration) segment_indices.append(i) modified_remove_start_indices = [] if len(segment_durations) >= 2: second_duration_str = "{0:.3f}".format(segment_durations[1]) if second_duration_str.endswith('67'): print(f"第2个分片({second_duration_str})符合规则,将删除前2个分片") modified_remove_start_indices = segment_indices[:2] elif len(segment_durations) >= 3: third_duration_str = "{0:.3f}".format(segment_durations[2]) if third_duration_str.endswith('67'): print(f"第3个分片({third_duration_str})符合规则,将删除前3个分片") modified_remove_start_indices = segment_indices[:3] lines_to_remove = set() for seg_idx in modified_remove_start_indices: lines_to_remove.add(seg_idx) if seg_idx + 1 < len(lines): lines_to_remove.add(seg_idx + 1) for i, line in enumerate(lines): line = line.strip() if not line: continue if line.startswith('#EXT-X-KEY:'): match = key_regex.search(line) if match: prefix = match.group(1) key_url = match.group(2) suffix = match.group(3) if not key_url.startswith(('http:', 'https:')): key_url = urljoin(base_url, key_url) updated_key_line = f'#EXT-X-KEY:{prefix}URI="{key_url}"{suffix}' m3u8_output.append(updated_key_line) print(f"补全加密KEY URL: {key_url}") continue if i in lines_to_remove: print(f"移除行: {line}") continue if not line.startswith('#') and i > first_segment_index: segment_url = line if not segment_url.startswith(('http:', 'https:')): segment_url = urljoin(base_url, segment_url) m3u8_output.append(segment_url) else: m3u8_output.append(line) if first_segment_index == -1 and line.startswith('#EXTINF:'): first_segment_index = i if not any(not line.startswith('#') for line in m3u8_output): raise Exception("未找到TS片段") if not m3u8_output or not m3u8_output[0].startswith('#EXTM3U'): m3u8_output.insert(0, '#EXTM3U') return '\n'.join(m3u8_output) except Exception as e: return f"#EXTM3U\n#EXT-X-ERROR:{str(e)}" def localProxy(self, params): if params['type'] == "58sp": return self.proxy58sp(params) return None def getName(self): pass def isVideoFormat(self, url): pass def manualVideoCheck(self): pass def destroy(self): pass