樱花动漫.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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/7
  7. import sys
  8. sys.path.append('..')
  9. try:
  10. # from base.spider import Spider as BaseSpider
  11. from base.spider import BaseSpider
  12. except ImportError:
  13. from t4.base.spider import BaseSpider
  14. from cachetools import cached, TTLCache # 可以缓存curd的函数,指定里面的key
  15. """
  16. 配置示例:
  17. t4的配置里ext节点会自动变成api对应query参数extend,但t4的ext字符串不支持路径格式,比如./开头或者.json结尾
  18. api里会自动含有ext参数是base64编码后的选中的筛选条件
  19. {
  20. "key":"hipy_t4_樱花动漫",
  21. "name":"樱花动漫(hipy_t4)",
  22. "type":4,
  23. "api":"http://192.168.31.49:5707/api/v1/vod/樱花动漫",
  24. "searchable":1,
  25. "quickSearch":0,
  26. "filterable":1,
  27. "ext":"https://jihulab.com/qiaoji/open/-/raw/main/yinghua"
  28. },
  29. {
  30. "key": "hipy_t3_樱花动漫",
  31. "name": "樱花动漫(hipy_t3)",
  32. "type": 3,
  33. "api": "{{host}}/txt/hipy/樱花动漫.py",
  34. "searchable": 1,
  35. "quickSearch": 0,
  36. "filterable": 1,
  37. "ext": "https://jihulab.com/qiaoji/open/-/raw/main/yinghua"
  38. },
  39. """
  40. def envkey(self, url: str):
  41. return url
  42. # 全局变量
  43. gParam = {
  44. "HomeDict": {},
  45. "TypeDict": {},
  46. }
  47. class Spider(BaseSpider): # 元类 默认的元类 type
  48. api_qj: str = 'https://jihulab.com/qiaoji/open/-/raw/main/yinghua'
  49. 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'
  50. key: str = 'fQiG3YWTpQEYHNFTxJXCBaZrcCkkpfxH'
  51. iv: str = '1238389483762837'
  52. token: str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBcHBUbyIsImlhdCI6MTcwMDA3MTcwMiwiZXhwIjoxNzMxNjA3NzAyLCJuYmYiOjE3MDAwNzE3MDIsInN1YiI6IkFwcFRvIiwianRpIjoiYzRjNTAzOTQxYTM4NWI1MDMyMTAyYmY3Yzk1OGY4MzEiLCJkYXRhIjp7InVzZXJfaWQiOjI0ODc1NCwidXNlcl9jaGVjayI6ImUzYmQ3NmNhNTJhMGY4NjAwMTdjNjdkZGUwN2QzZTM3IiwidXNlcl9uYW1lIjoiaGV6aWh1aSJ9fQ.4LWs3rNL-os8_Pqa9LgKtvVG5f0aIxVyAjYIagvO1F4'
  53. ic: str = 'bmXes2xsCWvsSdfYav0s9D78Ly7w1o%2BOYXApKx6SUd4NWKsTsapbS52l7y%2FsTVCM2kcoLws2jryaDQlHLse5fxD2B2VXZXfaQo0eMTOv2Xq7CKoPa51uVt8WiIY2SPztc7wxGE89%2Fcw2Q3n85uUT3A%3D%3D'
  54. api: str = 'http://60.204.185.245:7090/appto/v1'
  55. api_cofig: str = api + '/config/get?p=android'
  56. api_home: str = api + '/home/cateData?id=1'
  57. api_cate: str = api + '/vod/getLists'
  58. api_search: str = api + '/vod/getVodSearch'
  59. api_detail: str = api + '/vod/getVod?__platform=android&__ic=' + ic
  60. api_parse: str = api + '/parsing/proxy'
  61. def getName(self):
  62. return "樱花动漫"
  63. @cached(cache=TTLCache(maxsize=3, ttl=3600), key=envkey)
  64. def get_init_api(self, url):
  65. try:
  66. print('get_init_api请求URL:', url)
  67. r = self.fetch(url)
  68. ret = self.decode_rsa(r.text[1:])
  69. return ret
  70. except Exception as e:
  71. print(f'get_init_api请求URL发生错误:{e}')
  72. return {}
  73. def init_extend(self, url):
  74. ret = self.get_init_api(url)
  75. if ret.get('key'):
  76. self.key = ret.get('key')
  77. if ret.get('ic'):
  78. self.ic = ret.get('ic')
  79. if ret.get('token'):
  80. self.token = ret.get('token')
  81. if ret.get('url') and ret.get('api'):
  82. api = ret.get('url') + ret.get('api')
  83. self.api = api
  84. self.api_cofig: str = api + '/config/get?p=android'
  85. self.api_home: str = api + '/home/cateData?id=1'
  86. self.api_cate: str = api + '/vod/getLists'
  87. self.api_search: str = api + '/vod/getVodSearch'
  88. self.api_detail: str = api + '/vod/getVod?__platform=android&__ic=' + self.ic
  89. self.api_parse: str = api + '/parsing/proxy'
  90. def init_api_ext_file(self):
  91. """
  92. 这个函数用于初始化py文件对应的json文件,用于存筛选规则。
  93. 执行此函数会自动生成筛选文件
  94. @return:
  95. """
  96. ext_file = __file__.replace('.py', '.json')
  97. print(f'ext_file:{ext_file}')
  98. ext_file_dict = self.homeContent(True)['filters']
  99. with open(ext_file, mode='w+', encoding='utf-8') as f:
  100. f.write(self.json2str(ext_file_dict))
  101. def init(self, extend=""):
  102. """
  103. 初始化加载extend,一般与py文件名同名的json文件作为扩展筛选
  104. @param extend:
  105. @return:
  106. """
  107. ext = self.extend
  108. if ext.startswith('http'):
  109. self.init_extend(ext)
  110. else:
  111. self.init_extend(self.api_qj)
  112. # 装载模块,这里只要一个就够了
  113. if isinstance(extend, list):
  114. for lib in extend:
  115. if '.Spider' in str(type(lib)):
  116. self.module = lib
  117. break
  118. def isVideoFormat(self, url):
  119. pass
  120. def manualVideoCheck(self):
  121. pass
  122. def homeContent(self, filterable=False):
  123. """
  124. 获取首页分类及筛选数据
  125. @param filterable: 能否筛选,跟t3/t4配置里的filterable参数一致
  126. @return:
  127. """
  128. filter_names = {
  129. 'area': '地区',
  130. 'class': '分类',
  131. 'director': '导演',
  132. 'lang': '语言',
  133. 'star': '明星',
  134. 'state': '状态',
  135. 'version': '版本',
  136. 'year': '年份',
  137. }
  138. r = self.fetch(self.api_cofig)
  139. ret = r.json()
  140. data = self.decode(ret['data'])
  141. # print(data)
  142. result = {}
  143. classes = []
  144. filters = {}
  145. type_dict = {}
  146. for tp in data.get('get_type') or []:
  147. classes.append({
  148. 'type_name': tp['type_name'],
  149. 'type_id': tp['type_id']
  150. })
  151. type_dict[str(tp['type_id'])] = tp['type_name']
  152. tp_filters = []
  153. for key, value in tp['type_extend'].items():
  154. if value:
  155. tp_filters.append({
  156. 'key': key,
  157. 'name': filter_names.get(key) or key,
  158. 'value': [{'n': '全部', 'v': ''}] + [{'n': i, 'v': i} for i in value.split(',') if i]
  159. })
  160. filters[tp['type_id']] = tp_filters
  161. result['class'] = classes
  162. if filterable:
  163. result['filters'] = filters
  164. global gParam
  165. gParam['HomeDict'].update(result)
  166. gParam['TypeDict'].update(type_dict)
  167. return result
  168. def homeVideoContent(self):
  169. """
  170. 首页推荐列表
  171. @return:
  172. """
  173. r = self.fetch(self.api_home)
  174. ret = r.json()
  175. data = self.decode(ret['data'])
  176. # print(data)
  177. d = []
  178. for section in data['sections']:
  179. items = section['items']
  180. for item in items:
  181. d.append({
  182. 'vod_name': item['vod_name'],
  183. 'vod_id': item['vod_id'],
  184. 'vod_pic': item['vod_pic'],
  185. 'vod_remarks': item['vod_remarks'],
  186. })
  187. result = {
  188. 'list': d
  189. }
  190. return result
  191. def categoryContent(self, tid, pg, filterable, extend):
  192. """
  193. 返回一级列表页数据
  194. @param tid: 分类id
  195. @param pg: 当前页数
  196. @param filterable: 能否筛选
  197. @param extend: 当前筛选数据
  198. @return:
  199. """
  200. page_count = 21 # 默认赋值一页列表21条数据|这个值一定要写正确看他默认一页多少条
  201. fls = extend.keys() # 哪些刷新数据
  202. # ?type_id=1&area=&lang=&year=&order=time&type_name=&page=1&pageSize=21
  203. params = {'page': pg, 'pageSize': page_count, 'tid': tid, 'type_name': gParam['TypeDict'].get(str(tid)) or ''}
  204. for fl in fls:
  205. params[fl] = extend[fl]
  206. r = self.fetch(self.api_cate, data=params)
  207. print(r.url)
  208. ret = r.json()
  209. data = self.decode(ret['data'])
  210. d = data['data']
  211. result = {
  212. 'list': d,
  213. 'page': pg,
  214. 'pagecount': 9999 if len(d) >= page_count else pg,
  215. 'limit': 90,
  216. 'total': data['total'],
  217. }
  218. return result
  219. def detailContent(self, ids):
  220. """
  221. 返回二级详情页数据
  222. @param ids: 一级传过来的vod_id列表
  223. @return:
  224. """
  225. # id=110102
  226. vod_id = ids[0]
  227. params = {'id': vod_id}
  228. r = self.fetch(self.api_detail, data=params)
  229. print(r.url)
  230. ret = r.json()
  231. data = self.decode(ret['data'])
  232. # print(data)
  233. vod = {"vod_id": vod_id,
  234. "vod_name": data['vod_name'],
  235. "vod_pic": data['vod_pic'],
  236. "type_name": data['vod_en'],
  237. "vod_year": data['vod_year'],
  238. "vod_area": data['vod_area'],
  239. "vod_remarks": data['vod_remarks'],
  240. "vod_actor": data['vod_actor'],
  241. "vod_director": data['vod_director'],
  242. "vod_content": data['vod_blurb'],
  243. "vod_play_from": data['vod_play_from'],
  244. }
  245. vod_play_list = data['vod_play_list']
  246. vod_play_urls = []
  247. for vod_play in vod_play_list:
  248. v_from = vod_play['player_info']['from']
  249. v_show = vod_play['player_info']['show']
  250. vod_play_url = '#'.join(
  251. [url['name'] + '$' + '&&'.join([url['url'], v_from, v_show]) for url in vod_play['urls']])
  252. vod_play_urls.append(vod_play_url)
  253. vod['vod_play_url'] = '$$$'.join(vod_play_urls)
  254. result = {
  255. 'list': [vod]
  256. }
  257. # print(vod)
  258. return result
  259. def searchContent(self, wd, quick=False, pg=1):
  260. """
  261. 返回搜索列表
  262. @param wd: 搜索关键词
  263. @param quick: 是否来自快速搜索。t3/t4配置里启用了快速搜索,在快速搜索在执行才会是True
  264. @param pg: 页数
  265. @return:
  266. """
  267. # ?wd=%E4%B8%89%E5%A4%A7%E9%98%9F&page=1&type=
  268. params = {'wd': wd, 'type': '', 'page': pg}
  269. r = self.fetch(self.api_search, data=params)
  270. print(r.url)
  271. ret = r.json()
  272. data = self.decode(ret['data'])
  273. # print(data)
  274. d = data['data']
  275. result = {
  276. 'list': d
  277. }
  278. return result
  279. def playerContent(self, flag, id, vipFlags):
  280. """
  281. 解析播放,返回json。壳子视情况播放直链或进行嗅探
  282. @param flag: vod_play_from 播放来源线路
  283. @param id: vod_play_url 播放的链接
  284. @param vipFlags: vip标识
  285. @return:
  286. """
  287. headers = {
  288. 'Content-Type': 'multipart/form-data; boundary=--dio-boundary-1205762094',
  289. 'token': self.token,
  290. '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',
  291. }
  292. if '&&' in id:
  293. _v = id.split('&&')
  294. params = {'play_url': _v[0], 'label': _v[2], 'key': _v[1]}
  295. else:
  296. params = {'play_url': id, 'label': '主线', 'key': 'mp4'}
  297. # print(params)
  298. r = self.postBinary(self.api_parse, data=params, boundary='--dio-boundary-1205762094', headers=headers)
  299. # print(r.request.body.decode())
  300. ret = r.json()
  301. data = self.decode(ret['data'])
  302. # print(data)
  303. url = data['url']
  304. parse = 0
  305. result = {
  306. 'parse': parse, # 1=嗅探,0=播放
  307. 'playUrl': '', # 解析链接
  308. 'url': url, # 直链或待嗅探地址
  309. # 'header': headers, # 播放UA
  310. }
  311. return result
  312. config = {
  313. "player": {},
  314. "filter": {}
  315. }
  316. header = {
  317. "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",
  318. "Host": "www.baidu.com",
  319. "Referer": "https://www.baidu.com/"
  320. }
  321. def localProxy(self, params):
  322. return [200, "video/MP2T", ""]
  323. # -----------------------------------------------自定义函数-----------------------------------------------
  324. def decode(self, text):
  325. return self.str2json(self.aes_cbc_decode(text, self.key, self.iv))
  326. def decode_rsa(self, text):
  327. return self.str2json(self.rsa_private_decode(text, self.private_key))
  328. if __name__ == '__main__':
  329. # 在线aes测试 https://config.net.cn/tools/AES.html
  330. # 分类页:http://60.204.185.245:7090/appto/v1/home/cateData?id=1
  331. # 推荐页:http://60.204.185.245:7090/appto/v1/config/get?p=android
  332. from t4.core.loader import t4_spider_init
  333. spider = Spider()
  334. t4_spider_init(spider, 'https://jihulab.com/qiaoji/open/-/raw/main/yinghua')
  335. # spider.init_api_ext_file() # 生成筛选对应的json文件
  336. # print(spider.homeContent(True))
  337. # print(spider.homeVideoContent())
  338. # print(spider.categoryContent('1', 1, True, {'year': '2024'}))
  339. # print(spider.detailContent([110078]))
  340. print(spider.searchContent('斗罗大陆'))
  341. # print(spider.playerContent(None, 'f1d7d074f624e993e425f|11d1d091b0b28|31613145e4a7c|518737c8650978', None))
  342. # spider.searchContent('斗罗大陆')