summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRichard Yu <yurichard3839@gmail.com>2019-11-09 23:43:00 +0800
committerYousong Zhou <yszhou4tech@gmail.com>2019-11-15 19:03:34 +0800
commitdc26e2d037504f3fcd3ce1947770fbce92d48241 (patch)
tree8bbb861df4c7191e97a7308829e6b028fa081eba
parentc1f41f2919f079b82782c2ef282c834b60106621 (diff)
luci-app-shadowsocks-libev: add server links import feature
Signed-off-by: Richard Yu <yurichard3839@gmail.com>
-rw-r--r--applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/shadowsocks-libev.js66
-rw-r--r--applications/luci-app-shadowsocks-libev/htdocs/luci-static/resources/view/shadowsocks-libev/servers.js54
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 3aaaa50121..3d111d5791 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 d46bfb0aa7..5951e92e51 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)