Blog小功能-点击链接自动展开Codeforces代码

前言

codeforces评测机炸了,闲来无事想给blog写个新功能,这个过程里遇到了不少坑,在此记录一下。

另:本人基本没有JavaScript基础,只能根据w3cschool和别人的博客依样画葫芦来的,代码可能巨丑,请见谅。

需求简述

在网页中所有指向型如https://codeforces.com/contest/1338/submission/76667310的超链接下放添加一个可以快速查看源代码的小组件,实际效果如下。


由于博客园不允许js代码,请到这里查看


客户端本地解析

XMLHttpRequest

大体思路:通过客户端(浏览器)发送Get请求,得到网页源代码后解析。

var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function(){
    if(xhr.status === 200){
        // do something
    }
}
xhr.send();

结果遇到了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"

重新配置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

配置好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)

这里还有个小插曲:上述代码在本地正常运行,但是在服务器上失败,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";

另外,可以通过正则来配置跨域白名单,见链接

编写Js代码

最终代码如下,用到了highlight.js

<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>

小问题: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>

UPD:使用Cloudflare Worker

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))
})

因为API变动了,顺手把js脚本也改了(这里就不贴出来了)

posted @ 2020-06-06 15:55  啦啦啦chg  阅读(161)  评论(0)    收藏  举报