admin.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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,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 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. admin = Blueprint("admin", __name__)
  24. # @admin.route("/",methods=['get'])
  25. # def index():
  26. # return R.ok(msg='欢迎进入首页',data=None)
  27. # @admin.route("/info",methods=['get'])
  28. # def info_all():
  29. # data = storage_service.query_all()
  30. # return R.ok(data=data)
  31. @admin.route('/')
  32. def admin_index(): # 管理员界面
  33. if not verfy_token():
  34. return render_template('login.html')
  35. lsg = storage_service()
  36. live_url = lsg.getItem('LIVE_URL')
  37. use_py = lsg.getItem('USE_PY')
  38. js0_password = lsg.getItem('JS0_PASSWORD')
  39. # print(f'live_url:', live_url)
  40. rules = getRules('js')
  41. # print(rules)
  42. cache_count = getCacheCount()
  43. # print(cache_count)
  44. return render_template('admin.html',js0_password=js0_password, pystate=use_py,rules=rules,cache_count=cache_count, ver=getLocalVer(), live_url=live_url)
  45. @admin.route('/settings')
  46. def admin_settings(): # 管理员界面
  47. if not verfy_token():
  48. return render_template('login.html')
  49. lsg = storage_service()
  50. # 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('|')
  51. conf_lists = lsg.getStoreConf()
  52. # print(conf_lists)
  53. return render_template('settings.html', conf_lists=conf_lists,ver=getLocalVer())
  54. @admin.route('/save_conf',methods=['POST'])
  55. def admin_save_conf(): # 管理员界面
  56. if not verfy_token():
  57. # return render_template('login.html')
  58. return R.error('请登录后再试')
  59. key = getParmas('key')
  60. value = getParmas('value')
  61. print(f'key:{key},value:{value}')
  62. lsg = storage_service()
  63. res_id = lsg.setItem(key,value)
  64. return R.success(f'修改成功,记录ID为:{res_id}')
  65. @admin.route('/update_env',methods=['POST'])
  66. def admin_update_env(): # 更新环境变量中的某个值
  67. if not verfy_token():
  68. # return render_template('login.html')
  69. return R.error('请登录后再试')
  70. key = getParmas('key')
  71. value = getParmas('value')
  72. print(f'key:{key},value:{value}')
  73. ENV = update_env(key,value)
  74. return R.success(f'修改成功,最新的完整ENV见data',data=ENV)
  75. @admin.route("/view/<name>",methods=['GET'])
  76. def admin_view_rule(name):
  77. return js_render(name)
  78. # if not name or not name.split('.')[-1] in ['js','txt','py','json']:
  79. # return R.error(f'非法猥亵,未指定文件名。必须包含js|txt|json|py')
  80. # try:
  81. # env = get_env()
  82. # # print(env)
  83. # if env.get('js_proxy'):
  84. # js_proxy = env['js_proxy']
  85. # burl = request.base_url
  86. # if '=>' in js_proxy:
  87. # oldsrc = js_proxy.split('=>')[0]
  88. # if oldsrc in burl:
  89. # newsrc = js_proxy.split('=>')[1]
  90. # # print(f'js1源代理已启用,全局替换{oldsrc}为{newsrc}')
  91. # rurl = burl.replace(oldsrc, newsrc)
  92. # if burl != rurl:
  93. # jscode = parser.getJs(name, 'js')
  94. # # rjscode = render_template_string(jscode, env=env)
  95. # rjscode = jscode
  96. # for k in env:
  97. # # print(f'${k}', f'{env[k]}')
  98. # if f'${k}' in rjscode:
  99. # rjscode = rjscode.replace(f'${k}', f'{env[k]}')
  100. # # rjscode = render_template_string(jscode, **env)
  101. # if rjscode.strip() == jscode.strip(): # 无需渲染才代理
  102. # return redirect(rurl)
  103. # else:
  104. # logger.info(f'{name}由于存在环境变量无法被依赖代理')
  105. #
  106. # return parser.toJs(name,'js',env)
  107. # except Exception as e:
  108. # return R.error(f'非法猥亵\n{e}')
  109. @admin.route('/clear/<name>')
  110. def admin_clear_rule(name):
  111. if not name or not name.split('.')[-1] in ['js','txt','py','json']:
  112. return R.error(f'非法猥亵,未指定文件名。必须包含js|txt|json|py')
  113. if not verfy_token():
  114. return render_template('login.html')
  115. file_path = os.path.abspath(f'js/{name}')
  116. print(file_path)
  117. if not os.path.exists(file_path):
  118. return R.error('服务端没有此文件!'+file_path)
  119. os.remove(file_path)
  120. return R.ok('成功删除文件:'+file_path)
  121. @admin.route('/get_ver')
  122. def admin_get_ver():
  123. if not verfy_token():
  124. # return render_template('login.html')
  125. return R.error('请登录后再试')
  126. online_ver,msg = getOnlineVer()
  127. return jsonify({'local_ver':getLocalVer(),'online_ver':online_ver,'msg':msg})
  128. @admin.route('/update_db')
  129. def admin_update_db():
  130. if not verfy_token():
  131. # return render_template('login.html')
  132. return R.error('请登录后再试')
  133. old_dbfile = 'migrations'
  134. if os.path.exists(old_dbfile):
  135. logger.info(f'开始删除历史数据库迁移文件:{old_dbfile}')
  136. shutil.rmtree(old_dbfile)
  137. db.session.execute('drop table if exists alembic_version')
  138. cmd = 'flask db migrate && flask db upgrade'
  139. if not os.path.exists('migrations'):
  140. cmd = 'flask db init && '+cmd
  141. logger.info(f'开始执行cmd:{cmd}')
  142. result = os.system(cmd)
  143. logger.info(f'cmd执行结果:{result}')
  144. return R.success('数据库升级完毕')
  145. @admin.route('/update_ver')
  146. def admin_update_ver():
  147. if not verfy_token():
  148. return R.failed('请登录后再试')
  149. msg = download_new_version()
  150. return R.success(msg)
  151. @admin.route('/rule_state/<int:state>',methods=['POST'])
  152. def admin_rule_state(state=0): # 管理员修改规则状态
  153. if not verfy_token():
  154. return R.error('请登录后再试')
  155. names = getParmas('names')
  156. if not names:
  157. return R.success(f'修改失败,没有传递names参数')
  158. rule_list = names.split(',')
  159. rules = rules_service()
  160. # print(rules.query_all())
  161. # print(rules.getState(rule_list[0]))
  162. # print(rule_list)
  163. success_list = []
  164. for rule in rule_list:
  165. try:
  166. res_id = rules.setState(rule,state)
  167. success_list.append(f'{rule}:{res_id}')
  168. except:
  169. success_list.append(rule)
  170. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  171. @admin.route('/rule_order/<int:order>',methods=['POST'])
  172. def admin_rule_order(order=0): # 管理员修改规则顺序
  173. if not verfy_token():
  174. return R.error('请登录后再试')
  175. names = getParmas('names')
  176. if not names:
  177. return R.success(f'修改失败,没有传递names参数')
  178. rule_list = names.split(',')
  179. rules = rules_service()
  180. # print(rules.query_all())
  181. # print(rules.getState(rule_list[0]))
  182. # print(rule_list)
  183. success_list = []
  184. rule_list.reverse() # 倒序解决时间多重排序问题
  185. for rule in rule_list:
  186. try:
  187. res_id = rules.setOrder(rule,order)
  188. success_list.append(f'{rule}:{res_id}')
  189. except:
  190. success_list.append(rule)
  191. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  192. @admin.route('/parse/save_data',methods=['POST'])
  193. def admin_parse_save_data(): # 管理员保存拖拽排序后的解析数据
  194. if not verfy_token():
  195. return R.error('请登录后再试')
  196. data = getParmas('data')
  197. if not data:
  198. return R.success(f'修改失败,没有传递data参数')
  199. parse = parse_service()
  200. success_list = []
  201. data = ujson.loads(data)
  202. new_list = []
  203. new_data = []
  204. for nd in data:
  205. if not nd.get('url') and nd.get('name') != '🌐Ⓤ':
  206. continue
  207. if nd['url'] not in new_list:
  208. new_data.append(nd)
  209. new_list.append(nd['url'])
  210. print(f'去重前:{len(data)},去重后:{len(new_data)}')
  211. for i in range(len(new_data)):
  212. d = new_data[i]
  213. # if not d.get('url') and d.get('name') != '🌐Ⓤ':
  214. # continue
  215. obj = {
  216. 'name':d.get('name', ''),
  217. 'url':d.get('url', ''),
  218. 'state':d.get('state',1),
  219. 'type': d.get('state',0),
  220. 'order':i+1,
  221. 'ext':d.get('ext',''),
  222. 'header':d.get('header',''),
  223. }
  224. # print(obj)
  225. try:
  226. parse.saveData(obj)
  227. success_list.append(f'parse:{d["url"]}')
  228. # print(obj)
  229. # print(200,obj)
  230. except Exception as e:
  231. success_list.append(d["url"])
  232. print(f'{d["url"]}失败:{e}')
  233. # print(len(success_list))
  234. return R.success(f'修改成功,服务器反馈信息为:{success_list}')
  235. @admin.route('/force_update')
  236. def admin_force_update():
  237. if not verfy_token():
  238. return R.failed('请登录后再试')
  239. ret = copy_to_update()
  240. if ret:
  241. msg = '升级成功'
  242. return R.success(msg)
  243. else:
  244. msg = '升级失败。具体原因只能去看实时日志(通过9001端口)'
  245. return R.failed(msg)
  246. @admin.route('/update_lives')
  247. def admin_update_lives():
  248. url = getParmas('url')
  249. if not url:
  250. return R.failed('未提供被同步的直播源远程地址!')
  251. if not verfy_token():
  252. return R.failed('请登录后再试')
  253. live_url = url
  254. success = download_lives(live_url)
  255. if success:
  256. return R.success(f'直播源{live_url}同步成功')
  257. else:
  258. return R.failed(f'直播源{live_url}同步失败')
  259. @admin.route('/write_live_url')
  260. def admin_write_live_url():
  261. url = getParmas('url')
  262. if not url:
  263. return R.failed('未提供修改后的直播源地址!')
  264. if not verfy_token():
  265. return R.failed('请登录后再试')
  266. lsg = storage_service()
  267. id = lsg.setItem('LIVE_URL',url)
  268. msg = f'已修改的配置记录id为:{id}'
  269. return R.success(msg)
  270. @admin.route('/change_use_py')
  271. def admin_change_use_py():
  272. if not verfy_token():
  273. return R.failed('请登录后再试')
  274. lsg = storage_service()
  275. use_py = lsg.getItem('USE_PY')
  276. new_use_py = '' if use_py else '1'
  277. state = '开启' if new_use_py else '关闭'
  278. id = lsg.setItem('USE_PY', new_use_py)
  279. msg = f'已修改的配置记录id为:{id},结果为{state}'
  280. return R.success(msg)
  281. # @admin.route('/get_use_py')
  282. # def admin_get_use_py():
  283. # if not verfy_token():
  284. # return R.failed('请登录后再试')
  285. # lsg = storage_service()
  286. # use_py = lsg.getItem('USE_PY')
  287. # state = 1 if use_py else 0
  288. # return R.success(state)
  289. @admin.route('/upload', methods=['GET', 'POST'])
  290. def upload_file():
  291. if not verfy_token():
  292. return render_template('login.html')
  293. if request.method == 'POST':
  294. try:
  295. file = request.files['file']
  296. filename = secure_filename(file.filename)
  297. print(f'推荐安全文件命名:{filename}')
  298. savePath = f'js/{file.filename}'
  299. if os.path.exists(savePath):
  300. return R.failed(f'上传失败,文件已存在,请先查看删除再试')
  301. with open('js/模板.js', encoding='utf-8') as f2:
  302. before = f2.read().split('export')[0]
  303. upcode = file.stream.read().decode('utf-8')
  304. check_to_run = before + upcode
  305. # print(check_to_run)
  306. try:
  307. loader, _ = runJScode(check_to_run)
  308. rule = loader.eval('rule')
  309. if not rule:
  310. return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  311. except:
  312. return R.failed('文件上传失败,检测到上传的文件不是drpy框架支持的源代码')
  313. print(savePath)
  314. file.seek(0) # 读取后变成空文件,重新赋能
  315. file.save(savePath)
  316. return R.success('文件上传成功')
  317. except Exception as e:
  318. return R.failed(f'文件上传失败!{e}')
  319. else:
  320. # return render_template('upload.html')
  321. return R.failed('文件上传失败')
  322. @admin.route('/login',methods=['GET','POST'])
  323. def login_api():
  324. username = getParmas('username')
  325. password = getParmas('password')
  326. autologin = getParmas('autologin')
  327. if not all([username,password]):
  328. return R.failed('账号密码字段必填')
  329. token = md5(f'{username};{password}')
  330. check = verfy_token(token=token)
  331. if check:
  332. # response = make_response(redirect('/admin'))
  333. response = make_response(R.success('登录成功'))
  334. response.set_cookie('token', token)
  335. return response
  336. else:
  337. return R.failed('登录失败,用户名或密码错误')
  338. @admin.route('/logtail')
  339. def admin_logtail():
  340. if not verfy_token():
  341. return R.failed('请登录后再试')
  342. return render_template('logtail.html')
  343. @admin.route('/lives')
  344. def admin_lives():
  345. if not verfy_token():
  346. return R.failed('请登录后再试')
  347. # print(dir(request))
  348. # 完整地址: request.base_url url
  349. # 带http的前缀 host_url root_url
  350. # 不带http的前缀 host
  351. # 当前路径 path
  352. host_url = request.host_url
  353. def get_lives():
  354. base_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在目录
  355. # print(base_path)
  356. live_path = os.path.join(base_path, '../txt/lives')
  357. # print(live_path)
  358. files = os.listdir(live_path)
  359. # print(files)
  360. # files = list(filter(lambda x: str(x).endswith('.txt') and str(x).find('模板') < 0, files))
  361. files = list(filter(lambda x: str(x).split('.')[-1] in ['txt','json','m3u'] and str(x).find('模板') < 0, files))
  362. files = [f'{host_url}lives?path=txt/lives/{file}' for file in files]
  363. return files
  364. files = '\n'.join(get_lives())
  365. response = make_response(files)
  366. response.headers['Content-Type'] = 'text/plain; charset=utf-8'
  367. return response