From dc26e2d037504f3fcd3ce1947770fbce92d48241 Mon Sep 17 00:00:00 2001 From: Richard Yu Date: Sat, 9 Nov 2019 23:43:00 +0800 Subject: [PATCH] luci-app-shadowsocks-libev: add server links import feature Signed-off-by: Richard Yu --- .../resources/shadowsocks-libev.js | 66 +++++++++++++++++++ .../view/shadowsocks-libev/servers.js | 54 +++++++++++++-- 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js index 3aaaa5012..3d111d579 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js @@ -238,5 +238,71 @@ return L.Class.extend({ window.open(L.url('admin/system/opkg') + '?query=' + opkg_package, '_blank', 'noopener'); }; + }, + parse_uri: function(uri) { + var scheme = 'ss://'; + if (uri && uri.indexOf(scheme) === 0) { + var atPos = uri.indexOf('@'), hashPos = uri.lastIndexOf('#'), tag; + if (hashPos === -1) { + hashPos = undefined; + } else { + tag = uri.slice(hashPos + 1); + } + + if (atPos !== -1) { // SIP002 format https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html + var colonPos = uri.indexOf(':', atPos + 1), slashPos = uri.indexOf('/', colonPos + 1); + if (colonPos === -1) return null; + if (slashPos === -1) slashPos = undefined; + + var userinfo = atob(uri.slice(scheme.length, atPos) + .replace(/-/g, '+').replace(/_/g, '/')), + i = userinfo.indexOf(':'); + if (i === -1) return null; + + var config = { + server: uri.slice(atPos + 1, colonPos), + server_port: uri.slice(colonPos + 1, slashPos ? slashPos : hashPos), + password: userinfo.slice(i + 1), + method: userinfo.slice(0, i) + }; + + if (slashPos) { + var search = uri.slice(slashPos + 1, hashPos); + if (search[0] === '?') search = search.slice(1); + search.split('&').forEach(function(s) { + var j = s.indexOf('='); + if (j !== -1) { + var k = s.slice(0, j), v = s.slice(j + 1); + if (k === 'plugin') { + v = decodeURIComponent(v); + var k = v.indexOf(';'); + if (k !== -1) { + config['plugin'] = v.slice(0, k); + config['plugin_opts'] = v.slice(k + 1); + } + } + } + }); + } + return [config, tag]; + } else { // Legacy format https://shadowsocks.org/en/config/quick-guide.html + var plain = atob(uri.slice(scheme.length, hashPos)), + firstColonPos = plain.indexOf(':'), + lastColonPos = plain.lastIndexOf(':'), + atPos = plain.lastIndexOf('@', lastColonPos); + if (firstColonPos === -1 || + lastColonPos === -1 || + atPos === -1) return null; + + var config = { + server: plain.slice(atPos + 1, lastColonPos), + server_port: plain.slice(lastColonPos + 1), + password: plain.slice(firstColonPos + 1, atPos), + method: plain.slice(0, firstColonPos) + }; + return [config, tag]; + } + } + return null; } }); diff --git a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js index d46bfb0aa..5951e92e5 100644 --- a/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js +++ b/applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js @@ -1,21 +1,65 @@ 'use strict'; 'require form'; +'require uci'; +'require ui'; 'require shadowsocks-libev as ss'; -function startsWith(str, search) { - return str.substring(0, search.length) === search; -} +var conf = 'shadowsocks-libev'; return L.view.extend({ render: function() { var m, s, o; - m = new form.Map('shadowsocks-libev', _('Remote Servers'), + m = new form.Map(conf, _('Remote Servers'), _('Definition of remote shadowsocks servers. \ Disable any of them will also disable instances referring to it.')); s = m.section(form.GridSection, 'server'); s.addremove = true; + s.handleLinkImport = function() { + var textarea = new ui.Textarea(); + ui.showModal(_('Import Links'), [ + textarea.render(), + E('div', { class: 'right' }, [ + E('button', { + class: 'btn', + click: ui.hideModal + }, [ _('Cancel') ]), + ' ', + E('button', { + class: 'btn cbi-button-action', + click: ui.createHandlerFn(this, function() { + textarea.getValue().split('\n').forEach(function(s) { + var config = ss.parse_uri(s); + if (config) { + var tag = config[1]; + if (tag && !tag.match(/^[a-zA-Z0-9_]+$/)) tag = null; + var sid = uci.add(conf, 'server', tag); + config = config[0]; + Object.keys(config).forEach(function(k) { + uci.set(conf, sid, k, config[k]); + }); + } + }); + return uci.save() + .then(L.bind(this.map.load, this.map)) + .then(L.bind(this.map.reset, this.map)) + .then(L.ui.hideModal) + .catch(function() {}); + }) + }, [ _('Import') ]) + ]) + ]); + }; + s.renderSectionAdd = function(extra_class) { + var el = form.GridSection.prototype.renderSectionAdd.apply(this, arguments); + el.appendChild(E('button', { + 'class': 'cbi-button cbi-button-add', + 'title': _('Import Links'), + 'click': ui.createHandlerFn(this, 'handleLinkImport') + }, [ _('Import Links') ])); + return el; + }; o = s.option(form.Flag, 'disabled', _('Disable')); o.editable = true; @@ -26,7 +70,7 @@ return L.view.extend({ }, addFooter: function() { var p = '#edit='; - if (startsWith(location.hash, p)) { + if (location.hash.indexOf(p) === 0) { var section_id = location.hash.substring(p.length); var editBtn = document.querySelector('#cbi-shadowsocks-libev-' + section_id + ' button.cbi-button-edit'); if (editBtn) -- 2.25.1