喵次元.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # File : 喵次元.py
  4. # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
  5. # Author's Blog: https://blog.csdn.net/qq_32394351
  6. # Date : 2024/1/17
  7. import sys
  8. import time
  9. sys.path.append('..')
  10. try:
  11. from base.spider import BaseSpider
  12. except ImportError:
  13. from t4.base.spider import BaseSpider
  14. """
  15. 配置示例:
  16. t4的配置里ext节点会自动变成api对应query参数extend,但t4的ext字符串不支持路径格式,比如./开头或者.json结尾
  17. api里会自动含有ext参数是base64编码后的选中的筛选条件
  18. {
  19. "key":"hipy_t4_喵次元",
  20. "name":"喵次元(hipy_t4)",
  21. "type":4,
  22. "api":"http://192.168.31.49:5707/api/v1/vod/喵次元",
  23. "searchable":1,
  24. "quickSearch":0,
  25. "filterable":1,
  26. "ext":""
  27. },
  28. {
  29. "key": "hipy_t3_喵次元",
  30. "name": "喵次元(hipy_t3)",
  31. "type": 3,
  32. "api": "{{host}}/txt/hipy/喵次元.py",
  33. "searchable": 1,
  34. "quickSearch": 0,
  35. "filterable": 1,
  36. "ext": ""
  37. },
  38. """
  39. # 全局变量
  40. gParam = {
  41. "HomeDict": {},
  42. "TypeDict": {},
  43. }
  44. class Spider(BaseSpider): # 元类 默认的元类 type
  45. key: str = 'sLunqcoH85Nm/jDmFKns7A== '
  46. key_str: str = 'sLunqcoH85Nm/jDmFKns7A=='
  47. iv: str = 'fedcba9876543210'
  48. token: str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBcHBUbyIsImlhdCI6MTcwMDA3MTcwMiwiZXhwIjoxNzMxNjA3NzAyLCJuYmYiOjE3MDAwNzE3MDIsInN1YiI6IkFwcFRvIiwianRpIjoiYzRjNTAzOTQxYTM4NWI1MDMyMTAyYmY3Yzk1OGY4MzEiLCJkYXRhIjp7InVzZXJfaWQiOjI0ODc1NCwidXNlcl9jaGVjayI6ImUzYmQ3NmNhNTJhMGY4NjAwMTdjNjdkZGUwN2QzZTM3IiwidXNlcl9uYW1lIjoiaGV6aWh1aSJ9fQ.4LWs3rNL-os8_Pqa9LgKtvVG5f0aIxVyAjYIagvO1F4'
  49. ic: str = 'bmXes2xsCWvsSdfYav0s9D78Ly7w1o%2BOYXApKx6SUd4NWKsTsapbS52l7y%2FsTVCM2kcoLws2jryaDQlHLse5fxD2B2VXZXfaQo0eMTOv2Xq7CKoPa51uVt8WiIY2SPztc7wxGE89%2Fcw2Q3n85uUT3A%3D%3D'
  50. api: str = 'https://cym.zhui.la/api.php'
  51. api_cofig: str = api + '/type/get_list'
  52. api_home: str = api + '/video/index'
  53. api_cate: str = api + '/video/get_list'
  54. api_search: str = api + '/video/get_list'
  55. api_detail: str = api + '/video/get_detail'
  56. api_tabs: str = api + '/video/get_player'
  57. api_parse: str = api + '/video/get_definition'
  58. params: dict = {"versionName": "5.6.9", "uuid": "9cc01079c64e2495", "version": "4835d0a2", "versionCode": "35"}
  59. def getName(self):
  60. return "喵次元"
  61. def init(self, extend=""):
  62. """
  63. 初始化加载extend,一般与py文件名同名的json文件作为扩展筛选
  64. @param extend:
  65. @return:
  66. """
  67. ext = self.extend
  68. self.log(f'ext:{ext}')
  69. key = self.key_str
  70. # 转hex
  71. key_hex_str = self.bytesToHexString(key.encode('utf-8'))
  72. # 右侧补16个0
  73. key_hex_str += '0' * 16
  74. key_hex = key_hex_str
  75. # key_hex = '734C756E71636F4838354E6D2F6A446D464B6E7337413D3D0000000000000000'
  76. # 转回来
  77. key = self.hexStringTobytes(key_hex).decode('utf-8')
  78. self.key = key
  79. def isVideoFormat(self, url):
  80. pass
  81. def manualVideoCheck(self):
  82. pass
  83. def homeContent(self, filterable=False):
  84. """
  85. 获取首页分类及筛选数据
  86. @param filterable: 能否筛选,跟t3/t4配置里的filterable参数一致
  87. @return:
  88. """
  89. filter_names = {
  90. 'class': '分类',
  91. 'area': '地区',
  92. 'lang': '语言',
  93. 'year': '年份',
  94. 'star': '明星',
  95. 'director': '导演',
  96. 'state': '状态',
  97. 'version': '版本',
  98. }
  99. ret = self.fetch(self.api_cofig).json()
  100. data = self.decode(ret['data'])
  101. result = {}
  102. classes = []
  103. filters = {}
  104. type_dict = {}
  105. for tp in data.get('list') or []:
  106. classes.append({
  107. 'type_name': tp['type_name'],
  108. 'type_id': tp['type_id']
  109. })
  110. type_dict[str(tp['type_id'])] = tp['type_name']
  111. tp_filters = []
  112. for key, value in tp['type_extend'].items():
  113. if value:
  114. tp_filters.append({
  115. 'key': key,
  116. 'name': filter_names.get(key) or key,
  117. 'value': [{'n': '全部', 'v': ''}] + [{'n': i, 'v': i} for i in value.split(',') if i]
  118. })
  119. filters[tp['type_id']] = tp_filters
  120. result['class'] = classes
  121. if filterable:
  122. result['filters'] = filters
  123. global gParam
  124. gParam['HomeDict'].update(result)
  125. gParam['TypeDict'].update(type_dict)
  126. return result
  127. def homeVideoContent(self):
  128. """
  129. 首页推荐列表
  130. @return:
  131. """
  132. ret = self.fetch(self.api_home).json()
  133. data = self.decode(ret['data'])
  134. # print(data)
  135. d = []
  136. for cate_data in data:
  137. items = cate_data['video']
  138. for item in items:
  139. d.append({
  140. 'vod_name': item['vod_name'],
  141. 'vod_id': item['vod_id'],
  142. 'vod_pic': item['vod_pic'],
  143. 'vod_remarks': item['vod_remarks'],
  144. })
  145. result = {
  146. 'list': d
  147. }
  148. return result
  149. def categoryContent(self, tid, pg, filterable, extend):
  150. """
  151. 返回一级列表页数据
  152. @param tid: 分类id
  153. @param pg: 当前页数
  154. @param filterable: 能否筛选
  155. @param extend: 当前筛选数据
  156. @return:
  157. """
  158. page_count = 21 # 默认赋值一页列表21条数据|这个值一定要写正确看他默认一页多少条
  159. fls = extend.keys() # 哪些刷新数据
  160. new_params = self.params.copy()
  161. new_params.update({'type_id': str(tid), 'limit': str(page_count), 'page': str(pg),
  162. 'orderby': '', 'ctime': str(int(time.time()))
  163. })
  164. for fl in fls:
  165. new_params[f'vod_{fl}'] = extend[fl]
  166. params = self.get_sign_params(new_params)
  167. # print(params)
  168. r = self.postJson(self.api_cate, json=params)
  169. ret = r.json()
  170. data = self.decode(ret['data'])
  171. # print(data)
  172. d = data['list']
  173. result = {
  174. 'list': d,
  175. 'page': pg,
  176. 'pagecount': 9999 if len(d) >= page_count else pg,
  177. 'limit': 90,
  178. 'total': data['count'],
  179. }
  180. return result
  181. def detailContent(self, ids):
  182. """
  183. 返回二级详情页数据
  184. @param ids: 一级传过来的vod_id列表
  185. @return:
  186. """
  187. # id=110102
  188. vod_id = ids[0]
  189. new_params = self.params.copy()
  190. new_params.update({'vod_id': str(vod_id), 'ctime': str(int(time.time()))})
  191. params = self.get_sign_params(new_params)
  192. # print(params)
  193. r = self.postJson(self.api_detail, json=params)
  194. ret = r.json()
  195. data = self.decode(ret['data'])
  196. # print(data)
  197. vod = {"vod_id": vod_id,
  198. "vod_name": data['vod_name'],
  199. "vod_pic": data['vod_pic'],
  200. "type_name": data['vod_en'],
  201. "vod_year": data['vod_year'],
  202. "vod_area": data['vod_area'],
  203. "vod_remarks": data['vod_remarks'],
  204. "vod_actor": data['vod_actor'],
  205. "vod_director": data['vod_director'],
  206. "vod_content": data['vod_blurb'],
  207. }
  208. episodes = data['player']
  209. play_map = {}
  210. play_from = []
  211. play_list = []
  212. for ep in episodes:
  213. player = ep["code"]
  214. source = ep["name"]
  215. new_params = self.params.copy()
  216. new_params.update({
  217. 'vod_id': str(vod_id), 'ctime': str(int(time.time())),
  218. 'limit': str(5000), 'page': str(1),
  219. 'player': player,
  220. })
  221. params = self.get_sign_params(new_params)
  222. r = self.postJson(self.api_tabs, json=params)
  223. ret = r.json()
  224. data = self.decode(ret['data'])
  225. # print(data)
  226. for playurl in data['list']:
  227. if source not in play_map:
  228. play_map[source] = []
  229. play_map[source].append(
  230. playurl["drama"] + "$" + '&'.join(
  231. [str(playurl["ju_id"]), str(playurl["plyer"]), str(playurl["video_id"])]))
  232. for key, value in play_map.items():
  233. play_from.append(key)
  234. play_list.append('#'.join(value))
  235. vod['vod_play_from'] = '$$$'.join(play_from)
  236. vod['vod_play_url'] = '$$$'.join(play_list)
  237. result = {
  238. 'list': [vod]
  239. }
  240. # print(vod)
  241. return result
  242. def searchContent(self, wd, quick=False, pg=1):
  243. """
  244. 返回搜索列表
  245. @param wd: 搜索关键词
  246. @param quick: 是否来自快速搜索。t3/t4配置里启用了快速搜索,在快速搜索在执行才会是True
  247. @param pg: 页数
  248. @return:
  249. """
  250. page_count = 21 # 默认赋值一页列表21条数据|这个值一定要写正确看他默认一页多少条
  251. new_params = self.params.copy()
  252. new_params.update({
  253. 'orderby': 'up', 'ctime': str(int(time.time())),
  254. 'limit': str(page_count), 'page': str(pg), 'vod_name': str(wd)
  255. })
  256. params = self.get_sign_params(new_params)
  257. # print(params)
  258. r = self.postJson(self.api_cate, json=params)
  259. ret = r.json()
  260. data = self.decode(ret['data'])
  261. # print(data)
  262. d = data['list']
  263. result = {
  264. 'list': d
  265. }
  266. return result
  267. def playerContent(self, flag, id, vipFlags):
  268. """
  269. 解析播放,返回json。壳子视情况播放直链或进行嗅探
  270. @param flag: vod_play_from 播放来源线路
  271. @param id: vod_play_url 播放的链接
  272. @param vipFlags: vip标识
  273. @return:
  274. """
  275. _v = id.split('&')
  276. ju_id = _v[0]
  277. plyer = _v[1]
  278. video_id = _v[2]
  279. new_params = self.params.copy()
  280. new_params.update({
  281. 'player_id': str(plyer), 'ctime': str(int(time.time())),
  282. 'ju_id': str(ju_id), 'vod_id': str(video_id)
  283. })
  284. params = self.get_sign_params(new_params)
  285. # print(params)
  286. r = self.postJson(self.api_parse, json=params)
  287. ret = r.json()
  288. data = self.decode(ret['data'])
  289. # print(data)
  290. # 列表里第1条的分辨率最高
  291. url = data[0]['url']
  292. # print(url)
  293. """
  294. # 原始key
  295. key = 'sLunqcoH85Nm/jDmFKns7A=='
  296. # 转hex
  297. key_hex_str = self.bytesToHexString(key.encode('utf-8')).replace(' ', '')
  298. # 右侧补16个0
  299. key_hex_str += '0'*16
  300. key_hex = key_hex_str
  301. # key_hex = '734C756E71636F4838354E6D2F6A446D464B6E7337413D3D0000000000000000'
  302. # 转回来
  303. key = self.hexStringTobytes(key_hex).decode('utf-8')
  304. # print(key)
  305. iv = 'fedcba9876543210'
  306. """
  307. # key = self.key
  308. # iv = self.iv
  309. # input = self.aes_cbc_decode(url,key,iv)
  310. input = self.decode_aes(url)
  311. parse = 0
  312. result = {
  313. 'parse': parse, # 1=嗅探,0=播放
  314. 'playUrl': '', # 解析链接
  315. 'url': input, # 直链或待嗅探地址
  316. # 'header': headers, # 播放UA
  317. }
  318. return result
  319. config = {
  320. "player": {},
  321. "filter": {}
  322. }
  323. header = {
  324. "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",
  325. "Host": "www.baidu.com",
  326. "Referer": "https://www.baidu.com/"
  327. }
  328. def localProxy(self, params):
  329. return [200, "video/MP2T", ""]
  330. # -----------------------------------------------自定义函数-----------------------------------------------
  331. def get_sign_params(self, params: dict):
  332. keys = list(params.keys())
  333. keys.sort()
  334. str_list = []
  335. for key in keys:
  336. if params.get(key):
  337. str_list.append(params[key])
  338. str_list.append('alskeuscli')
  339. sign = self.md5(''.join(str_list))
  340. params['sign'] = sign
  341. return params
  342. def decode(self, text):
  343. return text
  344. # return self.str2json(self.aes_cbc_decode(text, self.key, self.iv))
  345. def decode_aes(self, text):
  346. key = self.key
  347. iv = self.iv
  348. input = self.aes_cbc_decode(text, key, iv)
  349. return input
  350. if __name__ == '__main__':
  351. # 在线aes测试 https://config.net.cn/tools/AES.html
  352. # 分类页:http://60.204.185.245:7090/appto/v1/home/cateData?id=1
  353. # 推荐页:http://60.204.185.245:7090/appto/v1/config/get?p=android
  354. from t4.core.loader import t4_spider_init
  355. spider = Spider()
  356. t4_spider_init(spider)
  357. # spider.init_api_ext_file() # 生成筛选对应的json文件
  358. # print(spider.homeContent(True))
  359. # print(spider.homeVideoContent())
  360. # print(spider.categoryContent('23', 1, True, {'year': '2024'}))
  361. # print(spider.detailContent([7533]))
  362. # print(spider.searchContent('斗罗大陆'))
  363. print(spider.playerContent('线路J', '1&duoduan&7533', None))
  364. print(spider.playerContent('线路Z', '1&ziru&7533', None))