哔滴影视.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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/10
  7. import os.path
  8. import sys
  9. import requests
  10. sys.path.append('..')
  11. try:
  12. # from base.spider import Spider as BaseSpider
  13. from base.spider import BaseSpider
  14. except ImportError:
  15. from t4.base.spider import BaseSpider
  16. from pathlib import Path
  17. import base64
  18. from cachetools import cached, TTLCache # 可以缓存curd的函数,指定里面的key
  19. """
  20. 配置示例:
  21. t4的配置里ext节点会自动变成api对应query参数extend,但t4的ext字符串不支持路径格式,比如./开头或者.json结尾
  22. api里会自动含有ext参数是base64编码后的选中的筛选条件
  23. {
  24. "key":"hipy_t4_哔滴影视",
  25. "name":"哔滴影视(hipy_t4)",
  26. "type":4,
  27. "api":"http://192.168.31.49:5707/api/v1/vod/哔滴影视?api_ext={{host}}/txt/hipy/bidi.jar",
  28. "searchable":1,
  29. "quickSearch":0,
  30. "filterable":1,
  31. "ext":"{{host}}/txt/hipy/bidi.jar"
  32. },
  33. {
  34. "key": "hipy_t3_哔滴影视",
  35. "name": "哔滴影视(hipy_t3)",
  36. "type": 3,
  37. "api": "{{host}}/txt/hipy/哔滴影视.py",
  38. "searchable": 1,
  39. "quickSearch": 0,
  40. "filterable": 1,
  41. "ext": "{{host}}/txt/hipy/bidi.jar"
  42. },
  43. """
  44. def envkey(self, url: str):
  45. return url
  46. # 全局变量
  47. gParam = {
  48. "inited": False,
  49. }
  50. class Spider(BaseSpider): # 元类 默认的元类 type
  51. api: str = 'https://www.bdys03.com/api/v1'
  52. javar = None
  53. def getDependence(self):
  54. return ['base_java_loader']
  55. def getName(self):
  56. return "哔滴影视"
  57. @cached(cache=TTLCache(maxsize=3, ttl=3600), key=envkey)
  58. def get_init_api(self, url):
  59. try:
  60. print('get_init_api请求URL:', url)
  61. r = self.fetch(url)
  62. ret = None
  63. if r.status_code == 200:
  64. self.log(f'url:{url},文件体积:{len(r.content)}')
  65. ret = r.content
  66. return ret
  67. except Exception as e:
  68. print(f'get_init_api请求URL发生错误:{e}')
  69. return {}
  70. def init_api_ext_file(self):
  71. """
  72. 这个函数用于初始化py文件对应的json文件,用于存筛选规则。
  73. 执行此函数会自动生成筛选文件
  74. @return:
  75. """
  76. pass
  77. def init(self, extend=""):
  78. """
  79. 初始化加载extend,一般与py文件名同名的json文件作为扩展筛选
  80. @param extend:
  81. @return:
  82. """
  83. global gParam
  84. ext = self.extend
  85. if isinstance(ext, str) and ext:
  86. if ext.endswith('.jar'):
  87. jar_path = os.path.join(os.path.dirname(__file__), './jars')
  88. os.makedirs(jar_path, exist_ok=True)
  89. # jar_file = os.path.join(os.path.dirname(__file__), './jars/bdys.jar')
  90. jar_file = os.path.join(os.path.dirname(__file__), './jars/bidi.jar')
  91. jar_file = Path(jar_file).as_posix()
  92. need_down = False
  93. msg = ''
  94. if not gParam['inited'] and not os.path.exists(jar_file):
  95. need_down = True
  96. msg = f'未inited,且文件不存在。开始下载文件'
  97. elif gParam['inited'] and not os.path.exists(jar_file):
  98. need_down = True
  99. msg = f'已inited,但文件不存在。开始下载文件'
  100. # elif not gParam['inited'] and os.path.exists(jar_file):
  101. # need_down = True
  102. # msg = f'未inited,但文件已存在。重新下载文件'
  103. if need_down:
  104. self.log(msg)
  105. if self.ENV.lower() == 't3':
  106. # ext = ext.replace('.jar', '.dex')
  107. pass
  108. content = self.get_init_api(ext)
  109. with open(jar_file, mode='wb+') as f:
  110. f.write(content)
  111. # 装载模块,这里只要一个就够了
  112. if isinstance(extend, list):
  113. for lib in extend:
  114. if '.Spider' in str(type(lib)):
  115. self.javar = lib
  116. break
  117. if self.javar:
  118. # jar_file = os.path.join(os.path.dirname(__file__), './jars/bdys.jar')
  119. jar_file = os.path.join(os.path.dirname(__file__), './jars/bidi.jar')
  120. jar_file = Path(jar_file).as_posix()
  121. self.javar.init_jar(jar_file)
  122. # self.class1 = self.javar.jClass('com.C4355b')
  123. self.token = str(self.javar.call_java('com.EncryptionUtils', 'getToken'))
  124. # self.class1 = self.javar.jClass('com.EncryptionUtils')
  125. # # class1 = self.class1() # 类实例化
  126. # class1 = self.class1
  127. # self.token = str(class1.getToken())
  128. # print(self.token)
  129. # # self.token = str(self.class1.getToken())
  130. self.headers.update({'token': self.token})
  131. gParam['inited'] = True
  132. def isVideo(self):
  133. """
  134. 返回是否为视频的匹配字符串
  135. @return: None空 reg:正则表达式 js:input js代码
  136. """
  137. return 'js:input.includes(".m3u8)?true:false'
  138. def isVideoFormat(self, url):
  139. pass
  140. def manualVideoCheck(self):
  141. pass
  142. def homeContent(self, filterable=False):
  143. """
  144. 获取首页分类及筛选数据
  145. @param filterable: 能否筛选,跟t3/t4配置里的filterable参数一致
  146. @return:
  147. """
  148. class_name = '电影&电视剧&动漫&综艺' # 静态分类名称拼接
  149. class_url = '0&1001&21&35' # 静态分类标识拼接
  150. result = {}
  151. classes = []
  152. if all([class_name, class_url]):
  153. class_names = class_name.split('&')
  154. class_urls = class_url.split('&')
  155. cnt = min(len(class_urls), len(class_names))
  156. for i in range(cnt):
  157. classes.append({
  158. 'type_name': class_names[i],
  159. 'type_id': class_urls[i]
  160. })
  161. result['class'] = classes
  162. if filterable:
  163. result['filters'] = self.config['filter']
  164. return result
  165. def homeVideoContent(self):
  166. """
  167. 首页推荐列表
  168. @return:
  169. """
  170. d = []
  171. d.append({
  172. 'vod_name': '测试',
  173. 'vod_id': 'index.html',
  174. 'vod_pic': 'https://gitee.com/CherishRx/imagewarehouse/raw/master/image/13096725fe56ce9cf643a0e4cd0c159c.gif',
  175. 'vod_remarks': '原始hipy',
  176. })
  177. result = {
  178. 'list': d
  179. }
  180. return result
  181. def categoryContent(self, tid, pg, filterable, extend):
  182. """
  183. 返回一级列表页数据
  184. @param tid: 分类id
  185. @param pg: 当前页数
  186. @param filterable: 能否筛选
  187. @param extend: 当前筛选数据
  188. @return:
  189. """
  190. url = self.api + f'/category/{tid}/{pg}?type=0'
  191. r = self.fetch(url, headers=self.headers)
  192. ret = r.json()
  193. data = self.decode(ret['data'])
  194. # print(data)
  195. page_count = 12 # 默认赋值一页列表12条数据|这个值一定要写正确看他默认一页多少条
  196. d = [{
  197. 'vod_name': vod['movieName'],
  198. 'vod_id': vod['id'],
  199. 'vod_pic': vod['cdnCover'],
  200. 'vod_remarks': vod['rank'],
  201. 'vod_content': vod['title'],
  202. } for vod in data['list']]
  203. result = {
  204. 'list': d,
  205. 'page': pg,
  206. 'pagecount': 9999 if len(d) >= page_count else pg,
  207. 'limit': 90,
  208. 'total': 999999,
  209. }
  210. return result
  211. def detailContent(self, ids):
  212. """
  213. 返回二级详情页数据
  214. @param ids: 一级传过来的vod_id列表
  215. @return:
  216. """
  217. vod_id = ids[0]
  218. url = self.api + f'/detail/{vod_id}'
  219. r = self.fetch(url, headers=self.headers)
  220. ret = r.json()
  221. data = self.decode(ret['data'])
  222. # print(self.json2str(data))
  223. vod = data['movie']
  224. playlist = data['playlist']
  225. titles = []
  226. plays = {}
  227. for p in playlist: # 选集列表
  228. title = p['title']
  229. titles.append(title)
  230. if not plays.get(title):
  231. plays[title] = []
  232. _type = '1' if p.get('tosId') else '0'
  233. purl = self.api + '/playurl/' + str(p['id']) + '?type=' + _type
  234. plays[title].append({'name': '至尊线路', 'url': f'vip://{purl}'})
  235. # if p.get('tosId'):
  236. # purl = self.api + '/playurl/' + str(p['id']) + '?type=' + str(p.get('tosId') or '0')
  237. # plays[title].append({'name': '至尊线路', 'url': f'vip://{purl}'})
  238. if p.get('url'):
  239. for p0 in p['url'].split(','):
  240. plays[title].append(
  241. {'name': p0.split('#')[1] if len(p0.split('#')) > 1 else '道长线路', 'url': p0.split('#')[0]})
  242. if p.get('url1'):
  243. for p1 in p['url1'].split(','):
  244. plays[title].append(
  245. {'name': p1.split('#')[1] if len(p1.split('#')) > 1 else '道长线路', 'url': p1.split('#')[0]})
  246. if p.get('url2'):
  247. for p2 in p['url2'].split(','):
  248. plays[title].append(
  249. {'name': p2.split('#')[1] if len(p2.split('#')) > 1 else '道长线路', 'url': p2.split('#')[0]})
  250. tabs = {}
  251. # key 选集列表 value是线路列表
  252. for key, value in plays.items():
  253. for tab in value:
  254. if not tab['name'] in tabs:
  255. tabs[tab['name']] = []
  256. tabs[tab['name']].append(f"{key}${tab['url']}")
  257. vod_play_from = '$$$'.join(tabs.keys())
  258. vod_play_urls = []
  259. for key, value in tabs.items():
  260. vod_play_urls.append('#'.join(value))
  261. vod_play_url = '$$$'.join(vod_play_urls)
  262. vod = {"vod_id": vod_id,
  263. "vod_name": vod['title'],
  264. "vod_pic": vod['cdnCover'],
  265. "type_name": ','.join(vod['m_type']),
  266. "vod_year": '',
  267. "vod_area": vod['area'],
  268. "vod_remarks": f"{vod['movieName']} {vod['rank']}",
  269. "vod_actor": ','.join(vod['m_performer']),
  270. "vod_director": ','.join(vod['m_director']),
  271. "vod_content": vod['intro'],
  272. "vod_play_from": vod_play_from,
  273. "vod_play_url": vod_play_url}
  274. result = {
  275. 'list': [vod]
  276. }
  277. return result
  278. def searchContent(self, wd, quick=False, pg=1):
  279. """
  280. 返回搜索列表
  281. @param wd: 搜索关键词
  282. @param quick: 是否来自快速搜索。t3/t4配置里启用了快速搜索,在快速搜索在执行才会是True
  283. @return:
  284. """
  285. url = self.api + f'/search/{wd}/{pg}'
  286. r = self.fetch(url, headers=self.headers)
  287. ret = r.json()
  288. data = self.decode(ret['data'])
  289. # print(data)
  290. d = []
  291. for li in data['list']:
  292. d.append({
  293. 'vod_name': li['movieName'],
  294. 'vod_id': li['id'],
  295. 'vod_pic': li['cdnCover'],
  296. 'vod_remarks': li['curEp'],
  297. 'vod_content': li['intro'],
  298. })
  299. result = {
  300. 'list': d
  301. }
  302. # print(result)
  303. return result
  304. def playerContent(self, flag, id, vipFlags):
  305. """
  306. 解析播放,返回json。壳子视情况播放直链或进行嗅探
  307. @param flag: vod_play_from 播放来源线路
  308. @param id: vod_play_url 播放的链接
  309. @param vipFlags: vip标识
  310. @return:
  311. """
  312. url = str(id)
  313. # 至尊线路
  314. if url.startswith('vip://'):
  315. purl = url.split('vip://')[1]
  316. # print(purl)
  317. r = self.fetch(purl, headers=self.headers)
  318. ret = r.json()
  319. data = self.decode(ret['data'])
  320. # print(data)
  321. url = data.get('url') or ''
  322. if not url:
  323. self.log(data)
  324. headers = {
  325. 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'
  326. }
  327. parse = 0
  328. if 'm3u8' in url:
  329. proxyUrl = self.getProxyUrl()
  330. if proxyUrl:
  331. url = proxyUrl + '&url=' + url + '&name=1.m3u8'
  332. elif '/obj/' in url:
  333. headers.update({
  334. 'Cookie': 'm=1',
  335. 'app': '1',
  336. 'Referer': 'https://doc.weixin.qq.com/',
  337. })
  338. result = {
  339. 'parse': parse, # 1=嗅探,0=播放
  340. 'playUrl': '', # 解析链接
  341. 'url': url, # 直链或待嗅探地址
  342. 'header': headers, # 播放UA
  343. }
  344. # print(result)
  345. return result
  346. config = {
  347. "player": {},
  348. "filter": {}
  349. }
  350. headers = {
  351. "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; HUAWEI MLA-AL10 Build/HUAWEIMLA-AL10)",
  352. "token": ""
  353. }
  354. def localProxy(self, params):
  355. # print(params)
  356. url = params.get('url')
  357. if not url:
  358. # return [302, 'text/html', None, {'location': 'https://www.baidu.com'}]
  359. # return [404, 'text/plain', 'Not Found']
  360. return [403, 'text/plain', '403 forbidden. url is required']
  361. name = params.get('name') or 'm3u8'
  362. burl = 'https://www.bdys03.com'
  363. new_url = url.replace("www.bde4.cc", "www.bdys03.com")
  364. self.log(f'原始url:{url},替换域名后url:{new_url}')
  365. headers = {
  366. "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36",
  367. "Referer": burl,
  368. "Origin": burl,
  369. }
  370. r = self.fetch(new_url, headers=headers)
  371. pdata = self.process_data(r.content).decode('utf-8')
  372. # pdata = re.sub(r'(.*?ts)', r'https://www.bdys03.com/\1', pdata)
  373. pdata = self.replaceAll(pdata, r'(.*?ts)', r'https://www.bdys03.com/\1')
  374. content = pdata.strip()
  375. media_type = 'text/plain' if 'txt' in name else 'video/MP2T'
  376. return [200, media_type, content]
  377. # -----------------------------------------------自定义函数-----------------------------------------------
  378. def decode(self, text):
  379. bt = base64.b64decode(text)
  380. # self.log(self.headers)
  381. if self.ENV.lower() == 't3':
  382. bt = self.javar.jarBytes(bt)
  383. res = self.javar.call_java('com.EncryptionUtils', 'dec', bt)
  384. # res = self.class1.dec(bt)
  385. # print(str(res))
  386. return self.str2json(str(res)) if res else None
  387. def process_data(self, req_bytes):
  388. """
  389. 个性化方法:跳过req返回的content 3354之前的字节并进行gzip解压
  390. @param req_bytes:
  391. @return:
  392. """
  393. stream = self.skip_bytes(req_bytes, 3354)
  394. decrypted_data = self.gzipCompress(stream)
  395. return decrypted_data
  396. if __name__ == '__main__':
  397. from t4.core.loader import t4_spider_init
  398. spider = Spider()
  399. t4_spider_init(spider)
  400. print(spider.ENV)
  401. # spider.init_api_ext_file() # 生成筛选对应的json文件
  402. # spider.log({'key': 'value'})
  403. # spider.log('====文本内容====')
  404. # print(spider.homeContent(True))
  405. # print(spider.homeVideoContent())
  406. r = requests.head(
  407. 'http://192.168.31.49:5707/api/v1/vod/%E5%93%94%E6%BB%B4%E5%BD%B1%E8%A7%86?proxy=1&do=py&url=https://www.bde4.cc/10E79044B82A84F70BE1308FFA5232E4DC3D0CA9EC2BF6B1D4EF56B2CE5B67CF238965CCAE17F859665B7E166720986D.m3u8')
  408. print(r.headers, r.content)
  409. r = requests.get('https://www.bdys10.com/obj/63BEE3B148E464F16EE62435C53087B994902679D844EA9CC3615658CF55E01D',
  410. headers={
  411. 'Cookie': 'm=1',
  412. 'app': '1',
  413. 'Referer': 'https://doc.weixin.qq.com/',
  414. })
  415. print(r.text)
  416. # print(spider.categoryContent('0', 1, False, None))
  417. # print(spider.detailContent([24420]))
  418. # spider.searchContent('斗罗大陆')
  419. # print(spider.playerContent('至尊线路', 'vip://https://www.bdys03.com/api/v1/playurl/174296?type=1', None))
  420. # print(spider.playerContent('需要解析',
  421. # 'https://www.bde4.cc/10E79044B82A84F70BE1308FFA5232E4DC3D0CA9EC2BF6B1D4EF56B2CE5B67CF238965CCAE17F859665B7E166720986D.m3u8',
  422. # None))