给blog添加新功能(1)
# 前言
UPD: 由于博客从Jekyll迁移到了VuePress,该功能暂时失效
codeforces评测机炸了,闲来无事想给blog写个新功能,这个过程里遇到了不少坑,在此记录一下。
另:本人基本没有JavaScript基础,只能根据w3cschool和别人的博客依样画葫芦来的,代码可能巨丑,请见谅。
# 需求简述
在网页中所有指向型如https://codeforces.com/contest/1338/submission/76667310
的超链接下放添加一个可以快速查看源代码的小组件,实际效果如下。
# 客户端本地解析
# XMLHttpRequest
大体思路:通过客户端(浏览器)发送Get请求,得到网页源代码后解析。
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function(){
if(xhr.status === 200){
// do something
}
}
xhr.send();
2
3
4
5
6
7
8
结果遇到了CORS跨域的问题,此路不通
# Jsonp
据说jsonp可以解决跨域问题,其原理是src
标签可以载入任意地方资源,而不要求同源。
但是经过实测,text/html
类型的数据并不能通过这种方法跨域,此路不同。
# 远程服务器解析
思来想去,还是用python解析比较可行,于是上flask(一个挺顺手的web框架),并搭建uwsgi(过程见我的Centos8配置uwsgi
)
先上个demo
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "Hello world"
2
3
4
5
6
7
重新配置uwsgi,添加callable
[uwsgi]
socket = 127.0.0.1:3031
chdir = /dic
wsgi-file = api.py
callable = app
processes = 4
threads = 2
stats=%(chdir)uwsgi/uwsgi.status
pidfile=%(chdir)uwsgi/uwsgi.pid
2
3
4
5
6
7
8
9
配置好nginx后打开127.0.0.1,看到"Hello world",说明配置正确。
# 最终服务端代码
from flask import Flask
from flask import abort
import requests
import re
from bs4 import BeautifulSoup
app = Flask(__name__)
def get_respond(url): # 获取网页源码
pass
def extract(content): # 解析html
pass
@app.route('/')
def home():
abort(403)
@app.route('/codeforces_sources/<path:url>')
def codeforces_sources(url):
url = 'https://%s' % url
html = get_respond(url)
if html is None: # 访问失败
abort(500)
code = extract(html)
if code is None: # 提取失败
abort(404)
return code.string
if __name__ == "__main__":
app.run(debug = True)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这里还有个小插曲:上述代码在本地正常运行,但是在服务器上失败,uwsgi报错ModuleNotFound
,检查后原因是beautifulsoup4
模块安装时使用了pip --user
参数,指定pythonHome
或把模块安装到默认路径下即可(最懒的方法:使用sudo pip install beautifulsoup4
)
# 修改nginx跨域配置
简单的三行代码,添加到location
下即可
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
add_header Access-Control-Allow-Methods "GET";
2
3
另外,可以通过正则来配置跨域白名单,见链接 (opens new window)
# 编写Js代码
最终代码如下,用到了highlight.js (opens new window)
<script>
var links = document.getElementsByTagName("a");
var pattern1 = new RegExp("https://(codeforces.com/problemset/submission/[0-9]+/[0-9]+)");
var pattern2 = new RegExp("https://(codeforces.com/contest/[0-9]+/submission/[0-9]+)");
for (var i=0; i<links.length; i++){
(function(i){
var href = links[i].href;
console.log(href);
var url = '';
if(pattern1.test(href)){
url = pattern1.exec(href)[1].trim();
}else if(pattern2.test(href)){
url = pattern2.exec(href)[1].trim();
}
if (url != ''){
var innerText = links[i].innerText
var parent = links[i].parentNode;
var frame = document.createElement("div");
if(parent.lastChild == links[i]){
parent.appendChild(frame, links[i]);
}else{
parent.insertBefore(frame, links[i].nextSibling);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.chgtaxihe.top/codeforces_sources/' + url, true);
xhr.onload = function(){
if(xhr.status === 200){
s = hljs.highlightAuto(xhr.responseText).value;
frame.innerHTML = '<details><summary>展开' + '</summary><div><pre>' + s + '</pre></div></details>';
}
}
xhr.send();
}
})(i);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
小问题:for循环中出现异步函数,引用值不正常。
# Upd: 懒加载
js代码更改如下,需要用到jquery
<script>
var links = document.getElementsByTagName("a");
var pattern1 = new RegExp("https://(codeforces.com/problemset/submission/[0-9]+/[0-9]+)");
var pattern2 = new RegExp("https://(codeforces.com/contest/[0-9]+/submission/[0-9]+)");
for (var i=0; i<links.length; i++){
(function(i){
var href = links[i].href;
var url = '';
if(pattern1.test(href)){
url = pattern1.exec(href)[1].trim();
}else if(pattern2.test(href)){
url = pattern2.exec(href)[1].trim();
}
if (url != ''){
var innerText = links[i].innerText
var parent = links[i].parentNode;
links[i].setAttribute('class', 'expand_code');
links[i].id = i;
var frame = document.createElement("div");
frame.setAttribute("class", 'expand_div_' + i.toString());
if(parent.lastChild == links[i]){
parent.appendChild(frame, links[i]);
}else{
parent.insertBefore(frame, links[i].nextSibling);
}
}
})(i);
}
$("a.expand_code").click(function(){
var dest = this.href.replace("https:\/\/",'');
var item = $('div.expand_div_' + this.id);
var state = item.html();
if(state == ''){
$.get('https://api.chgtaxihe.top/codeforces_sources/' + dest, function(result){
s = hljs.highlightAuto(result).value;
item.html('<pre>' + s + '</pre>');
item.slideToggle();
});
}
item.slideToggle();
return false;
});
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# UPD:使用Cloudflare Worker
async function handleRequest(request) {
// Make the headers mutable by re-constructing the Request.
const url = request.url
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
}
// Function to parse query strings
function getParameterByName(name) {
name = name.replace(/[\[\]]/g, '\\$&')
name = name.replace(/\//g, '')
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
results = regex.exec(url)
if (!results) return null
else if (!results[2]) return ''
else if (results[2]) {
results[2] = results[2].replace(/\//g, '')
}
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
if (getParameterByName("url") === null){
return new Response("Oh no! Something goes wrong!", {status: 500, headers: corsHeaders});
}
var dest_url = atob(getParameterByName("url"));
request = new Request(request)
request.headers.set('Referer', 'https://codeforces.com')
let response = await fetch(dest_url, request)
// Make the headers mutable by re-constructing the Response.
var content = await response.text();
var reg = /<pre id="program-source-text".+?>([.\s\S\n]+?)<\/pre>/;
response = new Response(reg.exec(content)[1], {headers: corsHeaders,})
return response
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
因为API变动了,顺手把js脚本也改了
var links = document.getElementsByTagName("a");
var pattern1 = new RegExp("https://(codeforces.com/problemset/submission/[0-9]+/[0-9]+)");
var pattern2 = new RegExp("https://(codeforces.com/contest/[0-9]+/submission/[0-9]+)");
$(document).ready(function(){
for (var i=0; i<links.length; i++){
(function(i){
var href = links[i].href;
var url = '';
if(pattern1.test(href)){
url = pattern1.exec(href)[1].trim();
}else if(pattern2.test(href)){
url = pattern2.exec(href)[1].trim();
}
if (url != ''){
var innerText = links[i].innerText
var parent = links[i].parentNode;
links[i].setAttribute('class', 'expand_code');
links[i].innerText = links[i].innerText + "(展开)";
links[i].id = i;
var frame = document.createElement("div");
frame.setAttribute("class", 'expand_div_' + i.toString());
if(parent.lastChild == links[i]){
parent.appendChild(frame, links[i]);
}else{
parent.insertBefore(frame, links[i].nextSibling);
}
}
})(i);
}
function HTMLDecode(text) {
var temp = document.createElement("div");
temp.innerHTML = text;
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
$("a.expand_code").click(function(){
var dest = this.href;
var item = $('div.expand_div_' + this.id);
var state = item.html();
if(state == ''){
$.get('https://api.chgtaxihe.top/sources_fetch?url=' + window.btoa(dest), function(result){
s = hljs.highlightAuto(HTMLDecode(result)).value;
item.html('<pre>' + s + '</pre>');
item.slideToggle();
});
}
item.slideToggle();
return false;
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51