app.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. # /* app.py
  2. # * Copyright (C) 2023 MikuInvidious Team
  3. # *
  4. # * This software is free software; you can redistribute it and/or
  5. # * modify it under the terms of the GNU General Public License as
  6. # * published by the Free Software Foundation; either version 3 of
  7. # * the License, or (at your option) any later version.
  8. # *
  9. # * This software is distributed in the hope that it will be useful,
  10. # * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # * General Public License for more details.
  13. # *
  14. # * You should have received a copy of the GNU General Public License
  15. # * along with this library. If not, see <http://www.gnu.org/licenses/>.
  16. # */
  17. from vidconv import av2bv
  18. from bbapi import *
  19. from htmlp import *
  20. from datetime import datetime
  21. from urllib.parse import urlparse
  22. import requests
  23. import flask
  24. import json
  25. import os
  26. import math
  27. from conf import *
  28. os.environ['TZ'] = 'Asia/Shanghai'
  29. app = flask.Flask(__name__)
  30. # Bultin Flask Cache-Control
  31. @app.after_request
  32. def add_cache_control(resp):
  33. try:
  34. resp.cache_control.max_age = cache_time
  35. except:
  36. resp.cache_control.max_age = 0
  37. return resp
  38. # Builtin Bilibili Pic Proxy
  39. @app.route('/proxy/pic/<path:url>')
  40. def picproxy(url):
  41. netloc = urlparse(url).netloc
  42. if not netloc.endswith('hdslb.com'):
  43. return 'bad', 404
  44. req = requests.get(url, stream = True, headers = safe_headers, cookies=cookies)
  45. return flask.Response(flask.stream_with_context(req.iter_content(512)),
  46. content_type = req.headers['Content-Type'], status = req.status_code)
  47. # Bultiin Bilibili Video Source Proxy
  48. @app.route('/proxy/vid/<path:url>')
  49. def proxy(url):
  50. url = flask.request.url.split('/proxy/vid/')[1]
  51. netloc = urlparse(url).netloc
  52. if not netloc.endswith('-mirrorakam.akamaized.net') and not netloc.endswith('.bilivideo.com'):
  53. return 'bad', 404
  54. if not always_proxy and netloc.endswith('-mirrorakam.akamaized.net'):
  55. rdrt = url + flask.request.url.strip(flask.request.base_url)
  56. return flask.redirect(rdrt)
  57. req_headers = safe_headers
  58. resp_headers = { 'Accept-Ranges': 'bytes', }
  59. if 'Range' in flask.request.headers:
  60. req_headers['Range'] = flask.request.headers['Range']
  61. resp_headers['Range'] = flask.request.headers['Range']
  62. req = requests.get(url, stream = True, headers = req_headers, cookies=cookies)
  63. resp_headers['Content-Length'] = req.headers['Content-Length']
  64. if 'Range' in flask.request.headers and req.status_code == 206:
  65. resp_headers['Content-Range'] = req.headers['Content-Range']
  66. return flask.Response(flask.stream_with_context(req.iter_content(8192)),
  67. content_type = req.headers['Content-Type'],
  68. status = req.status_code,
  69. headers = resp_headers)
  70. @app.route('/error/<code>')
  71. @app.route('/error/<code>/<id>')
  72. def error(id = '', code = ''):
  73. return 'oops'
  74. @app.route('/')
  75. def front_page():
  76. req = requests.get('https://s.search.bilibili.com/main/hotword')
  77. result_json = req.json()
  78. result = []
  79. if result_json['code'] == 0:
  80. result = result_json['list']
  81. return flask.render_template('index.html', hotwards = result)
  82. @app.route('/<b32tvid>')
  83. def b32tv_redirect(b32tvid):
  84. req = requests.get(f'https://b23.tv/{b32tvid}', allow_redirects=False)
  85. if req.status_code != 302:
  86. return 'oops', req.status_code
  87. url = urlparse(req.headers['Location'])
  88. if url.path.startswith('/read/mobile'):
  89. return flask.redirect(flask.url_for('read_page', cid = f'cv{url.path[13:]}'))
  90. elif url.path.startswith('/video/'):
  91. return flask.redirect(flask.url_for('video_page', vid = req.headers['Location'].split('/')[-1][:12]))
  92. @app.route('/vvinfo/<vid>:<cid>:<qn>')
  93. def video_view_info(vid, cid, qn = 16):
  94. # Convert AVid to BVid to simplify handling.
  95. if vid.lower().startswith('av'):
  96. vid = av2bv(int(vid[2:]))
  97. srcinfo = bbapi_src_from_bvid(cid, vid, qn)
  98. if srcinfo['code'] != 0:
  99. return ''
  100. if srcinfo['data']['quality'] != int(qn):
  101. return ''
  102. if not (srcinfo['data']['format'] == 'mp4' or srcinfo['data']['format'] == 'mp4720'):
  103. return ''
  104. return f'window.srcinfo[{qn}] = ' + json.dumps(srcinfo['data']) + ';'
  105. @app.route('/search')
  106. @app.route('/search/<kw>')
  107. @app.route('/search/<kw>:<pn>')
  108. def search_page(kw = '', pn = 1):
  109. if 'pn' in flask.request.args:
  110. pn = flask.request.args['pn']
  111. if 'kwards' in flask.request.args:
  112. kw = flask.request.args['kwards']
  113. elif kw == '':
  114. return 'oops', 404
  115. vids = bbapi_vids_from_kward(kw, pn)
  116. return flask.render_template('search.html', result = vids['data'], kw = kw)
  117. @app.route('/space/<mid>')
  118. @app.route('/space/<mid>:<page_num>')
  119. def member_page(mid, page_num = 1):
  120. page_num = int(page_num)
  121. if 'p' in flask.request.args:
  122. page_num = flask.request.args['p']
  123. vids = bbapi_vids_from_mid_paging(mid, page_num)
  124. info = bbapi_uinfo_from_mid(mid)
  125. card = bbapi_ucard_from_mid(mid)
  126. return flask.render_template('space.html', vids = vids['data'], page = vids['data']['page'],
  127. info = info['data'], card = card['data'])
  128. @app.route('/video/<vid>')
  129. @app.route('/video/<vid>/')
  130. @app.route('/video/<vid>:<cid>')
  131. @app.route('/video/<vid>:<cid>:<qn>')
  132. def video_page(vid, cid = 0, qn = 16):
  133. # Convert AVid to BVid to simplify handling.
  134. if vid.lower().startswith('av'):
  135. vid = av2bv(int(vid[2:]))
  136. vidinfo = bbapi_info_from_bvid(vid)
  137. if vidinfo['code'] != 0:
  138. return flask.redirect(flask.url_for('error', code = vidinfo['code'], id = vid))
  139. subvids = []
  140. if vidinfo['data']['videos'] > 1:
  141. subvids_result = bbapi_subvid_from_bvid(vid)
  142. if subvids_result['code'] == 0:
  143. subvids = subvids_result['data']
  144. relatedvids = bbapi_related_from_bvid(vid)
  145. if relatedvids['code'] != 0:
  146. relatedvids['data'] = []
  147. else:
  148. relatedvids['data'] = relatedvids['data'][:12]
  149. cums = bbapi_cum_from_bvid(vid)
  150. if cums['code'] != 0:
  151. cums['data'] = {}
  152. cums['data']['replies'] = []
  153. elif not cums['data']['replies']:
  154. cums['data']['replies'] = []
  155. # Convert AVid to BVid to simplify handling.
  156. if vid.lower().startswith('av'):
  157. vid = av2bv(int(vid[2:]))
  158. if 'qn' in flask.request.args:
  159. qn = flask.request.args['qn']
  160. if cid == 0:
  161. cid = vidinfo['data']['cid']
  162. srcinfo = bbapi_src_from_bvid(cid, vid, qn)
  163. if srcinfo['code'] != 0:
  164. return flask.redirect(flask.url_for('error', code = srcinfo['code'], id = vid))
  165. return flask.render_template('video.html', vidinfo = vidinfo['data'], srcinfo = srcinfo['data'],
  166. relatedvids = relatedvids['data'], comments = cums['data'],
  167. subvids = subvids, cid = cid)
  168. @app.route('/read/<cid>')
  169. def read_page(cid):
  170. info = bbapi_info_from_cv(cid[2:])
  171. if info['code'] != 0:
  172. return 'oops', 404
  173. req = requests.get(f'https://www.bilibili.com/read/{cid}')
  174. if req.status_code != 200:
  175. return 'oops', req.status_code
  176. content = html_format_article(req.text)
  177. return flask.render_template('read.html', info = info['data'], article = content)
  178. # Template filiters
  179. @app.template_filter('date')
  180. def _jinja2_filter_datetime(ts, fmt='%Y年%m月%d日 %H点%m分'):
  181. return datetime.fromtimestamp(1577835803).strftime(fmt)
  182. @app.template_filter('proxy')
  183. def _jinja2_filter_proxy(url):
  184. return flask.url_for('proxy', url = url)
  185. @app.template_filter('hexcolor')
  186. def _jinja2_filter_hexcolor(num):
  187. return str(hex(int(num)))[2:].upper()
  188. @app.template_filter('ceil')
  189. def _jinja2_filter_ceil(num):
  190. return int(math.ceil(float(num)))
  191. @app.template_filter('picproxy')
  192. def _jinja2_filter_picproxy(url):
  193. if always_proxy:
  194. if url.startswith('//'):
  195. url = 'http:' + url
  196. return flask.url_for('picproxy', url = url)
  197. else:
  198. return url
  199. if __name__ == '__main__':
  200. app.debug = True
  201. app.run()