|
|
const filenameUtil = require('scripts/filenameUtil')
|
|
|
|
|
|
String.prototype.strictTrim = function () {
|
|
|
let trimed = this.trim()
|
|
|
if ((matcher = trimed.match(/([\s\S]+),$/)) !== null) {
|
|
|
return matcher[1]
|
|
|
}
|
|
|
return trimed
|
|
|
}
|
|
|
|
|
|
function urlsafeBase64Encode(url) {
|
|
|
return $text.base64Encode(url).replace(/\-/g, '+').replace(/\\/g, '_').replace(/=+$/, '')
|
|
|
}
|
|
|
|
|
|
function urlsafeBase64Decode(base64) {
|
|
|
// Add removed at end '='
|
|
|
base64 += Array(5 - base64.length % 4).join('=');
|
|
|
base64 = base64
|
|
|
.replace(/\-/g, '+') // Convert '-' to '+'
|
|
|
.replace(/\_/g, '/'); // Convert '_' to '/'
|
|
|
return $text.base64Decode(base64).replace(/\u0000/, '');
|
|
|
}
|
|
|
|
|
|
function promiseConf(url) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
$http.get({
|
|
|
url: url,
|
|
|
header: {
|
|
|
'User-Agent': 'Surge/1174 CFNetwork/962 Darwin/18.0.0'
|
|
|
},
|
|
|
handler: function (resp) {
|
|
|
let data = resp.data + ''
|
|
|
let filename = url
|
|
|
try {
|
|
|
let matcher = resp.response.runtimeValue().invoke('allHeaderFields').rawValue()["Content-Disposition"].match(/filename="?(.*?)(?:.conf|"|$)/)
|
|
|
filename = matcher[1]
|
|
|
} catch (e) {
|
|
|
filename = filenameUtil.getConfName(url)
|
|
|
}
|
|
|
// 兼容不规范ssr链接
|
|
|
let noPaddingData = data
|
|
|
let padding = noPaddingData.length % 4 == 0 ? 0 : 4 - noPaddingData.length % 4
|
|
|
for (let i = 0; i < padding; i++) {
|
|
|
noPaddingData += '='
|
|
|
}
|
|
|
let decodedData = $text.base64Decode(data) || $text.base64Decode(noPaddingData)
|
|
|
if (/\[Proxy\]([\s\S]*?)\[Proxy Group\]/.test(data)) {
|
|
|
// Surge托管
|
|
|
resolve({
|
|
|
servers: RegExp.$1,
|
|
|
filename: filename,
|
|
|
type: 0
|
|
|
})
|
|
|
} else if (/^(ssr|ss|vmess):\/\//.test(decodedData)) {
|
|
|
let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^(ssr|ss|vmess):\/\//.test(i));
|
|
|
let output = rawLinks.map(i => {
|
|
|
if (/^ssr:\/\//.test(i)) {
|
|
|
let res = decodeSSR([i])
|
|
|
return res.servers
|
|
|
} else if (/^ss:\/\//.test(i)) {
|
|
|
let res = decodeScheme([i])
|
|
|
return res.servers
|
|
|
} else {
|
|
|
let res = decodeVmess([i])
|
|
|
return res.servers
|
|
|
}
|
|
|
})
|
|
|
resolve({
|
|
|
servers: output.reduce((p, c) => {
|
|
|
return p.concat(c)
|
|
|
}, []).join('\n'),
|
|
|
filename: getDomain(url),
|
|
|
type: 4
|
|
|
})
|
|
|
} else if (/^ssr:\/\//.test(decodedData)) {
|
|
|
// SSR订阅
|
|
|
let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^ssr:\/\//.test(i));
|
|
|
let res = decodeSSR(rawLinks);
|
|
|
resolve({
|
|
|
servers: res.servers.join('\n'),
|
|
|
filename: res.sstag || filename,
|
|
|
type: 1
|
|
|
})
|
|
|
} else if (/^ss:\/\//.test(decodedData)) {
|
|
|
// SS订阅
|
|
|
let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^ss:\/\//.test(i));
|
|
|
let serInfo = decodeScheme(rawLinks);
|
|
|
resolve({
|
|
|
servers: serInfo.servers.join('\n'),
|
|
|
filename: serInfo.sstag || filename,
|
|
|
type: 2
|
|
|
})
|
|
|
} else if (/^vmess:\/\//.test(decodedData)) {
|
|
|
let rawLinks = decodedData.split(/[\n\r\|\s]+/g).filter(i => i !== '' && /^vmess:\/\//.test(i));
|
|
|
console.log('rawLinks', typeof rawLinks);
|
|
|
let res = decodeVmess(rawLinks);
|
|
|
console.log('res', res);
|
|
|
resolve({
|
|
|
servers: res.servers.join('\n'),
|
|
|
filename: res.sstag || filename,
|
|
|
type: 3
|
|
|
})
|
|
|
} else {
|
|
|
resolve()
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function getDomain(url) {
|
|
|
if (/https?:\/\/.*?\.(.*?)\.(.*?)(?=\/|$)/.test(url)) {
|
|
|
return `${RegExp.$1.trim()}.${RegExp.$2.trim()}`
|
|
|
}
|
|
|
return '批量导入'
|
|
|
}
|
|
|
|
|
|
function decodeSSR(links) {
|
|
|
let tag = ''
|
|
|
let first = ''
|
|
|
function getParam(key, content) {
|
|
|
let reg = new RegExp(`${key}=(.*?)(?:&|$)`);
|
|
|
let matcher = content.match(reg);
|
|
|
return matcher && matcher[1] ? matcher[1] : '';
|
|
|
}
|
|
|
let decodedLinks = links.map(i => {
|
|
|
let rawContentMatcher = i.match(/^ssr:\/\/(.*?)$/);
|
|
|
if (rawContentMatcher && rawContentMatcher[1]) {
|
|
|
let rawContent = urlsafeBase64Decode(rawContentMatcher[1]);
|
|
|
let rawContentParts = rawContent.split(/\/*\?/g)
|
|
|
let paramsMatcher = rawContentParts[0].match(/^(.*?):(.*?):(.*?):(.*?):(.*?):(.*?)$/);
|
|
|
if (paramsMatcher && paramsMatcher.length === 7) {
|
|
|
let host = paramsMatcher[1];
|
|
|
let port = paramsMatcher[2];
|
|
|
let protocol = paramsMatcher[3];
|
|
|
let method = paramsMatcher[4];
|
|
|
let obfs = paramsMatcher[5];
|
|
|
let pass = urlsafeBase64Decode(paramsMatcher[6]);
|
|
|
let obfsparam = '';
|
|
|
let protoparam = '';
|
|
|
let group = '';
|
|
|
let remarks = '';
|
|
|
if (rawContentParts.length > 1) {
|
|
|
let target = rawContentParts[1];
|
|
|
obfsparam = urlsafeBase64Decode(getParam('obfsparam', target));
|
|
|
protoparam = urlsafeBase64Decode(getParam('protoparam', target));
|
|
|
group = urlsafeBase64Decode(getParam('group', target));
|
|
|
remarks = urlsafeBase64Decode(getParam('remarks', target));
|
|
|
}
|
|
|
if (tag === '' && group !== '') {
|
|
|
tag = group;
|
|
|
}
|
|
|
let finalName = remarks === '' ? `${host}:${port}` : remarks
|
|
|
first = finalName
|
|
|
let res = `${finalName} = shadowsocksr, ${host}, ${port}, ${method}, "${pass}", protocol=${protocol}, obfs=${obfs}`;
|
|
|
res += protoparam ? `, protocol_param=${protoparam}` : '';
|
|
|
res += obfsparam ? `, obfs_param="${obfsparam}"` : '';
|
|
|
return res;
|
|
|
}
|
|
|
else {
|
|
|
return '';
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
return '';
|
|
|
}
|
|
|
});
|
|
|
let sstag = first
|
|
|
if (decodedLinks.length > 1) {
|
|
|
sstag = `批量SSR节点(${decodedLinks.length})`
|
|
|
}
|
|
|
if (tag !== '') {
|
|
|
sstag = tag
|
|
|
}
|
|
|
return { servers: decodedLinks, sstag: sstag }
|
|
|
}
|
|
|
|
|
|
function getServersFromConfFile(params) {
|
|
|
let promiseArray = params.urls.map(i => promiseConf(i))
|
|
|
Promise.all(promiseArray).then(confs => {
|
|
|
for (let idx in confs) {
|
|
|
let res = confs[idx]
|
|
|
let type = res ? res.type : -1
|
|
|
let filename = res ? res.filename : '';
|
|
|
let servers = res ? res.servers.split(/[\n\r]+/).filter(item => item !== '').map(i => i.strictTrim()) : [];
|
|
|
params.handler({ servers: servers, filename: filename, url: params.urls[idx], type: type })
|
|
|
}
|
|
|
}).catch(reason => {
|
|
|
console.error(reason.stack)
|
|
|
params.handler(null)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function isJson(str) {
|
|
|
try {
|
|
|
JSON.parse(str)
|
|
|
} catch (e) {
|
|
|
return false
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
function decodeVmess(links) {
|
|
|
let result = []
|
|
|
let tag = ''
|
|
|
|
|
|
for (let idx in links) {
|
|
|
let link = links[idx]
|
|
|
|
|
|
if (/^vmess:\/\/(.*?)$/.test(link)) {
|
|
|
let content = urlsafeBase64Decode(RegExp.$1)
|
|
|
if (isJson(content)) {
|
|
|
// v2rayN style
|
|
|
let jsonConf = JSON.parse(content)
|
|
|
let group = ''
|
|
|
const ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/16A5366a'
|
|
|
let obfs = `,obfs=${jsonConf.net === 'ws' ? 'ws' : 'http'},obfs-path="${jsonConf.path || '/'}",obfs-header="Host:${jsonConf.host || jsonConf.add}[Rr][Nn]User-Agent:${ua}"`
|
|
|
let quanVmess = `${jsonConf.ps} = vmess,${jsonConf.add},${jsonConf.port},chacha20-ietf-poly1305,"${jsonConf.id}",group=${group},over-tls=${jsonConf.tls === 'tls' ? 'true' : 'false'},certificate=1${jsonConf.type === 'none' && jsonConf.net !== 'ws' ? '' : obfs}`
|
|
|
result.push(quanVmess)
|
|
|
} else {
|
|
|
// Quantumult style
|
|
|
if (/group=(.*?),/.test(content)) {
|
|
|
tag = RegExp.$1
|
|
|
}
|
|
|
result.push(content)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return { servers: result, sstag: tag || `批量V2Ray节点(${result.length})` }
|
|
|
}
|
|
|
|
|
|
function decodeScheme(urls) {
|
|
|
// let urls = params.ssURL
|
|
|
let result = []
|
|
|
let tag
|
|
|
let group = ''
|
|
|
|
|
|
for (let idx in urls) {
|
|
|
let url = urls[idx]
|
|
|
let method, password, hostname, port, plugin
|
|
|
if (!url.includes('#')) {
|
|
|
let name = '无节点名称'
|
|
|
url += `#${name}`
|
|
|
}
|
|
|
tag = $text.URLDecode(url.match(/#(.*?)$/)[1])
|
|
|
if (url.includes('?')) {
|
|
|
// tag = $text.URLDecode(url.match(/#(.*?)$/)[1])
|
|
|
let mdps = url.match(/ss:\/\/(.*?)@/)[1]
|
|
|
let padding = 4 - mdps.length % 4
|
|
|
if (padding < 4) {
|
|
|
mdps += Array(padding + 1).join('=')
|
|
|
}
|
|
|
let userinfo = $text.base64Decode(mdps)
|
|
|
method = userinfo.split(':')[0]
|
|
|
password = userinfo.split(':')[1]
|
|
|
let htpr = url.match(/@(.*?)\?/)[1].replace('\/', '')
|
|
|
hostname = htpr.split(':')[0]
|
|
|
port = htpr.split(':')[1]
|
|
|
let ps = $text.URLDecode(url.match(/\?(.*?)#/)[1])
|
|
|
let obfsMatcher = ps.match(/obfs=(.*?)(;|$)/)
|
|
|
let obfsHostMatcher = ps.match(/obfs-host=(.*?)(&|;|$)/)
|
|
|
if (obfsMatcher) {
|
|
|
let obfs = obfsMatcher[1]
|
|
|
let obfsHost = obfsHostMatcher ? obfsHostMatcher[1] : 'cloudfront.net'
|
|
|
plugin = `obfs=${obfs}, obfs-host=${obfsHost}`
|
|
|
}
|
|
|
if (/group=(.*)(&|;|$)/.test(ps)) {
|
|
|
group = $text.base64Decode(RegExp.$1.trim())
|
|
|
}
|
|
|
} else {
|
|
|
if (/ss:\/\/([^#]*)/.test(url)) {
|
|
|
let mdps = RegExp.$1
|
|
|
if (/^(.*)@(.*?):(.*?)$/.test(mdps)) {
|
|
|
hostname = RegExp.$2
|
|
|
port = RegExp.$3
|
|
|
let methodAndPass = urlsafeBase64Decode(RegExp.$1)
|
|
|
console.log('methodAndPass', methodAndPass);
|
|
|
if (/^(.*?):(.*?)$/.test(methodAndPass)) {
|
|
|
method = RegExp.$1
|
|
|
password = RegExp.$2
|
|
|
}
|
|
|
} else {
|
|
|
let padding = 4 - mdps.length % 4
|
|
|
if (padding < 4) {
|
|
|
mdps += Array(padding + 1).join('=')
|
|
|
}
|
|
|
if (/^(.*?):(.*)@(.*?):(.*?)$/.test($text.base64Decode(mdps))) {
|
|
|
method = RegExp.$1
|
|
|
password = RegExp.$2
|
|
|
hostname = RegExp.$3
|
|
|
port = RegExp.$4
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
let proxy = `${tag} = custom, ${hostname}, ${port}, ${method}, ${password}, https://github.com/lhie1/Rules/blob/master/SSEncrypt.module?raw=true`
|
|
|
if (plugin != undefined) {
|
|
|
proxy += `, ${plugin}`
|
|
|
}
|
|
|
result[idx] = proxy
|
|
|
}
|
|
|
let outName = ''
|
|
|
if (group) {
|
|
|
outName = group
|
|
|
} else if (result.length === 1) {
|
|
|
outName = tag
|
|
|
} else {
|
|
|
outName = `批量ss节点(${result.length})`
|
|
|
}
|
|
|
return { servers: result, sstag: outName }
|
|
|
}
|
|
|
|
|
|
module.exports = {
|
|
|
proxyFromConf: getServersFromConfFile,
|
|
|
proxyFromURL: decodeScheme,
|
|
|
proxyFromVmess: decodeVmess,
|
|
|
proxyFromSSR: decodeSSR
|
|
|
} |