LIVES.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. # -*- coding: utf-8 -*-
  2. # by @嗷呜
  3. import json
  4. import re
  5. import sys
  6. import time
  7. from base64 import b64decode, b64encode
  8. from urllib.parse import parse_qs
  9. import requests
  10. from pyquery import PyQuery as pq
  11. sys.path.append('..')
  12. from base.spider import Spider
  13. from concurrent.futures import ThreadPoolExecutor
  14. class Spider(Spider):
  15. def init(self, extend=""):
  16. tid = 'douyin'
  17. headers = self.gethr(0, tid)
  18. response = requests.head(self.hosts[tid], headers=headers)
  19. ttwid = response.cookies.get('ttwid')
  20. headers.update({
  21. 'authority': self.hosts[tid].split('//')[-1],
  22. 'cookie': f'ttwid={ttwid}' if ttwid else ''
  23. })
  24. self.dyheaders = headers
  25. pass
  26. def getName(self):
  27. pass
  28. def isVideoFormat(self, url):
  29. pass
  30. def manualVideoCheck(self):
  31. pass
  32. def destroy(self):
  33. pass
  34. headers = [
  35. {
  36. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0"
  37. },
  38. {
  39. "User-Agent": "Dart/3.4 (dart:io)"
  40. }
  41. ]
  42. excepturl = 'https://www.baidu.com'
  43. hosts = {
  44. "huya": ["https://www.huya.com","https://mp.huya.com"],
  45. "douyin": "https://live.douyin.com",
  46. "douyu": "https://www.douyu.com",
  47. "wangyi": "https://cc.163.com",
  48. "bili": ["https://api.live.bilibili.com", "https://api.bilibili.com"]
  49. }
  50. referers = {
  51. "huya": "https://live.cdn.huya.com",
  52. "douyin": "https://live.douyin.com",
  53. "douyu": "https://m.douyu.com",
  54. "bili": "https://live.bilibili.com"
  55. }
  56. playheaders = {
  57. "wangyi": {
  58. "User-Agent": "ExoPlayer",
  59. "Connection": "Keep-Alive",
  60. "Icy-MetaData": "1"
  61. },
  62. "bili": {
  63. 'Accept': '*/*',
  64. 'Icy-MetaData': '1',
  65. 'referer': referers['bili'],
  66. 'user-agent': headers[0]['User-Agent']
  67. },
  68. 'douyin': {
  69. 'User-Agent': 'libmpv',
  70. 'Icy-MetaData': '1'
  71. },
  72. 'huya': {
  73. 'User-Agent': 'ExoPlayer',
  74. 'Connection': 'Keep-Alive',
  75. 'Icy-MetaData': '1'
  76. },
  77. 'douyu': {
  78. 'User-Agent': 'libmpv',
  79. 'Icy-MetaData': '1'
  80. }
  81. }
  82. def process_bili(self):
  83. try:
  84. self.blfdata = self.fetch(
  85. f'{self.hosts["bili"][0]}/room/v1/Area/getList?need_entrance=1&parent_id=0',
  86. headers=self.gethr(0, 'bili')
  87. ).json()
  88. return ('bili', [{'key': 'cate', 'name': '分类',
  89. 'value': [{'n': i['name'], 'v': str(i['id'])}
  90. for i in self.blfdata['data']]}])
  91. except Exception as e:
  92. print(f"bili处理错误: {e}")
  93. return 'bili', None
  94. def process_douyin(self):
  95. try:
  96. data = self.getpq(self.hosts['douyin'], headers=self.dyheaders)('script')
  97. for i in data.items():
  98. if 'categoryData' in i.text():
  99. content = i.text()
  100. start = content.find('{')
  101. end = content.rfind('}') + 1
  102. if start != -1 and end != -1:
  103. json_str = content[start:end]
  104. json_str = json_str.replace('\\"', '"')
  105. try:
  106. self.dyifdata = json.loads(json_str)
  107. return ('douyin', [{'key': 'cate', 'name': '分类',
  108. 'value': [{'n': i['partition']['title'],
  109. 'v': f"{i['partition']['id_str']}@@{i['partition']['title']}"}
  110. for i in self.dyifdata['categoryData']]}])
  111. except json.JSONDecodeError as e:
  112. print(f"douyin解析错误: {e}")
  113. return 'douyin', None
  114. except Exception as e:
  115. print(f"douyin请求或处理错误: {e}")
  116. return 'douyin', None
  117. def process_douyu(self):
  118. try:
  119. self.dyufdata = self.fetch(
  120. f'{self.referers["douyu"]}/api/cate/list',
  121. headers=self.headers[1]
  122. ).json()
  123. return ('douyu', [{'key': 'cate', 'name': '分类',
  124. 'value': [{'n': i['cate1Name'], 'v': str(i['cate1Id'])}
  125. for i in self.dyufdata['data']['cate1Info']]}])
  126. except Exception as e:
  127. print(f"douyu错误: {e}")
  128. return 'douyu', None
  129. def homeContent(self, filter):
  130. result = {}
  131. cateManual = {
  132. "虎牙": "huya",
  133. "哔哩": "bili",
  134. "抖音": "douyin",
  135. "斗鱼": "douyu",
  136. "网易": "wangyi"
  137. }
  138. classes = []
  139. filters = {
  140. 'huya': [{'key': 'cate', 'name': '分类',
  141. 'value': [{'n': '网游', 'v': '1'}, {'n': '单机', 'v': '2'},
  142. {'n': '娱乐', 'v': '8'}, {'n': '手游', 'v': '3'}]}]
  143. }
  144. with ThreadPoolExecutor(max_workers=3) as executor:
  145. futures = {
  146. executor.submit(self.process_bili): 'bili',
  147. executor.submit(self.process_douyin): 'douyin',
  148. executor.submit(self.process_douyu): 'douyu'
  149. }
  150. for future in futures:
  151. platform, filter_data = future.result()
  152. if filter_data:
  153. filters[platform] = filter_data
  154. for k in cateManual:
  155. classes.append({
  156. 'type_name': k,
  157. 'type_id': cateManual[k]
  158. })
  159. result['class'] = classes
  160. result['filters'] = filters
  161. return result
  162. def homeVideoContent(self):
  163. pass
  164. def categoryContent(self, tid, pg, filter, extend):
  165. vdata = []
  166. result = {}
  167. pagecount = 9999
  168. result['page'] = pg
  169. result['limit'] = 90
  170. result['total'] = 999999
  171. if tid == 'wangyi':
  172. vdata, pagecount = self.wyccContent(tid, pg, filter, extend, vdata)
  173. elif 'bili' in tid:
  174. vdata, pagecount = self.biliContent(tid, pg, filter, extend, vdata)
  175. elif 'huya' in tid:
  176. vdata, pagecount = self.huyaContent(tid, pg, filter, extend, vdata)
  177. elif 'douyin' in tid:
  178. vdata, pagecount = self.douyinContent(tid, pg, filter, extend, vdata)
  179. elif 'douyu' in tid:
  180. vdata, pagecount = self.douyuContent(tid, pg, filter, extend, vdata)
  181. result['list'] = vdata
  182. result['pagecount'] = pagecount
  183. return result
  184. def wyccContent(self, tid, pg, filter, extend, vdata):
  185. params = {
  186. 'format': 'json',
  187. 'start': (int(pg) - 1) * 20,
  188. 'size': '20',
  189. }
  190. response = self.fetch(f'{self.hosts[tid]}/api/category/live/', params=params, headers=self.headers[0]).json()
  191. for i in response['lives']:
  192. if i.get('cuteid'):
  193. bvdata = self.buildvod(
  194. vod_id=f"{tid}@@{i['cuteid']}",
  195. vod_name=i.get('title'),
  196. vod_pic=i.get('cover'),
  197. vod_remarks=i.get('nickname'),
  198. style={"type": "rect", "ratio": 1.33}
  199. )
  200. vdata.append(bvdata)
  201. return vdata, 9999
  202. def biliContent(self, tid, pg, filter, extend, vdata):
  203. if extend.get('cate') and pg == '1' and 'click' not in tid:
  204. for i in self.blfdata['data']:
  205. if str(i['id']) == extend['cate']:
  206. for j in i['list']:
  207. v = self.buildvod(
  208. vod_id=f"click_{tid}@@{i['id']}@@{j['id']}",
  209. vod_name=j.get('name'),
  210. vod_pic=j.get('pic'),
  211. vod_tag=1,
  212. style={"type": "oval", "ratio": 1}
  213. )
  214. vdata.append(v)
  215. return vdata, 1
  216. else:
  217. path = f'/xlive/web-interface/v1/second/getListByArea?platform=web&sort=online&page_size=30&page={pg}'
  218. if 'click' in tid:
  219. ids = tid.split('_')[1].split('@@')
  220. tid = ids[0]
  221. path = f'/xlive/web-interface/v1/second/getList?platform=web&parent_area_id={ids[1]}&area_id={ids[-1]}&sort_type=&page={pg}'
  222. data = self.fetch(f'{self.hosts[tid][0]}{path}', headers=self.gethr(0, tid)).json()
  223. for i in data['data']['list']:
  224. if i.get('roomid'):
  225. data = self.buildvod(
  226. f"{tid}@@{i['roomid']}",
  227. i.get('title'),
  228. i.get('cover'),
  229. i.get('watched_show', {}).get('text_large'),
  230. 0,
  231. i.get('uname'),
  232. style={"type": "rect", "ratio": 1.33}
  233. )
  234. vdata.append(data)
  235. return vdata, 9999
  236. def huyaContent(self, tid, pg, filter, extend, vdata):
  237. if extend.get('cate') and pg == '1' and 'click' not in tid:
  238. id = extend.get('cate')
  239. data = self.fetch(f'{self.referers[tid]}/liveconfig/game/bussLive?bussType={id}',
  240. headers=self.headers[1]).json()
  241. for i in data['data']:
  242. v = self.buildvod(
  243. vod_id=f"click_{tid}@@{int(i['gid'])}",
  244. vod_name=i.get('gameFullName'),
  245. vod_pic=f'https://huyaimg.msstatic.com/cdnimage/game/{int(i["gid"])}-MS.jpg',
  246. vod_tag=1,
  247. style={"type": "oval", "ratio": 1}
  248. )
  249. vdata.append(v)
  250. return vdata, 1
  251. else:
  252. gid = ''
  253. if 'click' in tid:
  254. ids = tid.split('_')[1].split('@@')
  255. tid = ids[0]
  256. gid = f'&gameId={ids[1]}'
  257. data = self.fetch(f'{self.hosts[tid][0]}/cache.php?m=LiveList&do=getLiveListByPage&tagAll=0{gid}&page={pg}',
  258. headers=self.headers[1]).json()
  259. for i in data['data']['datas']:
  260. if i.get('profileRoom'):
  261. v = self.buildvod(
  262. f"{tid}@@{i['profileRoom']}",
  263. i.get('introduction'),
  264. i.get('screenshot'),
  265. str(int(i.get('totalCount', '1')) / 10000) + '万',
  266. 0,
  267. i.get('nick'),
  268. style={"type": "rect", "ratio": 1.33}
  269. )
  270. vdata.append(v)
  271. return vdata, 9999
  272. def douyinContent(self, tid, pg, filter, extend, vdata):
  273. if extend.get('cate') and pg == '1' and 'click' not in tid:
  274. ids = extend.get('cate').split('@@')
  275. for i in self.dyifdata['categoryData']:
  276. c = i['partition']
  277. if c['id_str'] == ids[0] and c['title'] == ids[1]:
  278. vlist = i['sub_partition'].copy()
  279. vlist.insert(0, {'partition': c})
  280. for j in vlist:
  281. j = j['partition']
  282. v = self.buildvod(
  283. vod_id=f"click_{tid}@@{j['id_str']}@@{j['type']}",
  284. vod_name=j.get('title'),
  285. vod_pic='https://p3-pc-weboff.byteimg.com/tos-cn-i-9r5gewecjs/pwa_v3/512x512-1.png',
  286. vod_tag=1,
  287. style={"type": "oval", "ratio": 1}
  288. )
  289. vdata.append(v)
  290. return vdata, 1
  291. else:
  292. path = f'/webcast/web/partition/detail/room/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&count=15&offset={(int(pg) - 1) * 15}&partition=720&partition_type=1'
  293. if 'click' in tid:
  294. ids = tid.split('_')[1].split('@@')
  295. tid = ids[0]
  296. path = f'/webcast/web/partition/detail/room/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&count=15&offset={(int(pg) - 1) * 15}&partition={ids[1]}&partition_type={ids[-1]}&req_from=2'
  297. data = self.fetch(f'{self.hosts[tid]}{path}', headers=self.dyheaders).json()
  298. for i in data['data']['data']:
  299. v = self.buildvod(
  300. vod_id=f"{tid}@@{i['web_rid']}",
  301. vod_name=i['room'].get('title'),
  302. vod_pic=i['room']['cover'].get('url_list')[0],
  303. vod_year=i.get('user_count_str'),
  304. vod_remarks=i['room']['owner'].get('nickname'),
  305. style={"type": "rect", "ratio": 1.33}
  306. )
  307. vdata.append(v)
  308. return vdata, 9999
  309. def douyuContent(self, tid, pg, filter, extend, vdata):
  310. if extend.get('cate') and pg == '1' and 'click' not in tid:
  311. for i in self.dyufdata['data']['cate2Info']:
  312. if str(i['cate1Id']) == extend['cate']:
  313. v = self.buildvod(
  314. vod_id=f"click_{tid}@@{i['cate2Id']}",
  315. vod_name=i.get('cate2Name'),
  316. vod_pic=i.get('icon'),
  317. vod_remarks=i.get('count'),
  318. vod_tag=1,
  319. style={"type": "oval", "ratio": 1}
  320. )
  321. vdata.append(v)
  322. return vdata, 1
  323. else:
  324. path = f'/japi/weblist/apinc/allpage/6/{pg}'
  325. if 'click' in tid:
  326. ids = tid.split('_')[1].split('@@')
  327. tid = ids[0]
  328. path = f'/gapi/rkc/directory/mixList/2_{ids[1]}/{pg}'
  329. url = f'{self.hosts[tid]}{path}'
  330. data = self.fetch(url, headers=self.headers[1]).json()
  331. for i in data['data']['rl']:
  332. v = self.buildvod(
  333. vod_id=f"{tid}@@{i['rid']}",
  334. vod_name=i.get('rn'),
  335. vod_pic=i.get('rs16'),
  336. vod_year=str(int(i.get('ol', 1)) / 10000) + '万',
  337. vod_remarks=i.get('nn'),
  338. style={"type": "rect", "ratio": 1.33}
  339. )
  340. vdata.append(v)
  341. return vdata, 9999
  342. def detailContent(self, ids):
  343. ids = ids[0].split('@@')
  344. if ids[0] == 'wangyi':
  345. vod = self.wyccDetail(ids)
  346. elif ids[0] == 'bili':
  347. vod = self.biliDetail(ids)
  348. elif ids[0] == 'huya':
  349. vod = self.huyaDetail(ids)
  350. elif ids[0] == 'douyin':
  351. vod = self.douyinDetail(ids)
  352. elif ids[0] == 'douyu':
  353. vod = self.douyuDetail(ids)
  354. return {'list': [vod]}
  355. def wyccDetail(self, ids):
  356. try:
  357. vdata = self.getpq(f'{self.hosts[ids[0]]}/{ids[1]}', self.headers[0])('script').eq(-1).text()
  358. def get_quality_name(vbr):
  359. if vbr <= 600:
  360. return "标清"
  361. elif vbr <= 1000:
  362. return "高清"
  363. elif vbr <= 2000:
  364. return "超清"
  365. else:
  366. return "蓝光"
  367. data = json.loads(vdata)['props']['pageProps']['roomInfoInitData']
  368. name = data['live'].get('title', ids[0])
  369. vod = self.buildvod(vod_name=data.get('keywords_suffix'), vod_remarks=data['live'].get('title'),
  370. vod_content=data.get('description_suffix'))
  371. resolution_data = data['live']['quickplay']['resolution']
  372. all_streams = {}
  373. sorted_qualities = sorted(resolution_data.items(),
  374. key=lambda x: x[1]['vbr'],
  375. reverse=True)
  376. for quality, data in sorted_qualities:
  377. vbr = data['vbr']
  378. quality_name = get_quality_name(vbr)
  379. for cdn_name, url in data['cdn'].items():
  380. if cdn_name not in all_streams and type(url) == str and url.startswith('http'):
  381. all_streams[cdn_name] = []
  382. if isinstance(url, str) and url.startswith('http'):
  383. all_streams[cdn_name].extend([quality_name, url])
  384. plists = []
  385. names = []
  386. for i, (cdn_name, stream_list) in enumerate(all_streams.items(), 1):
  387. names.append(f'线路{i}')
  388. pstr = f"{name}${ids[0]}@@{self.e64(json.dumps(stream_list))}"
  389. plists.append(pstr)
  390. vod['vod_play_from'] = "$$$".join(names)
  391. vod['vod_play_url'] = "$$$".join(plists)
  392. return vod
  393. except Exception as e:
  394. return self.handle_exception(e)
  395. def biliDetail(self, ids):
  396. try:
  397. vdata = self.fetch(
  398. f'{self.hosts[ids[0]][0]}/xlive/web-room/v1/index/getInfoByRoom?room_id={ids[1]}&wts={int(time.time())}',
  399. headers=self.gethr(0, ids[0])).json()
  400. v = vdata['data']['room_info']
  401. vod = self.buildvod(
  402. vod_name=v.get('title'),
  403. type_name=v.get('parent_area_name') + '/' + v.get('area_name'),
  404. vod_remarks=v.get('tags'),
  405. vod_play_from=v.get('title'),
  406. )
  407. data = self.fetch(
  408. f'{self.hosts[ids[0]][0]}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={ids[1]}&protocol=0%2C1&format=0%2C1%2C2&codec=0%2C1&platform=web',
  409. headers=self.gethr(0, ids[0])).json()
  410. vdnams = data['data']['playurl_info']['playurl']['g_qn_desc']
  411. all_accept_qns = []
  412. streams = data['data']['playurl_info']['playurl']['stream']
  413. for stream in streams:
  414. for format_item in stream['format']:
  415. for codec in format_item['codec']:
  416. if 'accept_qn' in codec:
  417. all_accept_qns.append(codec['accept_qn'])
  418. max_accept_qn = max(all_accept_qns, key=len) if all_accept_qns else []
  419. quality_map = {
  420. item['qn']: item['desc']
  421. for item in vdnams
  422. }
  423. quality_names = [f"{quality_map.get(qn)}${ids[0]}@@{ids[1]}@@{qn}" for qn in max_accept_qn]
  424. vod['vod_play_url'] = "#".join(quality_names)
  425. return vod
  426. except Exception as e:
  427. return self.handle_exception(e)
  428. def huyaDetail(self, ids):
  429. try:
  430. vdata = self.fetch(f'{self.hosts[ids[0]][1]}/cache.php?m=Live&do=profileRoom&roomid={ids[1]}',
  431. headers=self.headers[0]).json()
  432. v = vdata['data']['liveData']
  433. vod = self.buildvod(
  434. vod_name=v.get('introduction'),
  435. type_name=v.get('gameFullName'),
  436. vod_director=v.get('nick'),
  437. vod_remarks=v.get('contentIntro'),
  438. )
  439. data = dict(reversed(list(vdata['data']['stream'].items())))
  440. names = []
  441. plist = []
  442. for stream_type, stream_data in data.items():
  443. if isinstance(stream_data, dict) and 'multiLine' in stream_data and 'rateArray' in stream_data:
  444. names.append(f"线路{len(names) + 1}")
  445. qualities = sorted(
  446. stream_data['rateArray'],
  447. key=lambda x: (x['iBitRate'], x['sDisplayName']),
  448. reverse=True
  449. )
  450. cdn_urls = []
  451. for cdn in stream_data['multiLine']:
  452. quality_urls = []
  453. for quality in qualities:
  454. quality_name = quality['sDisplayName']
  455. bit_rate = quality['iBitRate']
  456. base_url = cdn['url']
  457. if bit_rate > 0:
  458. if '.m3u8' in base_url:
  459. new_url = base_url.replace(
  460. 'ratio=2000',
  461. f'ratio={bit_rate}'
  462. )
  463. else:
  464. new_url = base_url.replace(
  465. 'imgplus.flv',
  466. f'imgplus_{bit_rate}.flv'
  467. )
  468. else:
  469. new_url = base_url
  470. quality_urls.extend([quality_name, new_url])
  471. encoded_urls = self.e64(json.dumps(quality_urls))
  472. cdn_urls.append(f"{cdn['cdnType']}${ids[0]}@@{encoded_urls}")
  473. if cdn_urls:
  474. plist.append('#'.join(cdn_urls))
  475. vod['vod_play_from'] = "$$$".join(names)
  476. vod['vod_play_url'] = "$$$".join(plist)
  477. return vod
  478. except Exception as e:
  479. return self.handle_exception(e)
  480. def douyinDetail(self, ids):
  481. url = f'{self.hosts[ids[0]]}/webcast/room/web/enter/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&enter_from=web_live&web_rid={ids[1]}&room_id_str=&enter_source=&Room-Enter-User-Login-Ab=0&is_need_double_stream=false&cookie_enabled=true&screen_width=1980&screen_height=1080&browser_language=zh-CN&browser_platform=Win32&browser_name=Edge&browser_version=125.0.0.0'
  482. data = self.fetch(url, headers=self.dyheaders).json()
  483. try:
  484. vdata = data['data']['data'][0]
  485. vod = self.buildvod(
  486. vod_name=vdata['title'],
  487. vod_remarks=vdata['user_count_str'],
  488. )
  489. resolution_data = vdata['stream_url']['live_core_sdk_data']['pull_data']['options']['qualities']
  490. stream_json = vdata['stream_url']['live_core_sdk_data']['pull_data']['stream_data']
  491. stream_json = json.loads(stream_json)
  492. available_types = []
  493. if any(sdk_key in stream_json['data'] and 'main' in stream_json['data'][sdk_key] for sdk_key in
  494. stream_json['data']):
  495. available_types.append('main')
  496. if any(sdk_key in stream_json['data'] and 'backup' in stream_json['data'][sdk_key] for sdk_key in
  497. stream_json['data']):
  498. available_types.append('backup')
  499. plist = []
  500. for line_type in available_types:
  501. format_arrays = {'flv': [], 'hls': [], 'lls': []}
  502. qualities = sorted(resolution_data, key=lambda x: x['level'], reverse=True)
  503. for quality in qualities:
  504. sdk_key = quality['sdk_key']
  505. if sdk_key in stream_json['data'] and line_type in stream_json['data'][sdk_key]:
  506. stream_info = stream_json['data'][sdk_key][line_type]
  507. if stream_info.get('flv'):
  508. format_arrays['flv'].extend([quality['name'], stream_info['flv']])
  509. if stream_info.get('hls'):
  510. format_arrays['hls'].extend([quality['name'], stream_info['hls']])
  511. if stream_info.get('lls'):
  512. format_arrays['lls'].extend([quality['name'], stream_info['lls']])
  513. format_urls = []
  514. for format_name, url_array in format_arrays.items():
  515. if url_array:
  516. encoded_urls = self.e64(json.dumps(url_array))
  517. format_urls.append(f"{format_name}${ids[0]}@@{encoded_urls}")
  518. if format_urls:
  519. plist.append('#'.join(format_urls))
  520. names = ['线路1', '线路2'][:len(plist)]
  521. vod['vod_play_from'] = "$$$".join(names)
  522. vod['vod_play_url'] = "$$$".join(plist)
  523. return vod
  524. except Exception as e:
  525. return self.handle_exception(e)
  526. def douyuDetail(self, ids):
  527. headers = self.gethr(0, zr=f'{self.hosts[ids[0]]}/{ids[1]}')
  528. try:
  529. data = self.fetch(f'{self.hosts[ids[0]]}/betard/{ids[1]}', headers=headers).json()
  530. vname = data['room']['room_name']
  531. vod = self.buildvod(
  532. vod_name=vname,
  533. vod_remarks=data['room'].get('second_lvl_name'),
  534. vod_director=data['room'].get('nickname'),
  535. )
  536. vdata = self.fetch(f'{self.hosts[ids[0]]}/swf_api/homeH5Enc?rids={ids[1]}', headers=headers).json()
  537. json_body = vdata['data']
  538. json_body = {"html": self.douyu_text(json_body[f'room{ids[1]}']), "rid": ids[1]}
  539. sign = self.post('http://alive.nsapps.cn/api/AllLive/DouyuSign', json=json_body, headers=self.headers[1]).json()['data']
  540. body = f'{sign}&cdn=&rate=-1&ver=Douyu_223061205&iar=1&ive=1&hevc=0&fa=0'
  541. body=self.params_to_json(body)
  542. nubdata = self.post(f'{self.hosts[ids[0]]}/lapi/live/getH5Play/{ids[1]}', data=body, headers=headers).json()
  543. plist = []
  544. names = []
  545. for i,x in enumerate(nubdata['data']['cdnsWithName']):
  546. names.append(f'线路{i+1}')
  547. d = {'sign': sign, 'cdn': x['cdn'], 'id': ids[1]}
  548. plist.append(
  549. f'{vname}${ids[0]}@@{self.e64(json.dumps(d))}@@{self.e64(json.dumps(nubdata["data"]["multirates"]))}')
  550. vod['vod_play_from'] = "$$$".join(names)
  551. vod['vod_play_url'] = "$$$".join(plist)
  552. return vod
  553. except Exception as e:
  554. return self.handle_exception(e)
  555. def douyu_text(self, text):
  556. function_positions = [m.start() for m in re.finditer('function', text)]
  557. total_functions = len(function_positions)
  558. if total_functions % 2 == 0:
  559. target_index = total_functions // 2 + 1
  560. else:
  561. target_index = (total_functions - 1) // 2 + 1
  562. if total_functions >= target_index:
  563. cut_position = function_positions[target_index - 1]
  564. ctext = text[4:cut_position]
  565. return re.sub(r'eval\(strc\)\([\w\d,]+\)', 'strc', ctext)
  566. return text
  567. def searchContent(self, key, quick, pg="1"):
  568. pass
  569. def playerContent(self, flag, id, vipFlags):
  570. try:
  571. ids = id.split('@@')
  572. p = 1
  573. if ids[0] in ['wangyi', 'douyin','huya']:
  574. p, url = 0, json.loads(self.d64(ids[1]))
  575. elif ids[0] == 'bili':
  576. p, url = self.biliplay(ids)
  577. elif ids[0] == 'huya':
  578. p, url = 0, json.loads(self.d64(ids[1]))
  579. elif ids[0] == 'douyu':
  580. p, url = self.douyuplay(ids)
  581. return {'parse': p, 'url': url, 'header': self.playheaders[ids[0]]}
  582. except Exception as e:
  583. return {'parse': 1, 'url': self.excepturl, 'header': self.headers[0]}
  584. def biliplay(self, ids):
  585. try:
  586. data = self.fetch(
  587. f'{self.hosts[ids[0]][0]}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={ids[1]}&protocol=0,1&format=0,2&codec=0&platform=web&qn={ids[2]}',
  588. headers=self.gethr(0, ids[0])).json()
  589. urls = []
  590. line_index = 1
  591. for stream in data['data']['playurl_info']['playurl']['stream']:
  592. for format_item in stream['format']:
  593. for codec in format_item['codec']:
  594. for url_info in codec['url_info']:
  595. full_url = f"{url_info['host']}/{codec['base_url'].lstrip('/')}{url_info['extra']}"
  596. urls.extend([f"线路{line_index}", full_url])
  597. line_index += 1
  598. return 0, urls
  599. except Exception as e:
  600. return 1, self.excepturl
  601. def douyuplay(self, ids):
  602. try:
  603. sdata = json.loads(self.d64(ids[1]))
  604. headers = self.gethr(0, zr=f'{self.hosts[ids[0]]}/{sdata["id"]}')
  605. ldata = json.loads(self.d64(ids[2]))
  606. result_obj = {}
  607. with ThreadPoolExecutor(max_workers=len(ldata)) as executor:
  608. futures = [
  609. executor.submit(
  610. self.douyufp,
  611. sdata,
  612. quality,
  613. headers,
  614. self.hosts[ids[0]],
  615. result_obj
  616. ) for quality in ldata
  617. ]
  618. for future in futures:
  619. future.result()
  620. result = []
  621. for bit in sorted(result_obj.keys(), reverse=True):
  622. result.extend(result_obj[bit])
  623. if result:
  624. return 0, result
  625. return 1, self.excepturl
  626. except Exception as e:
  627. return 1, self.excepturl
  628. def douyufp(self, sdata, quality, headers, host, result_obj):
  629. try:
  630. body = f'{sdata["sign"]}&cdn={sdata["cdn"]}&rate={quality["rate"]}'
  631. body=self.params_to_json(body)
  632. data = self.post(f'{host}/lapi/live/getH5Play/{sdata["id"]}',
  633. data=body, headers=headers).json()
  634. if data.get('data'):
  635. play_url = data['data']['rtmp_url'] + '/' + data['data']['rtmp_live']
  636. bit = quality.get('bit', 0)
  637. if bit not in result_obj:
  638. result_obj[bit] = []
  639. result_obj[bit].extend([quality['name'], play_url])
  640. except Exception as e:
  641. print(f"Error fetching {quality['name']}: {str(e)}")
  642. def localProxy(self, param):
  643. pass
  644. def e64(self, text):
  645. try:
  646. text_bytes = text.encode('utf-8')
  647. encoded_bytes = b64encode(text_bytes)
  648. return encoded_bytes.decode('utf-8')
  649. except Exception as e:
  650. print(f"Base64编码错误: {str(e)}")
  651. return ""
  652. def d64(self, encoded_text):
  653. try:
  654. encoded_bytes = encoded_text.encode('utf-8')
  655. decoded_bytes = b64decode(encoded_bytes)
  656. return decoded_bytes.decode('utf-8')
  657. except Exception as e:
  658. print(f"Base64解码错误: {str(e)}")
  659. return ""
  660. def josn_to_params(self, params, skip_empty=False):
  661. query = []
  662. for k, v in params.items():
  663. if skip_empty and not v:
  664. continue
  665. query.append(f"{k}={v}")
  666. return "&".join(query)
  667. def params_to_json(self, query_string):
  668. parsed_data = parse_qs(query_string)
  669. result = {key: value[0] for key, value in parsed_data.items()}
  670. return result
  671. def buildvod(self, vod_id='', vod_name='', vod_pic='', vod_year='', vod_tag='', vod_remarks='', style='',
  672. type_name='', vod_area='', vod_actor='', vod_director='',
  673. vod_content='', vod_play_from='', vod_play_url=''):
  674. vod = {
  675. 'vod_id': vod_id,
  676. 'vod_name': vod_name,
  677. 'vod_pic': vod_pic,
  678. 'vod_year': vod_year,
  679. 'vod_tag': 'folder' if vod_tag else '',
  680. 'vod_remarks': vod_remarks,
  681. 'style': style,
  682. 'type_name': type_name,
  683. 'vod_area': vod_area,
  684. 'vod_actor': vod_actor,
  685. 'vod_director': vod_director,
  686. 'vod_content': vod_content,
  687. 'vod_play_from': vod_play_from,
  688. 'vod_play_url': vod_play_url
  689. }
  690. vod = {key: value for key, value in vod.items() if value}
  691. return vod
  692. def getpq(self, url, headers=None, cookies=None):
  693. data = self.fetch(url, headers=headers, cookies=cookies).text
  694. try:
  695. return pq(data)
  696. except Exception as e:
  697. print(f"解析页面错误: {str(e)}")
  698. return pq(data.encode('utf-8'))
  699. def gethr(self, index, rf='', zr=''):
  700. headers = self.headers[index]
  701. if zr:
  702. headers['referer'] = zr
  703. else:
  704. headers['referer'] = f"{self.referers[rf]}/"
  705. return headers
  706. def handle_exception(self, e):
  707. print(f"报错: {str(e)}")
  708. return {'vod_play_from': '哎呀翻车啦', 'vod_play_url': f'翻车啦${self.excepturl}'}