Support customize DoH resolvers

This commit is contained in:
Xylitol
2023-12-30 21:13:48 +08:00
parent 24a846c87a
commit 57050028dd
2 changed files with 57 additions and 39 deletions

View File

@ -13,6 +13,11 @@ PORT = 5125
# define the base directory # define the base directory
_basedir = Path(__file__).resolve().parent _basedir = Path(__file__).resolve().parent
# define the configuration files
_resolvers_conf = _basedir / "../resolvers.conf"
_flows_conf = _basedir / "../scrapeflows.conf"
_auth_conf = _basedir / "authorization"
# initialize the templates # initialize the templates
with open(_basedir / "templates/config.html", "r", encoding="utf-8") as html: with open(_basedir / "templates/config.html", "r", encoding="utf-8") as html:
_config_tmpl = string.Template(html.read()) _config_tmpl = string.Template(html.read())
@ -21,10 +26,6 @@ with open(_basedir / "templates/source.html", "r", encoding="utf-8") as html:
with open(_basedir / "templates/index.html", "r", encoding="utf-8") as html: with open(_basedir / "templates/index.html", "r", encoding="utf-8") as html:
_index_tmpl = string.Template(html.read()) _index_tmpl = string.Template(html.read())
# initialize the DoH resolvers
with open(_basedir / "../resolvers.conf", "r", encoding="utf-8") as doh_reader:
_doh_resolvers = ast.literal_eval(doh_reader.read())
def render_index(saved=None): def render_index(saved=None):
"""Render the index page.""" """Render the index page."""
@ -55,7 +56,7 @@ def render_index(saved=None):
source_html += _source_tmpl.substitute(source) source_html += _source_tmpl.substitute(source)
return _index_tmpl.substitute( return _index_tmpl.substitute(
sources=source_html, resolvers=_doh_resolvers, version=plugin_version() sources=source_html, resolvers=load_resolvers(), version=load_version()
) )
@ -100,8 +101,14 @@ def load_sites():
return dict(sorted(sites.items(), key=lambda x: x[0])) return dict(sorted(sites.items(), key=lambda x: x[0]))
def plugin_version(): def load_resolvers():
"""Get the plugin version from the directory name.""" """Load the list of DoH resolvers."""
with open(_resolvers_conf, "r", encoding="utf-8") as doh_reader:
return ast.literal_eval(doh_reader.read())
def load_version():
"""Load the plugin version from the directory name."""
dir_name = _basedir.parent.name dir_name = _basedir.parent.name
if "-" in dir_name: if "-" in dir_name:
version = dir_name.split("-")[-1] version = dir_name.split("-")[-1]
@ -118,11 +125,10 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
"""Request handler for the HTTP server.""" """Request handler for the HTTP server."""
def do_AUTH(self): def do_AUTH(self):
filepath = _basedir / "authorization" if not _auth_conf.exists():
if not filepath.exists():
return True return True
with open(filepath, "r", encoding="utf-8") as auth_reader: with open(_auth_conf, "r", encoding="utf-8") as auth_reader:
saved_auth = auth_reader.read() saved_auth = auth_reader.read()
if self.headers.get("Authorization") is not None: if self.headers.get("Authorization") is not None:
@ -141,22 +147,21 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
if not self.do_AUTH(): if not self.do_AUTH():
return return
if self.path == "/": self.send_response(200)
self.send_response(200) self.send_header("Content-type", "text/html")
self.send_header("Content-type", "text/html") self.end_headers()
self.end_headers()
filepath = _basedir / "../scrapeflows.conf" if self.path == "/":
if filepath.exists(): # index page
with open(filepath, "r", encoding="utf-8") as conf_reader: if _flows_conf.exists():
with open(_flows_conf, "r", encoding="utf-8") as conf_reader:
saved_conf = json.load(conf_reader) saved_conf = json.load(conf_reader)
self.wfile.write(render_index(saved_conf).encode("utf-8")) self.wfile.write(render_index(saved_conf).encode("utf-8"))
else: else:
self.wfile.write(_index_html.encode("utf-8")) self.wfile.write(_index_html.encode("utf-8"))
elif self.path == "/exit": elif self.path == "/exit":
self.send_response(200) # close the server
self.end_headers()
self.server.server_close() self.server.server_close()
sys.exit() sys.exit()
@ -164,19 +169,27 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler):
if not self.do_AUTH(): if not self.do_AUTH():
return return
filepath = None self.send_response(200)
if self.path == "/save": self.end_headers()
filepath = _basedir / "../scrapeflows.conf" content_length = int(self.headers["Content-Length"])
elif self.path == "/auth": request_body = self.rfile.read(content_length)
filepath = _basedir / "authorization"
if filepath is not None: if self.path == "/save":
content_length = int(self.headers["Content-Length"]) # save the configuration
body = self.rfile.read(content_length) conf = json.loads(request_body.decode("utf-8"))
with open(filepath, "w", encoding="utf-8") as writer: with open(_flows_conf, "w", encoding="utf-8") as conf_writer:
writer.write(body.decode("utf-8")) conf_writer.write(json.dumps(
self.send_response(200) conf["flows"], ensure_ascii=False, indent=2
self.end_headers() ))
with open(_resolvers_conf, "w", encoding="utf-8") as doh_writer:
doh_writer.write(json.dumps(
conf["resolvers"], ensure_ascii=False, indent=2
))
elif self.path == "/auth":
# save the authorization
with open(_auth_conf, "w", encoding="utf-8") as auth_writer:
auth_writer.write(request_body.decode("utf-8"))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -66,7 +66,7 @@
<div class="row"> <div class="row">
<div class="col s12"> <div class="col s12">
<h6>DNS-over-HTTPS Resolvers:</h6> <h6>DNS-over-HTTPS Resolvers:</h6>
<div id="doh" class="chips"></div> <div id="resolvers" class="chips"></div>
</div> </div>
</div> </div>
<h5 class="right-align">${version}</h5> <h5 class="right-align">${version}</h5>
@ -115,10 +115,12 @@
<script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@1.2.2/dist/js/materialize.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@materializecss/materialize@1.2.2/dist/js/materialize.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
const resolvers = ${resolvers} const resolvers = ${resolvers};
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
M.AutoInit(); M.AutoInit();
M.Chips.init(document.getElementById('doh'), {
var resolversElem = document.getElementById('resolvers');
M.Chips.init(resolversElem, {
placeholder: 'Set up resolvers', placeholder: 'Set up resolvers',
secondaryPlaceholder: '+ New resolver', secondaryPlaceholder: '+ New resolver',
data: resolvers.map(resolver => ({tag: resolver})), data: resolvers.map(resolver => ({tag: resolver})),
@ -134,28 +136,31 @@
saveBtn.addEventListener('click', event => { saveBtn.addEventListener('click', event => {
event.preventDefault(); event.preventDefault();
var data = {} var flowsConf = {};
document.querySelectorAll('form').forEach(form => { document.querySelectorAll('form').forEach(form => {
var typesElem = document.getElementById(form.id + '-types'); var typesElem = document.getElementById(form.id + '-types');
var dohElem = document.getElementById(form.id + '-doh'); var dohElem = document.getElementById(form.id + '-doh');
var priorityElem = document.getElementById(form.id + '-priority'); var priorityElem = document.getElementById(form.id + '-priority');
data[form.id] = { flowsConf[form.id] = {
types: M.FormSelect.getInstance(typesElem).getSelectedValues(), types: M.FormSelect.getInstance(typesElem).getSelectedValues(),
doh: M.FormSelect.getInstance(dohElem).getSelectedValues()[0] == '1', doh: M.FormSelect.getInstance(dohElem).getSelectedValues()[0] == '1',
priority: parseInt(priorityElem.value) priority: parseInt(priorityElem.value)
} };
var configs = document.getElementsByClassName(form.id + '-config'); var configs = document.getElementsByClassName(form.id + '-config');
[...configs].forEach(config => { [...configs].forEach(config => {
data[form.id][config.id.substring(form.id.length + 1)] = config.value; flowsConf[form.id][config.id.substring(form.id.length + 1)] = config.value;
}); });
}); });
var resolversInst = M.Chips.getInstance(resolversElem);
var resolversConf = resolversInst.chipsData.map(chip => chip.tag);
fetch('/save', { fetch('/save', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(data, null, 2) body: JSON.stringify({ flows: flowsConf, resolvers: resolversConf }, null, 2)
}).then(response => { }).then(response => {
if (response.ok) { if (response.ok) {
M.toast({ M.toast({