vod.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # File : vod.py
  4. # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
  5. # Date : 2022/9/6
  6. import functools
  7. import json
  8. from flask import Blueprint,abort,request,render_template,render_template_string,jsonify,make_response,redirect,current_app
  9. from time import time
  10. from utils.web import getParmas,get_interval
  11. from utils.cfg import cfg
  12. from utils.env import get_env
  13. from js.rules import getRuleLists,getJxs
  14. from base.R import R
  15. from utils.log import logger
  16. from utils import parser
  17. from controllers.cms import CMS
  18. from base.database import db
  19. from models.ruleclass import RuleClass
  20. from models.playparse import PlayParse
  21. from js.rules import getRules
  22. from controllers.service import storage_service,rules_service
  23. from concurrent.futures import ThreadPoolExecutor,as_completed,thread # 引入线程池
  24. from quickjs import Function,Context
  25. import ujson
  26. vod = Blueprint("vod", __name__)
  27. def search_one_py(rule, wd, before: str = ''):
  28. t1 = time()
  29. if not before:
  30. with open('js/模板.js', encoding='utf-8') as f:
  31. before = f.read().split('export')[0]
  32. js_path = f'js/{rule}.js'
  33. try:
  34. ctx, js_code = parser.runJs(js_path, before=before)
  35. if not js_code:
  36. return None
  37. ruleDict = ctx.rule.to_dict()
  38. ruleDict['id'] = rule # 把路由请求的id装到字典里,后面播放嗅探才能用
  39. logger.info(f'规则{rule}装载耗时:{get_interval(t1)}毫秒')
  40. cms = CMS(ruleDict, db, RuleClass, PlayParse, cfg)
  41. data = cms.searchContent(wd, show_name=True)
  42. return data
  43. except Exception as e:
  44. print(f'{rule}发生错误:{e}')
  45. return None
  46. def search_one(rule, wd, before: str = '',env:dict=None,app=None):
  47. t1 = time()
  48. if not before:
  49. with open('js/模板.js', encoding='utf-8') as f:
  50. before = f.read().split('export')[0]
  51. end_code = """\nif (rule.模板 && muban.hasOwnProperty(rule.模板)) {rule = Object.assign(muban[rule.模板], rule);}"""
  52. js_path = f'js/{rule}.js'
  53. ctx = Context()
  54. try:
  55. with open(js_path, encoding='utf-8') as f2:
  56. jscode = f2.read()
  57. if env:
  58. # 渲染字符串文本 render_template_string 必须带 flask的上下文
  59. with app.app_context():
  60. for k in env:
  61. # print(f'${k}', f'{env[k]}')
  62. if f'${k}' in jscode:
  63. jscode = jscode.replace(f'${k}', f'{env[k]}')
  64. # jscode = render_template_string(jscode, **env)
  65. # if '007' in rule:
  66. # print(rule,jscode)
  67. jscode = before + jscode + end_code
  68. # print(jscode)
  69. ctx.eval(jscode)
  70. js_ret = ctx.get('rule')
  71. ruleDict = ujson.loads(js_ret.json())
  72. ruleDict['id'] = rule # 把路由请求的id装到字典里,后面播放嗅探才能用
  73. logger.info(f'规则{rule}装载耗时:{get_interval(t1)}毫秒')
  74. cms = CMS(ruleDict, db, RuleClass, PlayParse, cfg)
  75. data = cms.searchContent(wd, show_name=True)
  76. return data
  77. except Exception as e:
  78. logger.info(f'{e}')
  79. return R.failed('爬虫规则加载失败')
  80. def multi_search2(wd):
  81. t1 = time()
  82. lsg = storage_service()
  83. try:
  84. timeout = round(int(lsg.getItem('SEARCH_TIMEOUT', 5000)) / 1000, 2)
  85. except:
  86. timeout = 5
  87. rules = getRules('js')['list']
  88. rule_names = list(map(lambda x: x['name'], rules))
  89. rules_exclude = ['drpy']
  90. new_rules = list(filter(lambda x: x.get('searchable', 0) and x.get('name', '') not in rules_exclude, rules))
  91. search_sites = [new_rule['name'] for new_rule in new_rules]
  92. nosearch_sites = set(rule_names) ^ set(search_sites)
  93. nosearch_sites.remove('drpy')
  94. # print(nosearch_sites)
  95. logger.info(f'开始聚搜{wd},共计{len(search_sites)}个规则,聚搜超时{timeout}秒')
  96. logger.info(f'不支持聚搜的规则,共计{len(nosearch_sites)}个规则:{",".join(nosearch_sites)}')
  97. # print(search_sites)
  98. res = []
  99. with open('js/模板.js', encoding='utf-8') as f:
  100. before = f.read().split('export')[0]
  101. logger.info(f'聚搜准备工作耗时:{get_interval(t1)}毫秒')
  102. t2 = time()
  103. thread_pool = ThreadPoolExecutor(len(search_sites)) # 定义线程池来启动多线程执行此任务
  104. obj_list = []
  105. try:
  106. for site in search_sites:
  107. obj = thread_pool.submit(search_one, site, wd, before)
  108. obj_list.append(obj)
  109. thread_pool.shutdown(wait=True) # 等待所有子线程并行完毕
  110. vod_list = [obj.result() for obj in obj_list]
  111. for vod in vod_list:
  112. if vod and isinstance(vod, dict) and vod.get('list') and len(vod['list']) > 0:
  113. res.extend(vod['list'])
  114. result = {
  115. 'list': res
  116. }
  117. logger.info(f'drpy聚搜{len(search_sites)}个源耗时{get_interval(t2)}毫秒,含准备共计耗时{get_interval(t1)}毫秒')
  118. except Exception as e:
  119. result = {
  120. 'list': []
  121. }
  122. logger.info(f'drpy聚搜{len(search_sites)}个源耗时{get_interval(t2)}毫秒,含准备共计耗时:{get_interval(t1)}毫秒,发生错误:{e}')
  123. return jsonify(result)
  124. def merged_hide(merged_rules):
  125. t1 = time()
  126. store_rule = rules_service()
  127. hide_rules = store_rule.getHideRules()
  128. hide_rule_names = list(map(lambda x: x['name'], hide_rules))
  129. # print('隐藏:',hide_rule_names)
  130. all_cnt = len(merged_rules)
  131. # print(merged_rules)
  132. def filter_show(x):
  133. # name = x['api'].split('rule=')[1].split('&')[0] if 'rule=' in x['api'] else x['key'].replace('dr_','')
  134. name = x
  135. # print(name)
  136. return name not in hide_rule_names
  137. merged_rules = list(filter(filter_show, merged_rules))
  138. # print('隐藏后:',merged_rules)
  139. logger.info(f'数据库筛选隐藏规则耗时{get_interval(t1)}毫秒,共计{all_cnt}条规则,隐藏后可渲染{len(merged_rules)}条规则')
  140. # merged_rules = []
  141. return merged_rules
  142. def disable_exit_for_threadpool_executor():
  143. import atexit
  144. import concurrent.futures
  145. atexit.unregister(concurrent.futures.thread._python_exit)
  146. def sort_lsg_rules(sites:list):
  147. """
  148. 查询结果按order和write_date 联合排序
  149. :param sites:
  150. :return:
  151. """
  152. def comp(x, y):
  153. if x['order'] > y['order']:
  154. return 1
  155. elif x['order'] < y['order']:
  156. return - 1
  157. else:
  158. if x['write_date'] < y['write_date']:
  159. return 1
  160. elif x['write_date'] > y['write_date']:
  161. return -1
  162. else:
  163. return 0
  164. sites.sort(key=functools.cmp_to_key(comp), reverse=False)
  165. return sites
  166. def sort_lsg_rules2(sites:list,lsg_rule_names:list):
  167. """
  168. 查询结果按order和write_date 联合排序
  169. :param sites:
  170. :return:
  171. """
  172. def comp(x, y):
  173. try:
  174. x1 = lsg_rule_names.index(x)
  175. except:
  176. x1 = 999
  177. try:
  178. y1 = lsg_rule_names.index(y)
  179. except:
  180. y1 = 999
  181. if x1 >= y1:
  182. return 1
  183. elif x1 < y1:
  184. return - 1
  185. sites.sort(key=functools.cmp_to_key(comp), reverse=False)
  186. return sites
  187. def getSearchSites():
  188. val = {}
  189. lsg = storage_service()
  190. try:
  191. timeout = round(int(lsg.getItem('SEARCH_TIMEOUT',5000))/1000,2)
  192. except:
  193. timeout = 5
  194. val['timeout'] = timeout
  195. rules = getRules('js')['list']
  196. rule_names = list(map(lambda x: x['name'], rules))
  197. rules_exclude = ['drpy']
  198. new_rules = list(filter(lambda x: x.get('searchable', 0) and x.get('name', '') not in rules_exclude, rules))
  199. total_search = [new_rule['name'] for new_rule in new_rules]
  200. nosearch_sites = set(rule_names) ^ set(total_search)
  201. nosearch_sites.remove('drpy')
  202. val['total_search'] = total_search
  203. val['nosearch_sites'] = list(nosearch_sites)
  204. search_sites = merged_hide(total_search)
  205. lsg_rules = rules_service()
  206. lsg_rule_list = lsg_rules.query_all()
  207. lsg_rule_list = list(filter(lambda x: x['name'] in search_sites, lsg_rule_list))
  208. lsg_rule_names = list(map(lambda x: x['name'], lsg_rule_list))
  209. search_sites = sort_lsg_rules2(search_sites, lsg_rule_names)
  210. search_limit = lsg.getItem('SEARCH_LIMIT', 24)
  211. try:
  212. search_limit = int(search_limit)
  213. except:
  214. search_limit = 0
  215. if search_limit < 1:
  216. search_limit = 0
  217. search_sites = search_sites[:search_limit]
  218. val['search_limit'] = search_limit
  219. val['search_sites'] = search_sites
  220. return val
  221. def multi_search(wd):
  222. t1 = time()
  223. val = getSearchSites()
  224. timeout = val['timeout']
  225. total_search = val['total_search']
  226. nosearch_sites = val['nosearch_sites']
  227. search_limit = val['search_limit']
  228. search_sites = val['search_sites']
  229. env = get_env()
  230. logger.info(f'开始聚搜{wd},共计{len(total_search)}个规则,聚搜超时{timeout}秒')
  231. logger.info(f'不支持聚搜的规则,共计{len(nosearch_sites)}个规则:{",".join(nosearch_sites)}')
  232. msearch_msg = f'搜索限制条数:{search_limit}/{len(search_sites)} {search_sites}'
  233. logger.info(msearch_msg)
  234. print(msearch_msg)
  235. # search_sites = []
  236. res = []
  237. if len(search_sites) > 0:
  238. with open('js/模板.js', encoding='utf-8') as f:
  239. before = f.read().split('export')[0]
  240. with ThreadPoolExecutor(max_workers=len(search_sites)) as executor:
  241. to_do = []
  242. for site in search_sites:
  243. future = executor.submit(search_one, site, wd, before,env,current_app._get_current_object())
  244. to_do.append(future)
  245. try:
  246. for future in as_completed(to_do, timeout=timeout): # 并发执行
  247. ret = future.result()
  248. # print(ret)
  249. if ret and isinstance(ret,dict) and ret.get('list'):
  250. res.extend(ret['list'])
  251. except Exception as e:
  252. print(f'发生错误:{e}')
  253. import atexit
  254. atexit.unregister(thread._python_exit)
  255. executor.shutdown = lambda wait: None
  256. # disable_exit_for_threadpool_executor()
  257. logger.info(f'drpy聚搜{len(search_sites)}个源共计耗时{get_interval(t1)}毫秒')
  258. return jsonify({
  259. "list": res
  260. })
  261. @vod.route('/vods')
  262. def vods_search():
  263. val = getSearchSites()
  264. print(val)
  265. # return jsonify(val)
  266. return render_template('show_search.html',val=val)
  267. @vod.route('/vod')
  268. def vod_home():
  269. lsg = storage_service()
  270. js0_disable = lsg.getItem('JS0_DISABLE',cfg.get('JS0_DISABLE',0))
  271. if js0_disable:
  272. abort(403)
  273. js0_password = lsg.getItem('JS0_PASSWORD', cfg.get('JS0_PASSWORD', ''))
  274. # print('js0_password:',js0_password)
  275. if js0_password:
  276. pwd = getParmas('pwd')
  277. if pwd != js0_password:
  278. abort(403)
  279. t0 = time()
  280. rule = getParmas('rule')
  281. ac = getParmas('ac')
  282. ids = getParmas('ids')
  283. if ac and ids and ids.find('#') > -1: # 聚搜的二级
  284. id_list = ids.split(',')
  285. rule = id_list[0].split('#')[1]
  286. # print(rule)
  287. ext = getParmas('ext')
  288. filters = getParmas('f')
  289. tp = getParmas('type')
  290. # print(f'type:{tp}')
  291. # if not ext.startswith('http') and not rule:
  292. if not rule:
  293. return R.failed('规则字段必填')
  294. rule_list = getRuleLists()
  295. # if not ext.startswith('http') and not rule in rule_list:
  296. if not ext and not rule in rule_list:
  297. msg = f'服务端本地仅支持以下规则:{",".join(rule_list)}'
  298. return R.failed(msg)
  299. # logger.info(f'检验耗时:{get_interval(t0)}毫秒')
  300. t1 = time()
  301. # js_path = f'js/{rule}.js' if not ext.startswith('http') else ext
  302. js_path = f'js/{rule}.js' if not ext else ext
  303. with open('js/模板.js', encoding='utf-8') as f:
  304. before = f.read().split('export')[0]
  305. # logger.info(f'js读取耗时:{get_interval(t1)}毫秒')
  306. end_code = """\nif (rule.模板 && muban.hasOwnProperty(rule.模板)) {rule = Object.assign(muban[rule.模板], rule);}"""
  307. logger.info(f'参数检验js读取共计耗时:{get_interval(t0)}毫秒')
  308. t2 = time()
  309. # ctx, js_code = parser.runJs(js_path,before=before)
  310. # if not js_code:
  311. # return R.failed('爬虫规则加载失败')
  312. # # rule = ctx.eval('rule')
  313. # # print(type(ctx.rule.lazy()),ctx.rule.lazy().toString())
  314. # ruleDict = ctx.rule.to_dict()
  315. ctx = Context()
  316. try:
  317. with open(js_path,encoding='utf-8') as f2:
  318. jscode = f2.read()
  319. env = get_env()
  320. for k in env:
  321. # print(f'${k}',f'{env[k]}')
  322. if f'${k}' in jscode:
  323. jscode = jscode.replace(f'${k}',f'{env[k]}')
  324. # print(env)
  325. # if env:
  326. # jscode = render_template_string(jscode,**env)
  327. # print(jscode)
  328. jscode = before + jscode + end_code
  329. # print(jscode)
  330. ctx.eval(jscode)
  331. js_ret = ctx.get('rule')
  332. ruleDict = ujson.loads(js_ret.json())
  333. except Exception as e:
  334. logger.info(f'{e}')
  335. return R.failed('爬虫规则加载失败')
  336. # print(type(ruleDict))
  337. # print(ruleDict)
  338. # print(ruleDict)
  339. ruleDict['id'] = rule # 把路由请求的id装到字典里,后面播放嗅探才能用
  340. # print(ruleDict)
  341. # print(rule)
  342. # print(type(rule))
  343. # print(ruleDict)
  344. logger.info(f'js装载耗时:{get_interval(t2)}毫秒')
  345. # print(ruleDict)
  346. # print(rule)
  347. cms = CMS(ruleDict,db,RuleClass,PlayParse,cfg,ext)
  348. wd = getParmas('wd')
  349. quick = getParmas('quick')
  350. play = getParmas('play') # 类型为4的时候点击播放会带上来
  351. flag = getParmas('flag') # 类型为4的时候点击播放会带上来
  352. # myfilter = getParmas('filter')
  353. t = getParmas('t')
  354. pg = getParmas('pg','1')
  355. pg = int(pg)
  356. q = getParmas('q')
  357. play_url = getParmas('play_url')
  358. if play:
  359. jxs = getJxs()
  360. play_url = play.split('play_url=')[1]
  361. play_url = cms.playContent(play_url, jxs,flag)
  362. if isinstance(play_url, str):
  363. # return redirect(play_url)
  364. # return jsonify({'parse': 0, 'playUrl': play_url, 'jx': 0, 'url': play_url})
  365. # return jsonify({'parse': 0, 'playUrl': play_url, 'jx': 0, 'url': ''})
  366. return jsonify({'parse': 0, 'playUrl': '', 'jx': 0, 'url': play_url})
  367. elif isinstance(play_url, dict):
  368. return jsonify(play_url)
  369. else:
  370. return play_url
  371. if play_url: # 播放
  372. jxs = getJxs()
  373. play_url = cms.playContent(play_url,jxs)
  374. if isinstance(play_url,str):
  375. return redirect(play_url)
  376. elif isinstance(play_url,dict):
  377. return jsonify(play_url)
  378. else:
  379. return play_url
  380. if ac and t: # 一级
  381. fl = {}
  382. if filters and filters.find('{') > -1 and filters.find('}') > -1:
  383. fl = json.loads(filters)
  384. # print(filters,type(filters))
  385. # print(fl,type(fl))
  386. data = cms.categoryContent(t,pg,fl)
  387. # print(data)
  388. return jsonify(data)
  389. if ac and ids: # 二级
  390. id_list = ids.split(',')
  391. show_name = False
  392. if ids.find('#') > -1:
  393. id_list = list(map(lambda x:x.split('#')[0],id_list))
  394. show_name = True
  395. # print('app:377',len(id_list))
  396. # print(id_list)
  397. data = cms.detailContent(pg,id_list,show_name)
  398. # print(data)
  399. return jsonify(data)
  400. if wd: # 搜索
  401. if rule == 'drpy':
  402. print(f'准备单独处理聚合搜索:{wd}')
  403. return multi_search(wd)
  404. # return multi_search2(wd)
  405. else:
  406. data = cms.searchContent(wd)
  407. # print(data)
  408. return jsonify(data)
  409. # return jsonify({'rule':rule,'js_code':js_code})
  410. home_data = cms.homeContent(pg)
  411. return jsonify(home_data)