猎手影视.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. # coding=utf-8
  2. # !/usr/bin/python
  3. # by嗷呜(finally)
  4. import sys
  5. import os
  6. sys.path.append("..")
  7. import re
  8. import hashlib
  9. import hmac
  10. import random
  11. import string
  12. from Crypto.Util.Padding import unpad
  13. from concurrent.futures import ThreadPoolExecutor
  14. from Crypto.PublicKey import RSA
  15. from Crypto.Cipher import PKCS1_v1_5, AES
  16. from base64 import b64encode, b64decode
  17. import json
  18. import time
  19. from base.spider import Spider
  20. class Spider(Spider):
  21. def getName(self):
  22. return "电影猎手"
  23. def init(self, extend=""):
  24. self.device = self.device_id()
  25. self.host = self.gethost()
  26. pass
  27. def isVideoFormat(self, url):
  28. pass
  29. def manualVideoCheck(self):
  30. pass
  31. def action(self, action):
  32. pass
  33. def destroy(self):
  34. pass
  35. t = str(int(time.time()))
  36. def homeContent(self, filter):
  37. result = {}
  38. filters = {}
  39. classes = []
  40. bba = self.url()
  41. data = self.fetch(f"{self.host}/api/v1/app/config?pack={bba[0]}&signature={bba[1]}", headers=self.header()).text
  42. data1 = self.aes(data)
  43. dy = {"class":"类型","area":"地区","lang":"语言","year":"年份","letter":"字母","by":"排序","sort":"排序"}
  44. data1['data']['movie_screen']['sort'].pop(0)
  45. for item in data1['data']['movie_screen']['sort']:
  46. item['n'] = item.pop('name')
  47. item['v'] = item.pop('value')
  48. for item in data1['data']['movie_screen']['filter']:
  49. has_non_empty_field = False
  50. classes.append({"type_name": item["name"], "type_id": str(item["id"])})
  51. for key in dy:
  52. if key in item and item[key]:
  53. has_non_empty_field = True
  54. break
  55. if has_non_empty_field:
  56. filters[str(item["id"])] = []
  57. filters[str(item["id"])].append(
  58. {"key": 'sort', "name": '排序', "value": data1['data']['movie_screen']['sort']})
  59. for dkey in item:
  60. if dkey in dy and item[dkey]:
  61. item[dkey].pop(0)
  62. value_array = [
  63. {"n": value.strip(), "v": value.strip()}
  64. for value in item[dkey]
  65. if value.strip() != ""
  66. ]
  67. filters[str(item["id"])].append(
  68. {"key": dkey, "name": dy[dkey], "value": value_array}
  69. )
  70. result["class"] = classes
  71. result["filters"] = filters
  72. return result
  73. def homeVideoContent(self):
  74. bba = self.url()
  75. url = f'{self.host}/api/v1/movie/index_recommend?pack={bba[0]}&signature={bba[1]}'
  76. data = self.fetch(url, headers=self.header()).json()
  77. videos = []
  78. for item in data['data']:
  79. if len(item['list']) > 0:
  80. for it in item['list']:
  81. try:
  82. videos.append(self.voides(it))
  83. except Exception as e:
  84. continue
  85. result = {"list": videos}
  86. return result
  87. def categoryContent(self, tid, pg, filter, extend):
  88. body = {"type_id": tid, "sort": extend.get("sort", "by_default"), "class": extend.get("class", "类型"),
  89. "area": extend.get("area", "地区"), "year": extend.get("year", "年份"), "page": str(pg),
  90. "pageSize": "21"}
  91. result = {}
  92. list = []
  93. bba = self.url(body)
  94. url = f"{self.host}/api/v1/movie/screen/list?pack={bba[0]}&signature={bba[1]}"
  95. data = self.fetch(url, headers=self.header()).json()['data']['list']
  96. for item in data:
  97. list.append(self.voides(item))
  98. result["list"] = list
  99. result["page"] = pg
  100. result["pagecount"] = 9999
  101. result["limit"] = 90
  102. result["total"] = 999999
  103. return result
  104. def detailContent(self, ids):
  105. body = {"id": ids[0]}
  106. bba = self.url(body)
  107. url = f'{self.host}/api/v1/movie/detail?pack={bba[0]}&signature={bba[1]}'
  108. data = self.fetch(url, headers=self.header()).json()['data']
  109. video = {'vod_name': data.get('name'),'type_name': data.get('type_name'),'vod_year': data.get('year'),'vod_area': data.get('area'),'vod_remarks': data.get('dynami'),'vod_content': data.get('content')}
  110. play = []
  111. names = []
  112. tasks = []
  113. for itt in data["play_from"]:
  114. name = itt["name"]
  115. a = []
  116. if len(itt["list"]) > 0:
  117. names.append(name)
  118. play.append(self.playeach(itt['list']))
  119. else:
  120. tasks.append({"movie_id": ids[0], "from_code": itt["code"]})
  121. names.append(name)
  122. if tasks:
  123. with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
  124. results = executor.map(self.playlist, tasks)
  125. for result in results:
  126. if result:
  127. play.append(result)
  128. else:
  129. play.append("")
  130. video["vod_play_from"] = "$$$".join(names)
  131. video["vod_play_url"] = "$$$".join(play)
  132. result = {"list": [video]}
  133. return result
  134. def searchContent(self, key, quick, pg=1):
  135. body = {"keyword": key, "sort": "", "type_id": "0", "page": str(pg), "pageSize": "10",
  136. "res_type": "by_movie_name"}
  137. bba = self.url(body)
  138. url = f"{self.host}/api/v1/movie/search?pack={bba[0]}&signature={bba[1]}"
  139. data = self.fetch(url, headers=self.header()).json()['data'].get('list')
  140. videos = []
  141. for it in data:
  142. try:
  143. videos.append(self.voides(it))
  144. except Exception as e:
  145. continue
  146. result = {"list": videos, "page": pg}
  147. return result
  148. def playerContent(self, flag, id, vipFlags):
  149. url = id
  150. if "m3u8" not in url and "mp4" not in url:
  151. try:
  152. add = id.split('|||')
  153. data = {"from_code": add[0], "play_url": add[1], "episode_id": add[2], "type": "play"}
  154. bba = self.url(data)
  155. data2 = self.fetch(f"{self.host}/api/v1/movie_addr/parse_url?pack={bba[0]}&signature={bba[1]}",
  156. headers=self.header()).json()['data']
  157. url = data2.get('play_url') or data2.get('download_url')
  158. try:
  159. url1 = self.fetch(url, headers=self.header(), allow_redirects=False).headers['Location']
  160. if url1 and "http" in url1:
  161. url = url1
  162. except:
  163. pass
  164. except Exception as e:
  165. pass
  166. if '.jpg' in url or '.jpeg' in url or '.png' in url:
  167. url = self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
  168. result = {}
  169. result["parse"] = 0
  170. result["url"] = url
  171. result["header"] = {'user-agent': 'okhttp/4.9.2'}
  172. return result
  173. def localProxy(self, param):
  174. url = b64decode(param["url"]).decode('utf-8')
  175. durl = url[:url.rfind('/')]
  176. data = self.fetch(url, headers=self.header()).content.decode("utf-8")
  177. lines = data.strip().split('\n')
  178. for index, string in enumerate(lines):
  179. # if 'URI="' in string and 'http' not in string:
  180. # lines[index] = index
  181. # 暂时预留,貌似用不到
  182. if '#EXT' not in string and 'http' not in string:
  183. lines[index] = durl + ('' if string.startswith('/') else '/') + string
  184. data = '\n'.join(lines)
  185. return [200, "application/vnd.apple.mpegur", data]
  186. def device_id(self):
  187. characters = string.ascii_lowercase + string.digits
  188. random_string = ''.join(random.choices(characters, k=32))
  189. return random_string
  190. def gethost(self):
  191. headers = {
  192. 'User-Agent': 'okhttp/4.9.2',
  193. 'Connection': 'Keep-Alive',
  194. }
  195. response = self.fetch('https://app-site.ecoliving168.com/domain_v5.json', headers=headers).json()
  196. url = response['api_service'].replace('/api/', '')
  197. return url
  198. def header(self):
  199. headers = {
  200. 'User-Agent': 'Android',
  201. 'Accept': 'application/prs.55App.v2+json',
  202. 'timestamp': self.t,
  203. 'x-client-setting': '{"pure-mode":1}',
  204. 'x-client-uuid': '{"device_id":' + self.device + '}, "type":1,"brand":"Redmi", "model":"M2012K10C", "system_version":30, "sdk_version":"3.1.0.7"}',
  205. 'x-client-version': '3096 '
  206. }
  207. return headers
  208. def url(self, id=None):
  209. if not id:
  210. id = {}
  211. id["timestamp"] = self.t
  212. public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA02F/kPg5A2NX4qZ5JSns+bjhVMCC6JbTiTKpbgNgiXU+Kkorg6Dj76gS68gB8llhbUKCXjIdygnHPrxVHWfzmzisq9P9awmXBkCk74Skglx2LKHa/mNz9ivg6YzQ5pQFUEWS0DfomGBXVtqvBlOXMCRxp69oWaMsnfjnBV+0J7vHbXzUIkqBLdXSNfM9Ag5qdRDrJC3CqB65EJ3ARWVzZTTcXSdMW9i3qzEZPawPNPe5yPYbMZIoXLcrqvEZnRK1oak67/ihf7iwPJqdc+68ZYEmmdqwunOvRdjq89fQMVelmqcRD9RYe08v+xDxG9Co9z7hcXGTsUquMxkh29uNawIDAQAB'
  213. encrypted_text = json.dumps(id)
  214. public_key = RSA.import_key(b64decode(public_key))
  215. cipher = PKCS1_v1_5.new(public_key)
  216. encrypted_message = cipher.encrypt(encrypted_text.encode('utf-8'))
  217. encrypted_message_base64 = b64encode(encrypted_message).decode('utf-8')
  218. result = encrypted_message_base64.replace('+', '-').replace('/', '_').replace('=', '')
  219. key = '635a580fcb5dc6e60caa39c31a7bde48'
  220. sign = hmac.new(key.encode(), result.encode(), hashlib.md5).hexdigest()
  221. return result, sign
  222. def playlist(self, body):
  223. try:
  224. bba = self.url(body)
  225. url = f'{self.host}/api/v1/movie_addr/list?pack={bba[0]}&signature={bba[1]}'
  226. data = self.fetch(url, headers=self.header()).json()['data']
  227. return self.playeach(data)
  228. except Exception:
  229. return []
  230. def playeach(self,data):
  231. play_urls = []
  232. for it in data:
  233. if re.search(r"mp4|m3u8", it["play_url"]):
  234. play_urls.append(f"{it['episode_name']}${it['play_url']}")
  235. else:
  236. play_urls.append(
  237. f"{it['episode_name']}${it['from_code']}|||{it['play_url']}|||{it['episode_id']}"
  238. )
  239. return '#'.join(play_urls)
  240. def voides(self, item):
  241. if item['name'] or item['title']:
  242. voide = {
  243. "vod_id": item.get('id') or item.get('click'),
  244. 'vod_name': item.get('name') or item.get('title'),
  245. 'vod_pic': item.get('cover') or item.get('image'),
  246. 'vod_year': item.get('year') or item.get('label'),
  247. 'vod_remarks': item.get('dynamic') or item.get('sub_title')
  248. }
  249. return voide
  250. def aes(self, text):
  251. text = text.replace('-', '+').replace('_', '/') + '=='
  252. key = b"e6d5de5fcc51f53d"
  253. iv = b"2f13eef7dfc6c613"
  254. cipher = AES.new(key, AES.MODE_CBC, iv)
  255. pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size).decode("utf-8")
  256. return json.loads(pt)