admin.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # File : admin.py
  4. # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
  5. # Date : 2022/9/6
  6. import os
  7. import ujson
  8. from flask import Blueprint, abort, request, render_template, render_template_string, jsonify, make_response, redirect
  9. from controllers.service import storage_service, rules_service, parse_service
  10. from base.R import R
  11. from base.database import db
  12. from utils.log import logger
  13. import shutil
  14. from utils.update import zipfile, getLocalVer, getOnlineVer, download_new_version, download_lives, copy_to_update
  15. from utils import parser
  16. from utils.env import get_env, update_env
  17. from utils.web import getParmas, verfy_token
  18. from js.rules import getRules, getCacheCount
  19. from utils.parser import runJScode
  20. from werkzeug.utils import secure_filename
  21. from utils.web import md5
  22. from utils.common_api import js_render
  23. from utils.files import get_jar_list, get_jsd_list, get_drop_js
  24. from quickjs import Function, Context
  25. admin = Blueprint("admin", __name__)
  26. # @admin.route("/",methods=['get'])
  27. # def index():
  28. # return R.ok(msg='欢迎进入首页',data=None)
  29. # @admin.route("/info",methods=['get'])
  30. # def info_all():
  31. # data = storage_service.query_all()
  32. # return R.ok(data=data)
  33. @admin.route('/')
  34. def admin_index(): # 管理员界面
  35. if not verfy_token():
  36. return render_template('login.html')
  37. lsg = storage_service()
  38. live_url = lsg.getItem('LIVE_URL')
  39. use_py = lsg.getItem('USE_PY')
  40. force_up = lsg.getItem('FORCE_UP')
  41. js0_password = lsg.getItem('JS0_PASSWORD')
  42. # print(f'live_url:', live_url)
  43. rules = getRules('js')
  44. # print(rules)
  45. cache_count = getCacheCount()
  46. # print(cache_count)
  47. return render_template('admin.html', js0_password=js0_password, pystate=use_py,force_up=force_up, rules=rules,
  48. cache_count=cache_count, ver=getLocalVer(), live_url=live_url)
  49. @admin.route('/settings')
  50. def admin_settings(): # 管理员界面
  51. if not verfy_token():
  52. return render_template('login.html')
  53. lsg = storage_service()
  54. # conf_list = 'LIVE_URL|USE_PY|PLAY_URL|PLAY_DISABLE|LAZYPARSE_MODE|WALL_PAPER_ENABLE|WALL_PAPER|UNAME|PWD|LIVE_MODE|LIVE_URL|CATE_EXCLUDE|TAB_EXCLUDE'.split('|')
  55. conf_lists = lsg.getStoreConf()
  56. # print(conf_lists)
  57. jar_lists = get_jar_list()
  58. SPIDER_JAR = lsg.getItem('SPIDER_JAR', 'custom_spider.jar')
  59. ZB_PLAYER = lsg.getItem('ZB_PLAYER', '1')
  60. # print('ZB_PLAYER:',ZB_PLAYER)
  61. return render_template('settings.html', conf_lists=conf_lists, jar_lists=jar_lists, jar_now=SPIDER_JAR,player_now=ZB_PLAYER,
  62. ver=getLocalVer())
  63. @admin.route('/save_conf', methods=['POST'])
  64. def admin_save_conf(): # 管理员界面
  65. if not verfy_token():
  66. # return render_template('login.html')
  67. return R.error('请登录后再试')
  68. key = getParmas('key')
  69. value = getParmas('value')
  70. print(f'key:{key},value:{value}')
  71. lsg = storage_service()
  72. res_id = lsg.setItem(key, value)
  73. return R.success(f'修改成功,记录ID为:{res_id}')
  74. @admin.route('/update_env', methods=['POST'])
  75. def admin_update_env(): # 更新环境变量中的某个值
  76. if not verfy_token():
  77. # return render_template('login.html')
  78. return R.error('请登录后再试')
  79. key = getParmas('key')
  80. value = getParmas('value')
  81. print(f'key:{key},value:{value}')
  82. ENV = update_env(key, value)
  83. return R.success(f'修改成功,最新的完整ENV见data', data=ENV)
  84. @admin.route("/edit/<name>", methods=['GET'])
  85. def admin_edit_rule(name):
  86. # print(name)
  87. if not verfy_token():
  88. return render_template('login.html')
  89. return render_template('edit_rule.html', name=name)
  90. @admin.route("/edit2/<name>", methods=['GET'])
  91. def admin_edit2_rule(name):
  92. # print(name)
  93. if not verfy_token():
  94. return render_template('login.html')
  95. return render_template('edit_rule_mobile.html', name=name)
  96. @admin.route("/save_edit/<name>", methods=['POST'])
  97. def admin_save_edit_rule(name):
  98. # print(name)
  99. if not verfy_token():
  100. return R.error('请登录后再试')
  101. code = getParmas('code')
  102. file_path = os.path.abspath(f'js/{name}')
  103. if 'var rule' not in code and name != '模板.js':
  104. return R.error(f'文件{name}保存失败,未检测到关键词:var rule')
  105. if not os.path.exists(file_path):
  106. return R.error('服务端没有此文件!' + file_path)
  107. logger.info(f'待保存文件路径:{file_path}')
  108. with open(file_path, mode='w+', encoding='utf-8') as f:
  109. f.write(code)
  110. return R.success(f'保存成功')
  111. @admin.route("/view/<name>", methods=['GET'])
  112. def admin_view_rule(name):
  113. return js_render(name)
  114. # if not name or not name.split('.')[-1] in ['js','txt','py','json']:
  115. # return R.error(f'非法猥亵,未指定文件名。必须包含js|txt|json|py')
  116. # try:
  117. # env = get_env()
  118. # # print(env)
  119. # if env.get('js_proxy'):
  120. # js_proxy = env['js_proxy']
  121. # burl = request.base_url
  122. # if '=>' in js_proxy:
  123. # oldsrc = js_proxy.split('=>')[0]
  124. # if oldsrc in burl:
  125. # newsrc = js_proxy.split('=>')[1]
  126. # # print(f'js1源代理已启用,全局替换{oldsrc}为{newsrc}')
  127. # rurl = burl.replace(oldsrc, newsrc)
  128. # if burl != rurl:
  129. # jscode = parser.getJs(name, 'js')
  130. # # rjscode = render_template_string(jscode, env=env)
  131. # rjscode = jscode
  132. # for k in env:
  133. # # print(f'${k}', f'{env[k]}')
  134. # if f'${k}' in rjscode:
  135. # rjscode = rjscode.replace(f'${k}', f'{env[k]}')
  136. # # rjscode = render_template_string(jscode, **env)
  137. # if rjscode.strip() == jscode.strip(): # 无需渲染才代理
  138. # return redirect(rurl)
  139. # else:
  140. # logger.info(f'{name}由于存在环境变量无法被依赖代理')
  141. #
  142. # return parser.toJs(name,'js',env)
  143. # except Exception as e:
  144. # return R.error(f'非法猥亵\n{e}')
  145. @admin.route('/clear/<name>')
  146. def admin_clear_rule(name):
  147. if not name or not name.split('.')[-1] in ['js', 'txt', 'py', 'json']:
  148. return R.error(f'非法猥亵,未指定文件名。必须包含js|txt|json|py')
  149. if not verfy_token():
  150. return render_template('login.html')
  151. file_path = os.path.abspath(f'js/{name}')
  152. print(file_path)
  153. if not os.path.exists(file_path):
  154. return R.error('服务端没有此文件!' + file_path)
  155. os.remove(file_path)
  156. return R.ok('成功删除文件:' + file_path)
  157. @admin.route('/get_ver')
  158. def admin_get_ver():
  159. if not verfy_token():
  160. # return render_template('login.html')
  161. return R.error('请登录后再试')
  162. lsg = storage_service()
  163. update_proxy = lsg.getItem('UPDATE_PROXY')
  164. online_ver, msg = getOnlineVer(update_proxy)
  165. return jsonify({'local_ver': getLocalVer(), 'online_ver': online_ver, 'msg': msg})
  166. @admin.route('/update_db')
  167. def admin_update_db():
  168. if not verfy_token():
  169. # return render_template('login.html')
  170. return R.error('请登录后再试')
  171. old_dbfile = 'migrations'
  172. if os.path.exists(old_dbfile):
  173. logger.info(f'开始删除历史数据库迁移文件:{old_dbfile}')
  174. shutil.rmtree(old_dbfile)
  175. db.session.execute('drop table if exists alembic_version')
  176. cmd = 'flask db migrate && flask db upgrade'
  177. if not os.path.exists('migrations'):
  178. cmd = 'flask db init && ' + cmd
  179. logger.info(f'开始执行cmd:{cmd}')
  180. result = os.system(cmd)
  181. logger.info(f'cmd执行结果:{result}')
  182. return R.success('数据库升级完毕')
  183. @admin.route('/update_ver')
  184. def admin_update_ver():
  185. if not verfy_token():
  186. return R.failed('请登录后再试')
  187. lsg = storage_service()
  188. update_proxy = lsg.getItem('UPDATE_PROXY')
  189. force_up = lsg.getItem('FORCE_UP')
  190. msg = download_new_version(update_proxy,force_up)
  191. return R.success(msg)
  192. @admin.route('/rule_state/<int:state>', methods=['POST'])
  193. def admin_rule_state(state=0): # 管理员修改规则状态
  194. if not verfy_token():
  195. return R.error('请登录后再试')
  196. names = getParmas('names')
  197. if not names:
  198. return R.success(f'修改失败,没有传递names参数')
  199. rule_list = names.split(',')
  200. rules = rules_service()
  201. # print(rules.query_all())
  202. # print(rules.getState(rule_list[0]))
  203. # print(rule_list)
  204. success_list = []
  205. for rule in rule_list:
  206. try:
  207. res_id = rules.setState(rule, state)
  208. success_list.append(f'{rule}:{res_id}')
  209. except:
  210. success_list.append(rule)
  211. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  212. @admin.route('/rule_order/<int:order>', methods=['POST'])
  213. def admin_rule_order(order=0): # 管理员修改规则顺序
  214. if not verfy_token():
  215. return R.error('请登录后再试')
  216. names = getParmas('names')
  217. if not names:
  218. return R.success(f'修改失败,没有传递names参数')
  219. rule_list = names.split(',')
  220. rules = rules_service()
  221. # print(rules.query_all())
  222. # print(rules.getState(rule_list[0]))
  223. # print(rule_list)
  224. success_list = []
  225. rule_list.reverse() # 倒序解决时间多重排序问题
  226. for rule in rule_list:
  227. try:
  228. res_id = rules.setOrder(rule, order)
  229. success_list.append(f'{rule}:{res_id}')
  230. except:
  231. success_list.append(rule)
  232. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  233. @admin.route('/parse/save_data', methods=['POST'])
  234. def admin_parse_save_data(): # 管理员保存拖拽排序后的解析数据
  235. if not verfy_token():
  236. return R.error('请登录后再试')
  237. data = getParmas('data')
  238. if not data:
  239. return R.success(f'修改失败,没有传递data参数')
  240. parse = parse_service()
  241. success_list = []
  242. data = ujson.loads(data)
  243. new_list = []
  244. new_data = []
  245. for nd in data:
  246. if not nd.get('url') and nd.get('name') != '🌐Ⓤ':
  247. continue
  248. if nd['url'] not in new_list:
  249. new_data.append(nd)
  250. new_list.append(nd['url'])
  251. print(f'去重前:{len(data)},去重后:{len(new_data)}')
  252. for i in range(len(new_data)):
  253. d = new_data[i]
  254. # if not d.get('url') and d.get('name') != '🌐Ⓤ':
  255. # continue
  256. obj = {
  257. 'name': d.get('name', ''),
  258. 'url': d.get('url', ''),
  259. 'state': d.get('state', 1),
  260. 'type': d.get('state', 0),
  261. 'order': i + 1,
  262. 'ext': d.get('ext', ''),
  263. 'header': d.get('header', ''),
  264. }
  265. # print(obj)
  266. try:
  267. parse.saveData(obj)
  268. success_list.append(f'parse:{d["url"]}')
  269. # print(obj)
  270. # print(200,obj)
  271. except Exception as e:
  272. success_list.append(d["url"])
  273. print(f'{d["url"]}失败:{e}')
  274. # print(len(success_list))
  275. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  276. @admin.route('/force_update')
  277. def admin_force_update():
  278. if not verfy_token():
  279. return R.failed('请登录后再试')
  280. ret = copy_to_update()
  281. if ret:
  282. msg = '升级成功'
  283. return R.success(msg)
  284. else:
  285. msg = '升级失败。具体原因只能去看实时日志(通过9001端口)'
  286. return R.failed(msg)
  287. @admin.route('/update_lives')
  288. def admin_update_lives():
  289. url = getParmas('url')
  290. if not url:
  291. return R.failed('未提供被同步的直播源远程地址!')
  292. if not verfy_token():
  293. return R.failed('请登录后再试')
  294. live_url = url
  295. success = download_lives(live_url)
  296. if success:
  297. return R.success(f'直播源{live_url}同步成功')
  298. else:
  299. return R.failed(f'直播源{live_url}同步失败')
  300. @admin.route('/write_live_url')
  301. def admin_write_live_url():
  302. url = getParmas('url')
  303. if not url:
  304. return R.failed('未提供修改后的直播源地址!')
  305. if not verfy_token():
  306. return R.failed('请登录后再试')
  307. lsg = storage_service()
  308. id = lsg.setItem('LIVE_URL', url)
  309. msg = f'已修改的配置记录id为:{id}'
  310. return R.success(msg)
  311. @admin.route('/change_use_py')
  312. def admin_change_use_py():
  313. if not verfy_token():
  314. return R.failed('请登录后再试')
  315. lsg = storage_service()
  316. use_py = lsg.getItem('USE_PY')
  317. new_use_py = '' if use_py else '1'
  318. state = '开启' if new_use_py else '关闭'
  319. id = lsg.setItem('USE_PY', new_use_py)
  320. msg = f'已修改的配置记录id为:{id},结果为{state}'
  321. return R.success(msg)
  322. @admin.route('/change_force_up')
  323. def admin_change_force_up():
  324. if not verfy_token():
  325. return R.failed('请登录后再试')
  326. lsg = storage_service()
  327. force_up = lsg.getItem('FORCE_UP')
  328. new_force_up = '' if force_up else '1'
  329. state = '开启' if new_force_up else '关闭'
  330. id = lsg.setItem('FORCE_UP', new_force_up)
  331. msg = f'已修改的配置记录id为:{id},结果为{state}'
  332. return R.success(msg)
  333. @admin.route('/clear_drop')
  334. def admin_clear_drop():
  335. if not verfy_token():
  336. return R.failed('请登录后再试')
  337. jsd_list = get_jsd_list()
  338. logger.info(f'jsd文件列表:{jsd_list}')
  339. js_list = get_drop_js(jsd_list)
  340. rm_list = []
  341. for i in range(len(js_list)):
  342. js_file = js_list[i]
  343. # shutil.rmtree(js_file, ignore_errors=False, onerror=None)
  344. if os.path.exists(js_file):
  345. os.remove(js_file)
  346. rm_list.append(jsd_list[i][:-1])
  347. logger.info(f'待删除js文件列表:{rm_list}')
  348. rm_str = ','.join(rm_list)
  349. msg = f'清理完毕,本次共计清理{len(rm_list)}个\n {rm_str}'
  350. return R.success(msg)
  351. # @admin.route('/get_use_py')
  352. # def admin_get_use_py():
  353. # if not verfy_token():
  354. # return R.failed('请登录后再试')
  355. # lsg = storage_service()
  356. # use_py = lsg.getItem('USE_PY')
  357. # state = 1 if use_py else 0
  358. # return R.success(state)
  359. def get_size(fobj):
  360. if fobj.content_length:
  361. return fobj.content_length
  362. try:
  363. pos = fobj.tell()
  364. fobj.seek(0, 2) # seek to end
  365. size = fobj.tell()
  366. fobj.seek(pos) # back to original position
  367. return size
  368. except (AttributeError, IOError):
  369. pass
  370. # in-memory file object that doesn't support seeking or tell
  371. return 0
  372. @admin.route('/upload', methods=['POST'])
  373. def upload_file():
  374. args = request.args
  375. force = args.get('force')
  376. if not verfy_token():
  377. return render_template('login.html')
  378. if request.method == 'POST':
  379. try:
  380. file = request.files['file']
  381. lsg = storage_service()
  382. js_max_len = lsg.getItem('JS_MAX_LENGTH', 0.1 * 1024 * 1024)
  383. if get_size(file) > float(js_max_len):
  384. logger.info(f'文件体积过大,禁止上传。当前体积:{get_size(file)},源体积限制:{js_max_len}')
  385. abort(413) # request entity too large
  386. filename = secure_filename(file.filename)
  387. logger.info(f'推荐安全文件命名:{filename}')
  388. savePath = f'js/{file.filename}'
  389. # print(savePath)
  390. if os.path.exists(savePath) and not force:
  391. return R.failed(f'上传失败,文件已存在,请先查看删除再试')
  392. with open('js/模板.js', encoding='utf-8') as f2:
  393. before = f2.read().split('export')[0]
  394. end_code = """\nif (rule.模板 && muban.hasOwnProperty(rule.模板)) {rule = Object.assign(muban[rule.模板], rule);}"""
  395. upcode = file.stream.read().decode('utf-8')
  396. check_to_run = before + upcode + end_code
  397. # print(check_to_run)
  398. # try:
  399. # loader, _ = runJScode(check_to_run)
  400. # rule = loader.eval('rule')
  401. # if not rule:
  402. # return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  403. # except Exception as e:
  404. # logger.info(f'上传文件发生了错误:{e}')
  405. # return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  406. try:
  407. ctx = Context()
  408. ctx.eval(check_to_run)
  409. js_ret = ctx.get('rule')
  410. rule_json = js_ret.json() # 规则的json字符串
  411. ruleDict = ujson.loads(rule_json)
  412. if not ruleDict:
  413. return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  414. except Exception as e:
  415. logger.info(f'上传文件发生了错误:{e}')
  416. return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  417. # print(savePath)
  418. file.seek(0) # 读取后变成空文件,重新赋能
  419. file.save(savePath)
  420. return R.success('文件上传成功')
  421. except Exception as e:
  422. return R.failed(f'文件上传失败!{e}')
  423. else:
  424. # return render_template('upload.html')
  425. return R.failed('文件上传失败')
  426. @admin.route('/upload_update', methods=['POST'])
  427. def upload_update():
  428. args = request.args
  429. force = args.get('force')
  430. print('force:', force)
  431. if not verfy_token():
  432. return render_template('login.html')
  433. if request.method == 'POST':
  434. try:
  435. file = request.files['file']
  436. filename = secure_filename(file.filename)
  437. logger.info(f'推荐安全文件命名:{filename}')
  438. savePath = f'tmp/dr_py.zip'
  439. file.seek(0) # 读取后变成空文件,重新赋能
  440. file.save(savePath)
  441. logger.info(f'开始解压文件:{savePath}')
  442. f = zipfile.ZipFile(savePath, 'r') # 压缩文件位置
  443. for file in f.namelist():
  444. f.extract(file, 'tmp') # 解压位置
  445. f.close()
  446. # print('解压完毕,开始升级')
  447. logger.info('解压完毕,开始升级')
  448. # ret = copy_to_update()
  449. return R.success('升级文件上传成功,请确认drpy目录内是否存在/tmp/dr_py-main/文件夹,如果ok你可以点击强制升级按钮升级刚才上传的文件')
  450. except Exception as e:
  451. return R.failed(f'升级文件上传失败!{e}')
  452. @admin.route('/login', methods=['GET', 'POST'])
  453. def login_api():
  454. username = getParmas('username')
  455. password = getParmas('password')
  456. autologin = getParmas('autologin')
  457. if not all([username, password]):
  458. return R.failed('账号密码字段必填')
  459. token = md5(f'{username};{password}')
  460. check = verfy_token(token=token)
  461. if check:
  462. # response = make_response(redirect('/admin'))
  463. response = make_response(R.success('登录成功'))
  464. response.set_cookie('token', token)
  465. return response
  466. else:
  467. return R.failed('登录失败,用户名或密码错误')
  468. @admin.route('/logtail')
  469. def admin_logtail():
  470. if not verfy_token():
  471. return R.failed('请登录后再试')
  472. return render_template('logtail.html')
  473. @admin.route('/lives')
  474. def admin_lives():
  475. if not verfy_token():
  476. return R.failed('请登录后再试')
  477. # print(dir(request))
  478. # 完整地址: request.base_url url
  479. # 带http的前缀 host_url root_url
  480. # 不带http的前缀 host
  481. # 当前路径 path
  482. host_url = request.host_url
  483. def get_lives():
  484. base_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在目录
  485. # print(base_path)
  486. live_path = os.path.join(base_path, '../txt/lives')
  487. # print(live_path)
  488. files = os.listdir(live_path)
  489. # print(files)
  490. # files = list(filter(lambda x: str(x).endswith('.txt') and str(x).find('模板') < 0, files))
  491. files = list(
  492. filter(lambda x: str(x).split('.')[-1] in ['txt', 'json', 'm3u'] and str(x).find('模板') < 0, files))
  493. files = [f'{host_url}lives?path=txt/lives/{file}' for file in files]
  494. return files
  495. files = '\n'.join(get_lives())
  496. response = make_response(files)
  497. response.headers['Content-Type'] = 'text/plain; charset=utf-8'
  498. return response
  499. @admin.route('/lives_web')
  500. def admin_lives_web():
  501. if not verfy_token():
  502. return R.failed('请登录后再试')
  503. # host_url = request.host_url
  504. def get_lives():
  505. base_path = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) # 上级目录
  506. live_path = os.path.join(base_path, f'base/直播.txt')
  507. with open(live_path,encoding='utf-8') as f:
  508. text = f.read()
  509. return text
  510. text = get_lives()
  511. # response = make_response(text)
  512. # response.headers['Content-Type'] = 'text/plain; charset=utf-8'
  513. # return response
  514. lives = []
  515. for line in text.split('\n'):
  516. if ',http' in line:
  517. lives.append({
  518. 'title':line.split(',')[0],
  519. 'url':line.split(',')[1],
  520. })
  521. print(lives)
  522. lsg = storage_service()
  523. zb_player = lsg.getItem('ZB_PLAYER','1')
  524. return render_template('lives.html',ver=getLocalVer(),lives=lives,zb_player=zb_player)
  525. @admin.route('/tools')
  526. def admin_tools():
  527. if not verfy_token():
  528. return R.failed('请登录后再试')
  529. return render_template('tools.html', ver=getLocalVer())