# /* app.py
# * Copyright (C) 2023 MikuInvidious Team
# *
# * This software is free software; you can redistribute it and/or
# * modify it under the terms of the GNU General Public License as
# * published by the Free Software Foundation; either version 3 of
# * the License, or (at your option) any later version.
# *
# * This software is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# * General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this library. If not, see .
# */
from vidconv import av2bv
from bbapi import *
from htmlp import *
from datetime import datetime
from urllib.parse import urlparse
import requests
import flask
import json
import os
import math
from conf import *
os.environ['TZ'] = 'Asia/Shanghai'
app = flask.Flask(__name__)
# Bultin Flask Cache-Control
@app.after_request
def add_cache_control(resp):
try:
resp.cache_control.max_age = cache_time
except:
resp.cache_control.max_age = 0
return resp
# Builtin Bilibili Pic Proxy
@app.route('/proxy/pic/')
def picproxy(url):
netloc = urlparse(url).netloc
if not netloc.endswith('hdslb.com'):
return 'bad', 404
req = requests.get(url, stream = True, headers = safe_headers, cookies=cookies)
return flask.Response(flask.stream_with_context(req.iter_content(512)),
content_type = req.headers['Content-Type'], status = req.status_code)
# Bultiin Bilibili Video Source Proxy
@app.route('/proxy/vid/')
def proxy(url):
url = flask.request.url.split('/proxy/vid/')[1]
netloc = urlparse(url).netloc
if not netloc.endswith('-mirrorakam.akamaized.net') and not netloc.endswith('.bilivideo.com'):
return 'bad', 404
if not always_proxy and netloc.endswith('-mirrorakam.akamaized.net'):
rdrt = url + flask.request.url.strip(flask.request.base_url)
return flask.redirect(rdrt)
req_headers = safe_headers
resp_headers = { 'Accept-Ranges': 'bytes', }
if 'Range' in flask.request.headers:
req_headers['Range'] = flask.request.headers['Range']
resp_headers['Range'] = flask.request.headers['Range']
req = requests.get(url, stream = True, headers = req_headers, cookies=cookies)
resp_headers['Content-Length'] = req.headers['Content-Length']
if 'Range' in flask.request.headers and req.status_code == 206:
resp_headers['Content-Range'] = req.headers['Content-Range']
return flask.Response(flask.stream_with_context(req.iter_content(8192)),
content_type = req.headers['Content-Type'],
status = req.status_code,
headers = resp_headers)
@app.route('/error/')
@app.route('/error//')
def error(id = '', code = ''):
return 'oops'
@app.route('/')
def front_page():
req = requests.get('https://s.search.bilibili.com/main/hotword')
result_json = req.json()
result = []
if result_json['code'] == 0:
result = result_json['list']
return flask.render_template('index.html', hotwards = result)
@app.route('/')
def b32tv_redirect(b32tvid):
req = requests.get(f'https://b23.tv/{b32tvid}', allow_redirects=False)
if req.status_code != 302:
return 'oops', req.status_code
url = urlparse(req.headers['Location'])
if url.path.startswith('/read/mobile'):
return flask.redirect(flask.url_for('read_page', cid = f'cv{url.path[13:]}'))
elif url.path.startswith('/video/'):
return flask.redirect(flask.url_for('video_page', vid = req.headers['Location'].split('/')[-1][:12]))
@app.route('/vvinfo/::')
def video_view_info(vid, cid, qn = 16):
# Convert AVid to BVid to simplify handling.
if vid.lower().startswith('av'):
vid = av2bv(int(vid[2:]))
srcinfo = bbapi_src_from_bvid(cid, vid, qn)
if srcinfo['code'] != 0:
return ''
if srcinfo['data']['quality'] != int(qn):
return ''
if not (srcinfo['data']['format'] == 'mp4' or srcinfo['data']['format'] == 'mp4720'):
return ''
return f'window.srcinfo[{qn}] = ' + json.dumps(srcinfo['data']) + ';'
@app.route('/search')
@app.route('/search/')
@app.route('/search/:')
def search_page(kw = '', pn = 1):
if 'pn' in flask.request.args:
pn = flask.request.args['pn']
if 'kwards' in flask.request.args:
kw = flask.request.args['kwards']
elif kw == '':
return 'oops', 404
vids = bbapi_vids_from_kward(kw, pn)
return flask.render_template('search.html', result = vids['data'], kw = kw)
@app.route('/space/')
@app.route('/space/:')
def member_page(mid, page_num = 1):
page_num = int(page_num)
if 'p' in flask.request.args:
page_num = flask.request.args['p']
vids = bbapi_vids_from_mid_paging(mid, page_num)
info = bbapi_uinfo_from_mid(mid)
card = bbapi_ucard_from_mid(mid)
return flask.render_template('space.html', vids = vids['data'], page = vids['data']['page'],
info = info['data'], card = card['data'])
@app.route('/video/')
@app.route('/video//')
@app.route('/video/:')
@app.route('/video/::')
def video_page(vid, cid = 0, qn = 16):
# Convert AVid to BVid to simplify handling.
if vid.lower().startswith('av'):
vid = av2bv(int(vid[2:]))
vidinfo = bbapi_info_from_bvid(vid)
if vidinfo['code'] != 0:
return flask.redirect(flask.url_for('error', code = vidinfo['code'], id = vid))
subvids = []
if vidinfo['data']['videos'] > 1:
subvids_result = bbapi_subvid_from_bvid(vid)
if subvids_result['code'] == 0:
subvids = subvids_result['data']
relatedvids = bbapi_related_from_bvid(vid)
if relatedvids['code'] != 0:
relatedvids['data'] = []
else:
relatedvids['data'] = relatedvids['data'][:12]
cums = bbapi_cum_from_bvid(vid)
if cums['code'] != 0:
cums['data'] = {}
cums['data']['replies'] = []
elif not cums['data']['replies']:
cums['data']['replies'] = []
# Convert AVid to BVid to simplify handling.
if vid.lower().startswith('av'):
vid = av2bv(int(vid[2:]))
if 'qn' in flask.request.args:
qn = flask.request.args['qn']
if cid == 0:
cid = vidinfo['data']['cid']
srcinfo = bbapi_src_from_bvid(cid, vid, qn)
if srcinfo['code'] != 0:
return flask.redirect(flask.url_for('error', code = srcinfo['code'], id = vid))
return flask.render_template('video.html', vidinfo = vidinfo['data'], srcinfo = srcinfo['data'],
relatedvids = relatedvids['data'], comments = cums['data'],
subvids = subvids, cid = cid)
@app.route('/read/')
def read_page(cid):
info = bbapi_info_from_cv(cid[2:])
if info['code'] != 0:
return 'oops', 404
req = requests.get(f'https://www.bilibili.com/read/{cid}')
if req.status_code != 200:
return 'oops', req.status_code
content = html_format_article(req.text)
return flask.render_template('read.html', info = info['data'], article = content)
# Template filiters
@app.template_filter('date')
def _jinja2_filter_datetime(ts, fmt='%Y年%m月%d日 %H点%m分'):
return datetime.fromtimestamp(1577835803).strftime(fmt)
@app.template_filter('proxy')
def _jinja2_filter_proxy(url):
return flask.url_for('proxy', url = url)
@app.template_filter('hexcolor')
def _jinja2_filter_hexcolor(num):
return str(hex(int(num)))[2:].upper()
@app.template_filter('ceil')
def _jinja2_filter_ceil(num):
return int(math.ceil(float(num)))
@app.template_filter('picproxy')
def _jinja2_filter_picproxy(url):
if always_proxy:
if url.startswith('//'):
url = 'http:' + url
return flask.url_for('picproxy', url = url)
else:
return url
if __name__ == '__main__':
app.debug = True
app.run()