mirror of
https://github.com/hjdhnx/dr_py.git
synced 2024-10-18 02:48:26 -05:00
376 lines
15 KiB
Python
376 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
# File : 樱花动漫.py
|
||
# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
|
||
# Author's Blog: https://blog.csdn.net/qq_32394351
|
||
# Date : 2024/1/7
|
||
|
||
import sys
|
||
|
||
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 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/樱花动漫",
|
||
"searchable":1,
|
||
"quickSearch":0,
|
||
"filterable":1,
|
||
"ext":"https://jihulab.com/qiaoji/open/-/raw/main/yinghua"
|
||
},
|
||
{
|
||
"key": "hipy_t3_樱花动漫",
|
||
"name": "樱花动漫(hipy_t3)",
|
||
"type": 3,
|
||
"api": "{{host}}/txt/hipy/樱花动漫.py",
|
||
"searchable": 1,
|
||
"quickSearch": 0,
|
||
"filterable": 1,
|
||
"ext": "https://jihulab.com/qiaoji/open/-/raw/main/yinghua"
|
||
},
|
||
"""
|
||
|
||
|
||
def envkey(self, url: str):
|
||
return url
|
||
|
||
|
||
# 全局变量
|
||
gParam = {
|
||
"HomeDict": {},
|
||
"TypeDict": {},
|
||
}
|
||
|
||
|
||
class Spider(BaseSpider): # 元类 默认的元类 type
|
||
api_qj: str = 'https://jihulab.com/qiaoji/open/-/raw/main/yinghua'
|
||
private_key: str = 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDA+5YTt3w1q/0WGw+TWyCSHTAeYiwBqAqDWot1u/1hoeANpED8gtW1AxB1mYNDQ+9eR8Ml+JC13+ME6RHjEbN4+n9V9OP90c81G0qSjBQ/DKQiMIFjbTH97RjVMtswf96tqwe4Rs/DT2ym6MP4P7QvJcxrFz5VVQXyOtUxhpMc9oktWuk0XKE8Mozu1FM879RknlM6WmJL85Wl/BnZrd+/AQbzziceELGrBfjbc1UOFAxYq2kA10H3o+Z4oOIODxUtXeh4R2oH3vHb4Ynnw6reXED5KsE3u1EO5HMQZyN16TZMTIps32bPe+vQlAT6V5nGcqXGT9fntjqIxJB0T9G3AgMBAAECggEBAKP6Yuh4BZP5g0CwV8jHKuLc6FE469mwdtZsLooo5cF68c3Fnu6xIXQAmZDDk3SpmhCLe7edASF5jwZSIL/H/68xcteQEdZP2/htKy1g16dHT4Q5oQfh9hOkznACGZuZW5ZH+HRNvyZfK5ybtkEPqERTouHwSyfo6feMpDDD/+cf3h1//7JKXKA7JPEU420YucsjQwjMuu5xdPa0TPqEc5mIbOBj753Pzn4GCScM+FRqJWr2x8e+KDPcPY8CUDLBSWxGLsB0A7+bEq/EiAQkbx09QKTwwxRLgVXjBbvyPB8BOuJpPM9BHx+vFcm5WSbkJdRI4qVFtEdsN/gDfFkwcjkCgYEA8Z8i/fTFRnzyvp9Pp8E+bSaYlvpTLUZ1KYNStaDg/BqlYGgGK1Jh90qjvRbBoiIjeBQd3IFLT4pFdd7Z9drLFdvqB22SNeVQU57kir/B6NY5G7yOjXB4qN17F4S3GubYIEcjF0W1tG/uOqqzb8FxrLJTK8WiFudbBt2ioCO4pJsCgYEAzHd8MctmD1Z1eM/xusvX1yCwGpxBuHT+ymThzLXyI6Ej0Q50jOQlf3cTyY/FgGbvAMz+oBybkEwE80gu7CPi0WPs+yCpAIB4+Th7afsrRylQI1ZWoRovaRmsyjnkIw0Mnj06VYNYPtkzm/OViRIqf4ESTTGas24bDm5DuwM9gxUCgYBwg4BR7gdnWYvYRGtdXNlrDowD0jGlZaftWt/LAE2EWAwmpooo5kYEV9eDl/M3QtptckCti++77FGIH+wzVl03op6KMvXg7xXGurkF+2GawRb62YUwS+2EBQ7q1rxFZLXD4hxvG+EPUwgGfbLtGZGLr8aXHYLrU3TJ769pDvlOfQKBgAFlAzzXtU9/eHele3GZuFQoTeswi6Y1bhN1UrDxwMALdlITtinL2JGg/0qNp3wzt4ea3lW7PDhkvFfocyF7MS3ab6Ba3aw6NBkHEJhtdSMcHgbPrPGWWyJtYWdTs8GlciOWKVKx/aUYGCkFJUz1CcMq3zQVlYeJxbd4ew/Iet/tAoGBAMRfvG1iLQAlS3AGaQeRwVxnvpciDn+7/sUCf8DEOk8Bqg4/ytJDTDrWufCtwmpsXmp6AUQig9mNKj7z26wSNbwYdzPsncK+sGRlS7eLAzzcv1a+1pghOOGDuQNzwlFOcauhkrcqjeKmu7OiKD48pvh3ZICiIWS1YL7LuMfUwHRJ'
|
||
key: str = 'fQiG3YWTpQEYHNFTxJXCBaZrcCkkpfxH'
|
||
iv: str = '1238389483762837'
|
||
token: str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBcHBUbyIsImlhdCI6MTcwMDA3MTcwMiwiZXhwIjoxNzMxNjA3NzAyLCJuYmYiOjE3MDAwNzE3MDIsInN1YiI6IkFwcFRvIiwianRpIjoiYzRjNTAzOTQxYTM4NWI1MDMyMTAyYmY3Yzk1OGY4MzEiLCJkYXRhIjp7InVzZXJfaWQiOjI0ODc1NCwidXNlcl9jaGVjayI6ImUzYmQ3NmNhNTJhMGY4NjAwMTdjNjdkZGUwN2QzZTM3IiwidXNlcl9uYW1lIjoiaGV6aWh1aSJ9fQ.4LWs3rNL-os8_Pqa9LgKtvVG5f0aIxVyAjYIagvO1F4'
|
||
ic: str = 'bmXes2xsCWvsSdfYav0s9D78Ly7w1o%2BOYXApKx6SUd4NWKsTsapbS52l7y%2FsTVCM2kcoLws2jryaDQlHLse5fxD2B2VXZXfaQo0eMTOv2Xq7CKoPa51uVt8WiIY2SPztc7wxGE89%2Fcw2Q3n85uUT3A%3D%3D'
|
||
api: str = 'http://60.204.185.245:7090/appto/v1'
|
||
api_cofig: str = api + '/config/get?p=android'
|
||
api_home: str = api + '/home/cateData?id=1'
|
||
api_cate: str = api + '/vod/getLists'
|
||
api_search: str = api + '/vod/getVodSearch'
|
||
api_detail: str = api + '/vod/getVod?__platform=android&__ic=' + ic
|
||
api_parse: str = api + '/parsing/proxy'
|
||
|
||
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 = self.decode_rsa(r.text[1:])
|
||
return ret
|
||
except Exception as e:
|
||
print(f'get_init_api请求URL发生错误:{e}')
|
||
return {}
|
||
|
||
def init_extend(self, url):
|
||
ret = self.get_init_api(url)
|
||
if ret.get('key'):
|
||
self.key = ret.get('key')
|
||
if ret.get('ic'):
|
||
self.ic = ret.get('ic')
|
||
if ret.get('token'):
|
||
self.token = ret.get('token')
|
||
if ret.get('url') and ret.get('api'):
|
||
api = ret.get('url') + ret.get('api')
|
||
self.api = api
|
||
self.api_cofig: str = api + '/config/get?p=android'
|
||
self.api_home: str = api + '/home/cateData?id=1'
|
||
self.api_cate: str = api + '/vod/getLists'
|
||
self.api_search: str = api + '/vod/getVodSearch'
|
||
self.api_detail: str = api + '/vod/getVod?__platform=android&__ic=' + self.ic
|
||
self.api_parse: str = api + '/parsing/proxy'
|
||
|
||
def init_api_ext_file(self):
|
||
"""
|
||
这个函数用于初始化py文件对应的json文件,用于存筛选规则。
|
||
执行此函数会自动生成筛选文件
|
||
@return:
|
||
"""
|
||
ext_file = __file__.replace('.py', '.json')
|
||
print(f'ext_file:{ext_file}')
|
||
ext_file_dict = self.homeContent(True)['filters']
|
||
with open(ext_file, mode='w+', encoding='utf-8') as f:
|
||
f.write(self.json2str(ext_file_dict))
|
||
|
||
def init(self, extend=""):
|
||
"""
|
||
初始化加载extend,一般与py文件名同名的json文件作为扩展筛选
|
||
@param extend:
|
||
@return:
|
||
"""
|
||
ext = self.extend
|
||
if ext.startswith('http'):
|
||
self.init_extend(ext)
|
||
else:
|
||
self.init_extend(self.api_qj)
|
||
|
||
# 装载模块,这里只要一个就够了
|
||
if isinstance(extend, list):
|
||
for lib in extend:
|
||
if '.Spider' in str(type(lib)):
|
||
self.module = lib
|
||
break
|
||
|
||
def isVideoFormat(self, url):
|
||
pass
|
||
|
||
def manualVideoCheck(self):
|
||
pass
|
||
|
||
def homeContent(self, filterable=False):
|
||
"""
|
||
获取首页分类及筛选数据
|
||
@param filterable: 能否筛选,跟t3/t4配置里的filterable参数一致
|
||
@return:
|
||
"""
|
||
filter_names = {
|
||
'area': '地区',
|
||
'class': '分类',
|
||
'director': '导演',
|
||
'lang': '语言',
|
||
'star': '明星',
|
||
'state': '状态',
|
||
'version': '版本',
|
||
'year': '年份',
|
||
}
|
||
r = self.fetch(self.api_cofig)
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
# print(data)
|
||
result = {}
|
||
classes = []
|
||
filters = {}
|
||
type_dict = {}
|
||
for tp in data.get('get_type') or []:
|
||
classes.append({
|
||
'type_name': tp['type_name'],
|
||
'type_id': tp['type_id']
|
||
})
|
||
type_dict[str(tp['type_id'])] = tp['type_name']
|
||
tp_filters = []
|
||
for key, value in tp['type_extend'].items():
|
||
if value:
|
||
tp_filters.append({
|
||
'key': key,
|
||
'name': filter_names.get(key) or key,
|
||
'value': [{'n': '全部', 'v': ''}] + [{'n': i, 'v': i} for i in value.split(',') if i]
|
||
})
|
||
filters[tp['type_id']] = tp_filters
|
||
|
||
result['class'] = classes
|
||
if filterable:
|
||
result['filters'] = filters
|
||
global gParam
|
||
gParam['HomeDict'].update(result)
|
||
gParam['TypeDict'].update(type_dict)
|
||
return result
|
||
|
||
def homeVideoContent(self):
|
||
"""
|
||
首页推荐列表
|
||
@return:
|
||
"""
|
||
r = self.fetch(self.api_home)
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
# print(data)
|
||
d = []
|
||
for section in data['sections']:
|
||
items = section['items']
|
||
for item in items:
|
||
d.append({
|
||
'vod_name': item['vod_name'],
|
||
'vod_id': item['vod_id'],
|
||
'vod_pic': item['vod_pic'],
|
||
'vod_remarks': item['vod_remarks'],
|
||
})
|
||
result = {
|
||
'list': d
|
||
}
|
||
return result
|
||
|
||
def categoryContent(self, tid, pg, filterable, extend):
|
||
"""
|
||
返回一级列表页数据
|
||
@param tid: 分类id
|
||
@param pg: 当前页数
|
||
@param filterable: 能否筛选
|
||
@param extend: 当前筛选数据
|
||
@return:
|
||
"""
|
||
page_count = 21 # 默认赋值一页列表21条数据|这个值一定要写正确看他默认一页多少条
|
||
fls = extend.keys() # 哪些刷新数据
|
||
# ?type_id=1&area=&lang=&year=&order=time&type_name=&page=1&pageSize=21
|
||
params = {'page': pg, 'pageSize': page_count, 'tid': tid, 'type_name': gParam['TypeDict'].get(str(tid)) or ''}
|
||
for fl in fls:
|
||
params[fl] = extend[fl]
|
||
r = self.fetch(self.api_cate, data=params)
|
||
print(r.url)
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
d = data['data']
|
||
result = {
|
||
'list': d,
|
||
'page': pg,
|
||
'pagecount': 9999 if len(d) >= page_count else pg,
|
||
'limit': 90,
|
||
'total': data['total'],
|
||
}
|
||
return result
|
||
|
||
def detailContent(self, ids):
|
||
"""
|
||
返回二级详情页数据
|
||
@param ids: 一级传过来的vod_id列表
|
||
@return:
|
||
"""
|
||
# id=110102
|
||
vod_id = ids[0]
|
||
params = {'id': vod_id}
|
||
r = self.fetch(self.api_detail, data=params)
|
||
print(r.url)
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
# print(data)
|
||
vod = {"vod_id": vod_id,
|
||
"vod_name": data['vod_name'],
|
||
"vod_pic": data['vod_pic'],
|
||
"type_name": data['vod_en'],
|
||
"vod_year": data['vod_year'],
|
||
"vod_area": data['vod_area'],
|
||
"vod_remarks": data['vod_remarks'],
|
||
"vod_actor": data['vod_actor'],
|
||
"vod_director": data['vod_director'],
|
||
"vod_content": data['vod_blurb'],
|
||
"vod_play_from": data['vod_play_from'],
|
||
}
|
||
vod_play_list = data['vod_play_list']
|
||
vod_play_urls = []
|
||
for vod_play in vod_play_list:
|
||
v_from = vod_play['player_info']['from']
|
||
v_show = vod_play['player_info']['show']
|
||
vod_play_url = '#'.join(
|
||
[url['name'] + '$' + '&&'.join([url['url'], v_from, v_show]) for url in vod_play['urls']])
|
||
vod_play_urls.append(vod_play_url)
|
||
vod['vod_play_url'] = '$$$'.join(vod_play_urls)
|
||
result = {
|
||
'list': [vod]
|
||
}
|
||
# print(vod)
|
||
return result
|
||
|
||
def searchContent(self, wd, quick=False, pg=1):
|
||
"""
|
||
返回搜索列表
|
||
@param wd: 搜索关键词
|
||
@param quick: 是否来自快速搜索。t3/t4配置里启用了快速搜索,在快速搜索在执行才会是True
|
||
@param pg: 页数
|
||
@return:
|
||
"""
|
||
# ?wd=%E4%B8%89%E5%A4%A7%E9%98%9F&page=1&type=
|
||
params = {'wd': wd, 'type': '', 'page': pg}
|
||
r = self.fetch(self.api_search, data=params)
|
||
print(r.url)
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
# print(data)
|
||
d = data['data']
|
||
result = {
|
||
'list': d
|
||
}
|
||
return result
|
||
|
||
def playerContent(self, flag, id, vipFlags):
|
||
"""
|
||
解析播放,返回json。壳子视情况播放直链或进行嗅探
|
||
@param flag: vod_play_from 播放来源线路
|
||
@param id: vod_play_url 播放的链接
|
||
@param vipFlags: vip标识
|
||
@return:
|
||
"""
|
||
headers = {
|
||
'Content-Type': 'multipart/form-data; boundary=--dio-boundary-1205762094',
|
||
'token': self.token,
|
||
'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',
|
||
}
|
||
if '&&' in id:
|
||
_v = id.split('&&')
|
||
params = {'play_url': _v[0], 'label': _v[2], 'key': _v[1]}
|
||
else:
|
||
params = {'play_url': id, 'label': '主线', 'key': 'mp4'}
|
||
# print(params)
|
||
r = self.postBinary(self.api_parse, data=params, boundary='--dio-boundary-1205762094', headers=headers)
|
||
# print(r.request.body.decode())
|
||
ret = r.json()
|
||
data = self.decode(ret['data'])
|
||
# print(data)
|
||
url = data['url']
|
||
parse = 0
|
||
result = {
|
||
'parse': parse, # 1=嗅探,0=播放
|
||
'playUrl': '', # 解析链接
|
||
'url': url, # 直链或待嗅探地址
|
||
# 'header': headers, # 播放UA
|
||
}
|
||
return result
|
||
|
||
config = {
|
||
"player": {},
|
||
"filter": {}
|
||
}
|
||
header = {
|
||
"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",
|
||
"Host": "www.baidu.com",
|
||
"Referer": "https://www.baidu.com/"
|
||
}
|
||
|
||
def localProxy(self, params):
|
||
return [200, "video/MP2T", ""]
|
||
|
||
# -----------------------------------------------自定义函数-----------------------------------------------
|
||
def decode(self, text):
|
||
return self.str2json(self.aes_cbc_decode(text, self.key, self.iv))
|
||
|
||
def decode_rsa(self, text):
|
||
return self.str2json(self.rsa_private_decode(text, self.private_key))
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# 在线aes测试 https://config.net.cn/tools/AES.html
|
||
# 分类页:http://60.204.185.245:7090/appto/v1/home/cateData?id=1
|
||
# 推荐页:http://60.204.185.245:7090/appto/v1/config/get?p=android
|
||
from t4.core.loader import t4_spider_init
|
||
|
||
spider = Spider()
|
||
t4_spider_init(spider, 'https://jihulab.com/qiaoji/open/-/raw/main/yinghua')
|
||
# spider.init_api_ext_file() # 生成筛选对应的json文件
|
||
|
||
# print(spider.homeContent(True))
|
||
# print(spider.homeVideoContent())
|
||
# print(spider.categoryContent('1', 1, True, {'year': '2024'}))
|
||
# print(spider.detailContent([110078]))
|
||
print(spider.searchContent('斗罗大陆'))
|
||
# print(spider.playerContent(None, 'f1d7d074f624e993e425f|11d1d091b0b28|31613145e4a7c|518737c8650978', None))
|
||
# spider.searchContent('斗罗大陆')
|