123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- # File : 哔滴影视.py
- # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
- # Author's Blog: https://blog.csdn.net/qq_32394351
- # Date : 2024/1/10
- import os.path
- import sys
- import requests
- sys.path.append('..')
- try:
- # from base.spider import Spider as BaseSpider
- from base.spider import BaseSpider
- except ImportError:
- from t4.base.spider import BaseSpider
- from pathlib import Path
- import base64
- from cachetools import cached, TTLCache # 可以缓存curd的函数,指定里面的key
- """
- 配置示例:
- t4的配置里ext节点会自动变成api对应query参数extend,但t4的ext字符串不支持路径格式,比如./开头或者.json结尾
- api里会自动含有ext参数是base64编码后的选中的筛选条件
- {
- "key":"hipy_t4_哔滴影视",
- "name":"哔滴影视(hipy_t4)",
- "type":4,
- "api":"http://192.168.31.49:5707/api/v1/vod/哔滴影视?api_ext={{host}}/txt/hipy/bidi.jar",
- "searchable":1,
- "quickSearch":0,
- "filterable":1,
- "ext":"{{host}}/files/hipy/jars/bidi.jar"
- },
- {
- "key": "hipy_t3_哔滴影视",
- "name": "哔滴影视(hipy_t3)",
- "type": 3,
- "api": "{{host}}/txt/hipy/哔滴影视.py",
- "searchable": 1,
- "quickSearch": 0,
- "filterable": 1,
- "ext": "{{host}}/files/hipy/jars/bidi.jar"
- },
- """
- def envkey(self, url: str):
- return url
- # 全局变量
- gParam = {
- "inited": False,
- }
- class Spider(BaseSpider): # 元类 默认的元类 type
- api: str = 'https://www.yjys.me/api/v1'
- javar = None
- def getDependence(self):
- return ['base_java_loader']
- def getName(self):
- return "哔滴影视"
- @cached(cache=TTLCache(maxsize=3, ttl=3600), key=envkey)
- def get_init_api(self, url):
- try:
- print('get_init_api请求URL:', url)
- r = self.fetch(url)
- ret = None
- if r.status_code == 200:
- self.log(f'url:{url},文件体积:{len(r.content)}')
- ret = r.content
- return ret
- except Exception as e:
- print(f'get_init_api请求URL发生错误:{e}')
- return {}
- def init_api_ext_file(self):
- """
- 这个函数用于初始化py文件对应的json文件,用于存筛选规则。
- 执行此函数会自动生成筛选文件
- @return:
- """
- pass
- def init(self, extend=""):
- """
- 初始化加载extend,一般与py文件名同名的json文件作为扩展筛选
- @param extend:
- @return:
- """
- global gParam
- ext = self.extend
- if isinstance(ext, str) and ext:
- if ext.endswith('.jar'):
- jar_path = os.path.join(os.path.dirname(__file__), './jars')
- os.makedirs(jar_path, exist_ok=True)
- # jar_file = os.path.join(os.path.dirname(__file__), './jars/bdys.jar')
- jar_file = os.path.join(os.path.dirname(__file__), './jars/bidi.jar')
- jar_file = Path(jar_file).as_posix()
- need_down = False
- msg = ''
- if not gParam['inited'] and not os.path.exists(jar_file):
- need_down = True
- msg = f'未inited,且文件不存在。开始下载文件'
- elif gParam['inited'] and not os.path.exists(jar_file):
- need_down = True
- msg = f'已inited,但文件不存在。开始下载文件'
- # elif not gParam['inited'] and os.path.exists(jar_file):
- # need_down = True
- # msg = f'未inited,但文件已存在。重新下载文件'
- if need_down:
- self.log(msg)
- if self.ENV.lower() == 't3':
- # ext = ext.replace('.jar', '.dex')
- pass
- content = self.get_init_api(ext)
- with open(jar_file, mode='wb+') as f:
- f.write(content)
- # 装载模块,这里只要一个就够了
- if isinstance(extend, list):
- for lib in extend:
- if '.Spider' in str(type(lib)):
- self.javar = lib
- break
- if self.javar:
- # jar_file = os.path.join(os.path.dirname(__file__), './jars/bdys.jar')
- jar_file = os.path.join(os.path.dirname(__file__), './jars/bidi.jar')
- jar_file = Path(jar_file).as_posix()
- self.javar.init_jar(jar_file)
- # self.class1 = self.javar.jClass('com.C4355b')
- self.token = str(self.javar.call_java('com.EncryptionUtils', 'getToken'))
- # self.class1 = self.javar.jClass('com.EncryptionUtils')
- # # class1 = self.class1() # 类实例化
- # class1 = self.class1
- # self.token = str(class1.getToken())
- # print(self.token)
- # # self.token = str(self.class1.getToken())
- self.headers.update({'token': self.token})
- gParam['inited'] = True
- def isVideo(self):
- """
- 返回是否为视频的匹配字符串
- @return: None空 reg:正则表达式 js:input js代码
- """
- return 'js:input.includes(".m3u8)?true:false'
- def isVideoFormat(self, url):
- pass
- def manualVideoCheck(self):
- pass
- def homeContent(self, filterable=False):
- """
- 获取首页分类及筛选数据
- @param filterable: 能否筛选,跟t3/t4配置里的filterable参数一致
- @return:
- """
- class_name = '电影&电视剧&动漫&综艺' # 静态分类名称拼接
- class_url = '0&1001&21&35' # 静态分类标识拼接
- result = {}
- classes = []
- if all([class_name, class_url]):
- class_names = class_name.split('&')
- class_urls = class_url.split('&')
- cnt = min(len(class_urls), len(class_names))
- for i in range(cnt):
- classes.append({
- 'type_name': class_names[i],
- 'type_id': class_urls[i]
- })
- result['class'] = classes
- if filterable:
- result['filters'] = self.config['filter']
- return result
- def homeVideoContent(self):
- """
- 首页推荐列表
- @return:
- """
- d = []
- d.append({
- 'vod_name': '测试',
- 'vod_id': 'index.html',
- 'vod_pic': 'https://gitee.com/CherishRx/imagewarehouse/raw/master/image/13096725fe56ce9cf643a0e4cd0c159c.gif',
- 'vod_remarks': '原始hipy',
- })
- result = {
- 'list': d
- }
- return result
- def categoryContent(self, tid, pg, filterable, extend):
- """
- 返回一级列表页数据
- @param tid: 分类id
- @param pg: 当前页数
- @param filterable: 能否筛选
- @param extend: 当前筛选数据
- @return:
- """
- url = self.api + f'/category/{tid}/{pg}?type=0'
- r = self.fetch(url, headers=self.headers)
- ret = r.json()
- data = self.decode(ret['data'])
- # print(data)
- page_count = 12 # 默认赋值一页列表12条数据|这个值一定要写正确看他默认一页多少条
- d = [{
- 'vod_name': vod['movieName'],
- 'vod_id': vod['id'],
- 'vod_pic': vod['cdnCover'],
- 'vod_remarks': vod['rank'],
- 'vod_content': vod['title'],
- } for vod in data['list']]
- result = {
- 'list': d,
- 'page': pg,
- 'pagecount': 9999 if len(d) >= page_count else pg,
- 'limit': 90,
- 'total': 999999,
- }
- return result
- def detailContent(self, ids):
- """
- 返回二级详情页数据
- @param ids: 一级传过来的vod_id列表
- @return:
- """
- vod_id = ids[0]
- url = self.api + f'/detail/{vod_id}'
- r = self.fetch(url, headers=self.headers)
- ret = r.json()
- data = self.decode(ret['data'])
- # print(self.json2str(data))
- vod = data['movie']
- playlist = data['playlist']
- titles = []
- plays = {}
- for p in playlist: # 选集列表
- title = p['title']
- titles.append(title)
- if not plays.get(title):
- plays[title] = []
- _type = '1' if p.get('tosId') else '0'
- purl = self.api + '/playurl/' + str(p['id']) + '?type=' + _type
- plays[title].append({'name': '至尊线路', 'url': f'vip://{purl}'})
- # if p.get('tosId'):
- # purl = self.api + '/playurl/' + str(p['id']) + '?type=' + str(p.get('tosId') or '0')
- # plays[title].append({'name': '至尊线路', 'url': f'vip://{purl}'})
- if p.get('url'):
- for p0 in p['url'].split(','):
- plays[title].append(
- {'name': p0.split('#')[1] if len(p0.split('#')) > 1 else '道长线路', 'url': p0.split('#')[0]})
- if p.get('url1'):
- for p1 in p['url1'].split(','):
- plays[title].append(
- {'name': p1.split('#')[1] if len(p1.split('#')) > 1 else '道长线路', 'url': p1.split('#')[0]})
- if p.get('url2'):
- for p2 in p['url2'].split(','):
- plays[title].append(
- {'name': p2.split('#')[1] if len(p2.split('#')) > 1 else '道长线路', 'url': p2.split('#')[0]})
- tabs = {}
- # key 选集列表 value是线路列表
- for key, value in plays.items():
- for tab in value:
- if not tab['name'] in tabs:
- tabs[tab['name']] = []
- tabs[tab['name']].append(f"{key}${tab['url']}")
- vod_play_from = '$$$'.join(tabs.keys())
- vod_play_urls = []
- for key, value in tabs.items():
- vod_play_urls.append('#'.join(value))
- vod_play_url = '$$$'.join(vod_play_urls)
- vod = {"vod_id": vod_id,
- "vod_name": vod['title'],
- "vod_pic": vod['cdnCover'],
- "type_name": ','.join(vod['m_type']),
- "vod_year": '',
- "vod_area": vod['area'],
- "vod_remarks": f"{vod['movieName']} {vod['rank']}",
- "vod_actor": ','.join(vod['m_performer']),
- "vod_director": ','.join(vod['m_director']),
- "vod_content": vod['intro'],
- "vod_play_from": vod_play_from,
- "vod_play_url": vod_play_url}
- result = {
- 'list': [vod]
- }
- return result
- def searchContent(self, wd, quick=False, pg=1):
- """
- 返回搜索列表
- @param wd: 搜索关键词
- @param quick: 是否来自快速搜索。t3/t4配置里启用了快速搜索,在快速搜索在执行才会是True
- @return:
- """
- url = self.api + f'/search/{wd}/{pg}'
- r = self.fetch(url, headers=self.headers)
- ret = r.json()
- data = self.decode(ret['data'])
- # print(data)
- d = []
- for li in data['list']:
- d.append({
- 'vod_name': li['movieName'],
- 'vod_id': li['id'],
- 'vod_pic': li['cdnCover'],
- 'vod_remarks': li['curEp'],
- 'vod_content': li['intro'],
- })
- result = {
- 'list': d
- }
- # print(result)
- return result
- def playerContent(self, flag, id, vipFlags):
- """
- 解析播放,返回json。壳子视情况播放直链或进行嗅探
- @param flag: vod_play_from 播放来源线路
- @param id: vod_play_url 播放的链接
- @param vipFlags: vip标识
- @return:
- """
- url = str(id)
- # 至尊线路
- if url.startswith('vip://'):
- purl = url.split('vip://')[1]
- # print(purl)
- r = self.fetch(purl, headers=self.headers)
- ret = r.json()
- data = self.decode(ret['data'])
- # print(data)
- url = data.get('url') or ''
- if not url:
- self.log(data)
- headers = {
- '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'
- }
- parse = 0
- if 'm3u8' in url:
- proxyUrl = self.getProxyUrl()
- if proxyUrl:
- url = proxyUrl + '&url=' + url + '&name=1.m3u8'
- elif '/obj/' in url:
- headers.update({
- 'Cookie': 'm=1',
- 'app': '1',
- 'Referer': 'https://doc.weixin.qq.com/',
- })
- result = {
- 'parse': parse, # 1=嗅探,0=播放
- 'playUrl': '', # 解析链接
- 'url': url, # 直链或待嗅探地址
- 'header': headers, # 播放UA
- }
- # print(result)
- return result
- config = {
- "player": {},
- "filter": {}
- }
- headers = {
- "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; HUAWEI MLA-AL10 Build/HUAWEIMLA-AL10)",
- "token": ""
- }
- def localProxy(self, params):
- # print(params)
- url = params.get('url')
- if not url:
- # return [302, 'text/html', None, {'location': 'https://www.baidu.com'}]
- # return [404, 'text/plain', 'Not Found']
- return [403, 'text/plain', '403 forbidden. url is required']
- name = params.get('name') or 'm3u8'
- burl = 'https://www.yjys.me'
- new_url = url.replace("www.bde4.cc", "www.yjys.me")
- self.log(f'原始url:{url},替换域名后url:{new_url}')
- headers = {
- "User-Agent": "BDPlayer",
- "Referer": burl,
- "Origin": burl,
- }
- r = self.fetch(new_url, headers=headers)
- pdata = self.process_data(r.content).decode('utf-8')
- # pdata = re.sub(r'(.*?ts)', r'https://www.yjys.me/\1', pdata)
- pdata = self.replaceAll(pdata, r'(.*?ts)', r'https://vod.bdys.me/\1')
- content = pdata.strip()
- media_type = 'text/plain' if 'txt' in name else 'video/MP2T'
- return [200, media_type, content]
- # -----------------------------------------------自定义函数-----------------------------------------------
- def decode(self, text):
- bt = base64.b64decode(text)
- # self.log(self.headers)
- if self.ENV.lower() == 't3':
- bt = self.javar.jarBytes(bt)
- res = self.javar.call_java('com.EncryptionUtils', 'dec', bt)
- # res = self.class1.dec(bt)
- # print(str(res))
- return self.str2json(str(res)) if res else None
- def process_data(self, req_bytes):
- """
- 个性化方法:跳过req返回的content 3354之前的字节并进行gzip解压
- @param req_bytes:
- @return:
- """
- stream = self.skip_bytes(req_bytes, 3354)
- decrypted_data = self.gzipCompress(stream)
- return decrypted_data
- if __name__ == '__main__':
- from t4.core.loader import t4_spider_init
- spider = Spider()
- t4_spider_init(spider)
- print(spider.ENV)
- # spider.init_api_ext_file() # 生成筛选对应的json文件
- # spider.log({'key': 'value'})
- # spider.log('====文本内容====')
- # print(spider.homeContent(True))
- # print(spider.homeVideoContent())
- # r = requests.head(
- # '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')
- # print(r.headers, r.content)
- # r = requests.get('https://www.bdys10.com/obj/63BEE3B148E464F16EE62435C53087B994902679D844EA9CC3615658CF55E01D',
- # headers={
- # 'Cookie': 'm=1',
- # 'app': '1',
- # 'Referer': 'https://doc.weixin.qq.com/',
- # })
- # print(r.text)
- # print(spider.categoryContent('0', 1, False, None))
- # print(spider.detailContent([24420]))
- print(spider.searchContent('斗罗大陆'))
- # print(spider.playerContent('至尊线路', 'vip://https://www.yjys.me/api/v1/playurl/174296?type=1', None))
- # print(spider.playerContent('需要解析',
- # 'https://www.bde4.cc/10E79044B82A84F70BE1308FFA5232E4DC3D0CA9EC2BF6B1D4EF56B2CE5B67CF238965CCAE17F859665B7E166720986D.m3u8',
- # None))
|