import json, re, requests, threading, base64, random from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from urllib.parse import urlparse, quote from pyquery import PyQuery as pq from base.spider import Spider class Spider(Spider): headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', 'sec-ch-ua-platform': '"Windows"', 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="141", "Google Chrome";v="141"', 'origin': 'https://456movie.net', 'referer': 'https://456movie.net/'} def init(self, extend=""): self.site = 'https://456movie.net' self.chost, self.token = self.gettoken() self.phost = 'https://image.tmdb.org/t/p/w500' self.servers = {'vidlink': 'https://vidlink.pro', 'vidfast': 'https://vidfast.pro', '111movies': 'https://111movies.com', 'vidrock': 'https://vidrock.net', 'vidzee': 'https://player.vidzee.wtf'} self.server_order = ['vidfast','vidlink', '111movies', 'vidrock', 'vidzee'] self.headers.update({'origin': self.site, 'referer': f'{self.site}/', 'accept': 'application/json'}) self._111movies_key = bytes([1, 157, 45, 74, 228, 243, 24, 124, 194, 12, 184, 70, 3, 93, 102, 187, 254, 72, 230, 97, 57, 129, 254, 216, 223, 113, 82, 42, 62, 208, 244, 63]) self._111movies_iv = bytes([147, 233, 144, 118, 246, 33, 110, 119, 13, 209, 140, 42, 32, 186, 47, 89]) self._111movies_xkey = bytes([238, 123, 35, 56, 43, 184, 57, 233, 233, 41]) self.vidzee_key_hex = '6966796f75736372617065796f75726179676179000000000000000000000000' self.jx = 'https://111movies.com' def getName(self): return "Movies" def isVideoFormat(self, url): return '.m3u8' in url or '.mp4' in url def manualVideoCheck(self): return True def destroy(self): pass def homeContent(self, filter): cate = {"电影": "movie", "剧集": "tv"} return {'class': [{'type_name': k, 'type_id': v} for k, v in cate.items()], 'filters': {}} def homeVideoContent(self): data = self.fetch(f"{self.chost}/trending/all/week", params={'api_key': self.token, 'language': 'zh-CN', 'page': 1}, headers=self.headers).json() return {'list': self.getlist(data['results'])} def categoryContent(self, tid, pg, filter, extend): params = {'page': pg, 'api_key': self.token, 'language': 'zh-CN'} data = self.fetch(f'{self.chost}/discover/{tid}', params=params, headers=self.headers).json() return {'list': self.getlist(data['results'], tid), 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999} def detailContent(self, ids): path = ids[0] v = self.fetch(f'{self.chost}{path}', params={'api_key': self.token, 'language': 'zh-CN'}, headers=self.headers).json() is_movie = '/movie/' in path if is_movie: play_str = f"{v.get('title') or v.get('name')}${path}" else: play_items = [] for season in v.get('seasons') or []: season_number = season.get('season_number') if not season_number or season_number < 1: continue tv_id = re.findall(r'/tv/(\d+)', path)[0] season_data = self.fetch(f"{self.chost}/tv/{tv_id}/season/{season_number}", params={'api_key': self.token, 'language': 'zh-CN'}, headers=self.headers).json() for episode in season_data.get('episodes', []): episode_number = episode.get('episode_number') if episode_number: name = episode.get('name') or f'S{season_number:02d}E{episode_number:02d}' play_items.append(f"{name}$/tv/{tv_id}/{season_number}/{episode_number}") play_str = '#'.join(play_items) if play_items else f"{v.get('name')}${path}/1/1" origin_country = v.get('origin_country', []) vod_area = ', '.join(origin_country) if origin_country else '未知' play_from_list, play_url_list = [], [] for server_id in self.server_order: play_from_list.append(server_id) play_url_list.append(play_str) play_from, play_url = '$$$'.join(play_from_list), '$$$'.join(play_url_list) return {'list': [{'vod_id': path, 'vod_name': v.get('title') or v.get('name'), 'vod_year': (v.get('release_date') or v.get('last_air_date') or '')[:4], 'vod_area': vod_area, 'vod_remarks': v.get('tagline') or '', 'vod_content': v.get('overview') or '', 'vod_play_from': play_from, 'vod_play_url': play_url}]} def searchContent(self, key, quick, pg="1"): params = {'query': key, 'page': pg, 'api_key': self.token, 'language': 'zh-CN', 'include_adult': 'false'} data = self.fetch(f'{self.chost}/search/multi', params=params, headers=self.headers).json() return {'list': self.getlist(data.get('results', [])), 'page': pg} def getlist(self, data, tid=''): return [{'vod_id': f"/{media_type}/{i['id']}", 'vod_name': i.get('title') or i.get('name') or '', 'vod_pic': f"{self.phost}{poster}" if (poster := i.get('poster_path') or i.get('backdrop_path')) else '', 'vod_remarks': ''} for i in data or [] if (media_type := tid or i.get('media_type')) in ('movie', 'tv') and i.get('id')] def jxh(self): header = self.headers.copy() header.update({'referer': f'{self.jx}/', 'origin': self.jx, 'content-type': 'text/plain'}) header.pop('authorization', None) return header def get_server_headers(self, server_id): domain = self.servers[server_id] header = self.headers.copy() header.update({'referer': f'{domain}/', 'origin': domain, 'content-type': 'text/plain'}) header.pop('authorization', None) return header def _parse_play_id(self, id_str): m = re.match(r'^/(movie|tv)/(\d+)(?:/(\d+)/(\d+))?$', id_str or '') if not m: if '/movie/' in id_str: return 'movie', re.findall(r'/movie/(\d+)', id_str)[0], None, None elif '/tv/' in id_str: parts = re.findall(r'/tv/(\d+)(?:/(\d+)/(\d+))?', id_str)[0] return 'tv', parts[0], (parts[1] or '1'), (parts[2] or '1') else: raise ValueError('Unrecognized play id') media_type, tmdb_id, season, episode = m.groups() return media_type, tmdb_id, season, episode def _get_vidrock_url(self, tmdb_id, media_type, season, episode): default_domain = 'https://vidrock.net' passphrase = "x7k9mPqT2rWvY8zA5bC3nF6hJ2lK4mN9" item_id = str(tmdb_id) if media_type == 'movie' else f"{tmdb_id}_{season}_{episode}" key, iv = passphrase.encode(), passphrase.encode()[0:16] cipher = AES.new(key, AES.MODE_CBC, iv) ct = cipher.encrypt(pad(item_id.encode(), AES.block_size)) encoded = quote(base64.b64encode(ct).decode()) headers = {"Referer": default_domain, "Origin": default_domain, "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36"} response = requests.get(f'{default_domain}/api/{media_type}/{encoded}', headers=headers).json() sources = [src['url'] for src in response.values() if src.get('url') and ('.m3u8' in src['url'] or '.mp4' in src['url'])] return random.choice(sorted(sources, key=lambda x: '.m3u8' not in x)) if sources else None def _111movies_encrypt_data(self, data_str): cipher = AES.new(self._111movies_key, AES.MODE_CBC, self._111movies_iv) padded_data = pad(data_str.encode(), AES.block_size) encrypted_data = cipher.encrypt(padded_data) hex_data = encrypted_data.hex() result = "".join(chr(ord(hex_data[i]) ^ self._111movies_xkey[i % len(self._111movies_xkey)]) for i in range(len(hex_data))) base64_result = base64.b64encode(result.encode('utf-8')).decode('ascii').replace('+', '-').replace('/', '_').replace('=', '') source_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" target_chars = "PpowE6rtqQ9OxFNzg_vLTJmHKi07j45fXubCVecGURsaS1ny8lBWdAD2ZkM3-YhI" char_map = {source_chars[i]: target_chars[i] for i in range(len(source_chars))} return ''.join([char_map.get(c, c) for c in base64_result]) def _get_111movies_url(self, encrypted_txt): rsp = self.post(f"{self.jx}/rijevra/{encrypted_txt}/sr", headers=self.jxh(), timeout=10) data = rsp.json() urls = [] for i in data: if name := i.get('name'): if dat := i.get('data'): urls.extend([name, f"{self.getProxyUrl()}&dddd={dat}"]) return urls def _get_vidzee_url(self, tmdb_id, media_type, season, episode): user_agent = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36" default_domain = 'https://player.vidzee.wtf' headers = {'Referer': default_domain, 'Origin': default_domain, 'User-Agent': user_agent} server = 1 api_url = f'{default_domain}/api/server?id={tmdb_id}&sr={server}' if media_type == 'movie' else f'{default_domain}/api/server?id={tmdb_id}&sr={server}&ss={season}&ep={episode}' response = requests.get(api_url, headers=headers, timeout=10).json() if encrypted_url := response.get('url', [{}])[0].get('link'): iv_b64, ciphertext_b64 = base64.b64decode(encrypted_url).decode().split(':', 1) iv, ciphertext = base64.b64decode(iv_b64), base64.b64decode(ciphertext_b64) key = bytes.fromhex(self.vidzee_key_hex) cipher = AES.new(key, AES.MODE_CBC, iv) decrypted_data = cipher.decrypt(ciphertext) plaintext_bytes = unpad(decrypted_data, AES.block_size) return plaintext_bytes.decode('utf-8') return None def _vf_custom_encode(self, input_bytes): source_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" target_chars = "4stjqN6BT05-L8rQe_HxWmAVv9icYKaCDzIP1fZ7kwXRyFhd2GEng3SMJlUubOop" translation_table = str.maketrans(source_chars, target_chars) encoded = base64.urlsafe_b64encode(input_bytes).decode().rstrip('=') return encoded.translate(translation_table) def _get_vidfast_streams(self, tmdb_id, media_type, season, episode): base_url = f"https://vidfast.pro/movie/{tmdb_id}" if media_type == 'movie' else f"https://vidfast.pro/tv/{tmdb_id}/{season}/{episode}" ua = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36" default_domain = '{uri.scheme}://{uri.netloc}/'.format(uri=urlparse(base_url)) headers = {"Accept": "*/*", "Referer": default_domain, "User-Agent": ua, "X-Csrf-Token": "iwwuf3C7tleIfqxlgG5NUxOrOROfn5d9", "X-Requested-With": "XMLHttpRequest"} sess = requests.Session() requests.packages.urllib3.disable_warnings() def _get(url, hdrs): last = None for _ in range(3): r = sess.get(url, headers=hdrs, timeout=10, verify=False) last = r if r.status_code == 200: return r return last html_resp = _get(base_url, {"User-Agent": ua, "Referer": default_domain}) if not html_resp or html_resp.status_code != 200: return [] if m := re.search(r'\\"en\\":\\"(.*?)\\"', html_resp.text): raw_data = m.group(1) key_hex, iv_hex = '1f9b96f4e6604062c39f69f4c2edd92210d44d185434b0d569b077a72975bf08', '70ed610a03c6a59c7967abf77db57f71' aes_key, aes_iv = bytes.fromhex(key_hex), bytes.fromhex(iv_hex) cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv) padded = pad(raw_data.encode(), AES.block_size) aes_encrypted = cipher.encrypt(padded) xor_key = bytes.fromhex("d6f87ef72c") xor_bytes = bytes(b ^ xor_key[i % len(xor_key)] for i, b in enumerate(aes_encrypted)) encoded_final = self._vf_custom_encode(xor_bytes) static_path = "hezushon/ira/2264ec23bfa5e4891e26d563e5daac61bcb05688/b544e02b" api_servers = f"https://vidfast.pro/{static_path}/wfPFjh__qQ/{encoded_final}" resp = _get(api_servers, headers) if not resp or resp.status_code != 200: return [] servers_list = resp.json() wanted = {"Oscar", "Alpha", "vFast"} results = [] for item in servers_list or []: if (name := item.get('name')) and (data := item.get('data')) and name in wanted: api_stream = f"https://vidfast.pro/{static_path}/AddlBFe5/{data}" r2 = _get(api_stream, headers) if r2 and r2.status_code == 200 and (url := r2.json().get('url')) and ('.m3u8' in url or '.mp4' in url): results.append((name, url)) return results return [] def playerContent(self, flag, id, vipFlags): subs = [] media_type, tmdb_id, season, episode = self._parse_play_id(id) s, e = season or '1', episode or '1' server_id = flag if flag in self.servers else self.server_order[0] domain = self.servers.get(server_id) lang_map = {'english': 'en', 'chinese': 'zh', 'zh': 'zh', '简体': 'zh-CN', '繁體': 'zh-TW', 'japanese': 'ja', 'korean': 'ko'} def _map_lang(label): name = (label or '').lower() if name in lang_map: return lang_map[name] for k, v in lang_map.items(): if name.startswith(k) or k in name: return v return '' hdr = self.jxh().copy() hdr.update({'referer': 'https://vidrock.net/'}) sub_api = f"https://s.vdrk.site/subfetch.php?id={tmdb_id}" + (f'&s={s}&e={e}' if media_type == 'tv' else '') resp = self.fetch(sub_api, headers=hdr, timeout=7) if resp and resp.status_code == 200: items = resp.json() if hasattr(resp, 'json') else json.loads(resp.text or '[]') if not items and media_type == 'tv': resp2 = self.fetch(f"https://s.vdrk.site/subfetch.php?id={tmdb_id}", headers=hdr, timeout=7) if resp2 and resp2.status_code == 200: items = resp2.json() if hasattr(resp2, 'json') else json.loads(resp2.text or '[]') for it in items or []: if u := it.get('file') or it.get('url') or it.get('src'): fmt = 'application/x-subrip' if 'srt' in u.lower() else 'text/vtt' subs.append({'url': u, 'name': it.get('label') or it.get('name') or 'Subtitle (vdrk)', 'lang': _map_lang(it.get('label')), 'format': fmt}) if server_id == 'vidrock': if video_url := self._get_vidrock_url(tmdb_id, media_type, s, e): return {'parse': 0, 'url': video_url, 'header': self.get_server_headers(server_id), 'subs': subs} embed = f"{domain}/movie/{tmdb_id}" if media_type == 'movie' else f"{domain}/tv/{tmdb_id}/{s}/{e}?autoplay=true&autonext=true" return {'parse': 1, 'url': embed, 'header': self.get_server_headers(server_id), 'subs': subs} elif server_id == '111movies': html = self.fetch(f'{self.jx}{id}', headers=self.get_server_headers(server_id)).text next_data = pq(html)('#__NEXT_DATA__').text() jstr = json.loads(next_data) data_token = (jstr.get('props', {}).get('pageProps', {}) or {}).get('data') encrypted_txt = self._111movies_encrypt_data(data_token) if video_urls := self._get_111movies_url(encrypted_txt): return {'parse': 0, 'url': video_urls, 'header': self.get_server_headers(server_id), 'subs': subs} return {'parse': 1, 'url': f'{self.jx}{id}', 'header': self.get_server_headers(server_id), 'subs': subs} elif server_id == 'vidzee': if video_url := self._get_vidzee_url(tmdb_id, media_type, s, e): vidzee_headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', 'Referer': 'https://player.vidzee.wtf/', 'Origin': 'https://player.vidzee.wtf', 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="141", "Google Chrome";v="141"', 'sec-ch-ua-mobile': '?1', 'sec-ch-ua-platform': '"Android"'} return {'parse': 0, 'url': video_url, 'header': vidzee_headers, 'subs': subs} embed = f"{domain}/embed/{media_type}/{tmdb_id}" + (f"/{s}/{e}" if media_type == 'tv' else "") return {'parse': 1, 'url': embed, 'header': self.get_server_headers(server_id), 'subs': subs} elif server_id == 'vidfast': if pairs := self._get_vidfast_streams(tmdb_id, media_type, s, e): url_list = [] for name, u in pairs: url_list.extend([name, u]) vf_headers = {'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36', 'Referer': 'https://vidfast.pro/', 'Origin': 'https://vidfast.pro'} return {'parse': 0, 'url': url_list, 'header': vf_headers, 'subs': subs} embed = f"{domain}/movie/{tmdb_id}" if media_type == 'movie' else f"{domain}/tv/{tmdb_id}/{s}/{e}?autoNext=true&nextButton=false&title=true&poster=true&autoPlay=true" return {'parse': 1, 'url': embed, 'header': self.get_server_headers(server_id), 'subs': subs} else: if media_type == 'movie': embed = f"{domain}/movie/{tmdb_id}" else: if server_id == 'vidfast': embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?autoNext=true&nextButton=false&title=true&poster=true&autoPlay=true" elif server_id == 'vidlink': embed = f"{domain}/tv/{tmdb_id}/{s}/{e}?primaryColor=63b8bc&secondaryColor=a2a2a2&iconColor=eefdec&icons=default&player=default&title=true&poster=true&autoplay=true&nextbutton=true" else: embed = f"{domain}/embed/{'movie' if media_type=='movie' else 'tv'}/{tmdb_id}{'' if media_type=='movie' else f'/{s}/{e}'}" return {'parse': 1, 'url': embed, 'header': self.get_server_headers(server_id), 'subs': subs} def localProxy(self, param): if dddd := param.get('dddd', ''): headers = self.jxh() data = self.post(f"{self.jx}/rijevra/{dddd}", headers=headers).json() return [302, 'application/vnd.apple.mpegurl', None, {'Location': data['url']}] return '' def gettoken(self): hosts, paths = [self.site], ['/', '/movies', '/tv-shows'] key_pattern = re.compile(r'TMDB_API_KEY\s*[:=]\s*[\"\']([A-Za-z0-9]+)[\"\']') for host in hosts: for path in paths: hdr = self.headers.copy() hdr.update({'origin': host, 'referer': f'{host}/'}) html = self.fetch(f'{host}{path}', headers=hdr, timeout=10).text if mod := pq(html)('script[type="module"]').attr('src'): murl = mod if mod.startswith('http') else f'{host}{mod}' mjs = self.fetch(murl, headers=hdr, timeout=10).text if m := key_pattern.search(mjs): return 'https://api.themoviedb.org/3', m.group(1) if mw := re.search(r'player-watch-([\w-]+)\.js', mjs): pjs = self.fetch(f"{host}/assets/player-watch-{mw.group(1)}.js", headers=hdr, timeout=10).text if m2 := key_pattern.search(pjs): return 'https://api.themoviedb.org/3', m2.group(1) return 'https://api.themoviedb.org/3', '524c16f6e2a0a13c49ff7b99d27b5efb'